/* ==========================================================================
   tools-motion.css : motion layer for the Free AI Tools pages
   /free-ai-tools/** , English and Chinese

   THE RULE THIS FILE OBEYS
   The brief was to add motion without changing the existing visual design,
   structure, content, type, colour, nav, functionality or image placement.
   So every rule here is either (a) inert until tools-motion.js adds a class,
   or (b) a correction to a measured defect, listed and justified at the
   bottom of this file. Nothing here restyles a component.

   That is why the reveal states live behind `.tm-on`, set on <html> by the
   script only when motion is wanted. With JavaScript off, or with
   prefers-reduced-motion set, or on a browser without the APIs, this
   stylesheet changes nothing at all: no element starts hidden, nothing is
   translated, and the page renders exactly as it does today. A reveal that
   parks content at opacity 0 and waits for an observer is the one failure
   mode a motion layer must not have, so the hidden state cannot even be
   expressed until the script has confirmed it can un-hide it.

   MOTION BUDGET
   Only opacity and transform. Nothing here can trigger layout or paint
   during a scroll. Parallax runs as a CSS scroll-driven animation where the
   browser has one, so it costs no JavaScript; everything else is a one-shot
   transition fired by an IntersectionObserver that then stops observing.
   ========================================================================== */

:root {
  --tm-ease: cubic-bezier(.22, .61, .36, 1);
  --tm-rise: 16px;          /* deliberately small: this is punctuation */
  --tm-dur: .62s;
}

/* ------------------------------------------------------------- 1. reveals
   Applied to a chosen set of components, listed in tools-motion.js. Text and
   images get slightly different treatments: text rises, an image settles from
   a fractional scale, which reads as a photograph coming to rest rather than
   a box sliding in. */

.tm-on [data-tm="rise"] {
  opacity: 0;
  transform: translate3d(0, var(--tm-rise), 0);
  transition: opacity var(--tm-dur) var(--tm-ease),
              transform var(--tm-dur) var(--tm-ease);
}
.tm-on [data-tm="art"] {
  opacity: 0;
  transform: scale(.985);
  transition: opacity .8s var(--tm-ease), transform .8s var(--tm-ease);
}

.tm-on [data-tm].is-in { opacity: 1; transform: none; }

/* A stagger for genuinely repeated things only: a row of cards reads as one
   object arriving in sequence. Capped at six, after which everything lands
   together rather than making the reader wait. */
.tm-on [data-tm-i="1"] { transition-delay: .06s; }
.tm-on [data-tm-i="2"] { transition-delay: .12s; }
.tm-on [data-tm-i="3"] { transition-delay: .18s; }
.tm-on [data-tm-i="4"] { transition-delay: .24s; }
.tm-on [data-tm-i="5"] { transition-delay: .30s; }

/* Keyboard focus completes a reveal at once. A focused link inside a block
   that has not been revealed yet is a dead end, and tabbing can reach it
   before scrolling does. */
.tm-on [data-tm]:focus-within { opacity: 1; transform: none; transition: none; }

/* ------------------------------------------------------------ 2. parallax
   The only continuously scroll-linked effect, and only on the two pages that
   already carry a glass-acrylic hero plate. 18px of travel over a full
   viewport of scroll: enough to read as depth, not enough to read as a
   slideshow. Where the browser has no scroll timeline the image simply sits
   still, which is what it does today. */

@supports (animation-timeline: view()) {
  .tm-on .ga-mount img {
    animation: tm-drift linear both;
    animation-timeline: view();
    animation-range: entry 0% exit 100%;
    will-change: transform;
  }
  @keyframes tm-drift {
    from { transform: translate3d(0, 18px, 0); }
    to   { transform: translate3d(0, -18px, 0); }
  }
}

/* ------------------------------------------------- 3. the one pinned panel
   Measured rather than chosen. On the Custom Instructions cheat sheet the
   platform switcher sits at the top of a 1029px panel in a 900px viewport:
   945px of travel, so pinning it means ChatGPT / Claude / Gemini stay
   reachable while you read a long prompt. That is the only place under
   /free-ai-tools/ where a sticky panel has somewhere to go.

   .tk-out-col in toolkit.css is already declared sticky and is NOT enabled
   here, because measuring all twelve builder tasks showed the output column
   is always the taller of the two (898 to 907px against a form of 408 to
   624px), so it defines the row height and sticky has zero travel. Enabling
   it would change nothing; making it work would mean restructuring the grid,
   which the brief rules out. It is left exactly as it is.

   Desktop only, and never on touch: a pinned bar on a phone costs screen
   height the prompt needs more. */

@media (min-width: 1024px) and (hover: hover) {
  .tm-sticky .ci-tabs {
    position: sticky;
    /* The background has to start at the bar, not below it, or prompt text
       scrolls through the strip between the two. style.css shrinks the header
       to 74px once the page moves, so the pinned box starts 8px above the
       nominal height and pays the difference back as padding: the tabs
       themselves still sit 12px clear of the bar, and the negative margin
       cancels the padding so nothing moves while the panel is unpinned. */
    top: calc(var(--ux-hdr, 80px) - 8px);
    padding-top: 20px;
    margin-top: -20px;
    padding-bottom: 10px;
    z-index: 2;
    background: var(--bg-lift, #0b0b0b);
  }
}

/* ======================================================= reduced motion
   Belt and braces. The script already withholds .tm-on when the OS asks for
   reduced motion, so these rules should never be needed; they exist because
   the setting can change after the page has loaded, and a page that has
   already revealed its content must not animate anything afterwards. */

@media (prefers-reduced-motion: reduce) {
  .tm-on [data-tm],
  .tm-on [data-tm].is-in {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .tm-on .ga-mount img { animation: none !important; transform: none !important; }
}

@media (forced-colors: active) {
  .tm-on [data-tm] { opacity: 1 !important; transform: none !important; }
}

@media print {
  .tm-on [data-tm] { opacity: 1 !important; transform: none !important; }
  .tm-sticky .ci-tabs { position: static; }
}

/* ==========================================================================
   MEASURED CORRECTIONS

   Everything below fixes a defect found by _build/spacing_audit.py, which
   rendered all 30 pages at 1440, 1024 and 390 and reported the dominant
   value for every spacing property. Only values that broke the pattern are
   touched. What the audit found already consistent, and therefore what is
   NOT changed here: every container width (1180px on all 30), all 30 column
   and row gap pairs, card padding and border radius per component, input
   height and font size, and empty spacer elements (there are none).

   Two things that looked like inconsistencies and are not, so they are left
   alone: .tk-btn measures 46px and .tk-btn--sm measures 40px, which is a
   deliberate size modifier used consistently; and .ci-mode is 96px in
   English against 77px in Chinese, which is only Chinese setting shorter.
   ========================================================================== */

/* ------------------------------------------- correction 1: header clearance
   The fixed bar is 80px (70px below 1350px). Twenty-six of the thirty pages
   clear it by 63px on a desktop and 50px on a phone. Four did not clear it
   at all: the toolkit hub ran its hero plate 8px UNDER the bar at 1440px and
   19px under it at 390px, and the visibility checker ran its breadcrumb 5px
   and 16px under. Both templates gave their first section 72px of padding
   against a bar 80px tall.

   The fix is the formula the other twenty-six already use, so all thirty now
   clear the bar by the same amount rather than by a number picked here.

   :first-of-type, not :first-child: the support card is <main>'s first child
   on these pages, so :first-child matched nothing and the correction silently
   did not apply. */

.tk .section.tk-hero,
main > .avc-section:first-of-type {
  padding-top: calc(var(--ux-hdr, 80px) + var(--ux-top, 60px));
}

/* ------------------------------ correction 2: the task grid's ragged rows
   The twelve task buttons in the prompt builder measured 105px in rows one
   and three and 124px in row two (85px and 68px in Chinese), so a grid whose
   whole job is to look like one set of twelve equal choices had rows of three
   different heights. Equal auto rows make every card the height of the
   tallest, which is what the design already looks like it intends. */

.tk-tasks { grid-auto-rows: 1fr; }

/* ----------------------------- correction 3: letting sticky work at all
   style.css sets `overflow-x: hidden` on html and body. That makes both
   scroll containers, and a sticky element sticks to its nearest scrollport,
   which is then <body> -- an element that never scrolls. So position:sticky
   is dead site-wide: computed style still reads "sticky" and the element
   never moves. site-ux.css already records this trap killing the geo
   template's header.

   `clip` prevents sideways overflow exactly as `hidden` does but creates no
   scroll container, and it is the one overflow value that pairs with
   `visible` on the other axis without forcing it to `auto`.

   Scoped to .tm-sticky, which tools-motion.js puts on <html> only after
   measuring that the panel has somewhere to go, so the two cheat-sheet pages
   are the only ones whose overflow behaviour changes. An earlier version
   scoped this with html:has(.tm-sticky .ci-tabs) and silently matched
   nothing: the class is ON <html>, and html is not its own descendant, so
   :has() could never find it. The tabs kept their sticky declaration and
   kept not sticking. */

html.tm-sticky,
html.tm-sticky body { overflow-x: clip; }
