/* ============================================================================
   TIRELESS MOTION SYSTEM — v1.0 (Redesign Phase 3, 2026-07-30)
   The motion half of the design system. Zero dependencies; reads Phase 2
   tokens (../tokens.css). NOT wired to any live page — Phase 4 applies it.

   LAW (inherited, non-negotiable):
   - Motion explains, never performs. One signature motion per view.
   - transform/opacity only — nothing that triggers layout or paint storms.
   - EVERYTHING collapses under prefers-reduced-motion; content is visible
     by default (the reveal classes hide nothing until JS + motion-ok add
     the choreography — no-JS visitors see a complete page).

   HIERARCHY (who may move, and how much):
     1 ambience   (halo canvas, breathe)     continuous, ≤ 6% luminance change
     2 entrance   (section reveals)          once, on scroll into view
     3 feedback   (hover/focus/press)        ≤ 300ms, user-caused only
     4 status     (loading, progress)        only while genuinely waiting
   Two layers may never compete for the same glance.

   TIMING SCALE (from tokens): 150ms feedback · 300ms lift/slide · 700ms
   reveals. DELAY SCALE: stagger step 70ms, cap 420ms — nothing waits longer
   than half a second to exist.
   ========================================================================== */

/* ---- the master switch -------------------------------------------------- */
/* JS adds .tm-on to <html> only when: JS runs AND matchMedia allows motion.
   Every rule below keys off it, so the default experience is static+complete. */

/* ---- 2. entrance choreography ------------------------------------------- */
.tm-on [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity var(--tl-t-slow) var(--tl-ease-out),
    transform var(--tl-t-slow) var(--tl-ease-out);
  transition-delay: calc(var(--tm-i, 0) * 70ms);
}
.tm-on [data-reveal="fade"]  { transform: none; }
.tm-on [data-reveal="scale"] { transform: scale(.96); }
.tm-on [data-reveal="left"]  { transform: translateX(-24px); }
/* transform-only reveal: for anything that may be visible at first paint.
   Opacity stays 1 throughout, so the element paints immediately and is
   eligible to BE the LCP element from the very first frame — it simply
   arrives at its final position a beat later. 14px not 18px: above-the-fold
   movement should be quieter than below-the-fold movement, because the
   visitor is already looking at it. transform never reflows, so this cannot
   contribute to layout shift either.
   No .tm-in rule of its own is needed: the generic .tm-on [data-reveal].tm-in
   below is specificity (0,3,0) against this rule's (0,2,0) AND comes later,
   so it already resets transform to none. */
.tm-on [data-reveal="rise"]  { opacity: 1; transform: translateY(14px); }
.tm-on [data-reveal].tm-in {
  opacity: 1;
  transform: none;
}
/* stagger: parent sets data-stagger; JS assigns --tm-i to children (cap 6) */

/* ---- 3. feedback — hover / focus / press -------------------------------- */
.tm-on .tm-lift { transition: transform var(--tl-t-base) var(--tl-ease-out), box-shadow var(--tl-t-base), border-color var(--tl-t-fast); }
@media (hover: hover) {
  .tm-on .tm-lift:hover { transform: translateY(-3px); box-shadow: var(--tl-e-2); border-color: var(--tl-border-accent); }
}
.tm-on .tm-press:active { transform: translateY(-1px) scale(.985); transition-duration: var(--tl-t-fast); }

/* reveal + lift on the SAME element (a card that both arrives and lifts).
   .tm-lift's transition shorthand above sits later in this file at equal
   specificity to .tm-on [data-reveal], so it was silently replacing the
   reveal's 700ms timing AND wiping its stagger delay — measured on
   pricing.html, where --tm-i read 0/1/2 correctly but transition-delay read
   0s/0s/0s and all three tiers arrived together. Higher specificity (0,3,0)
   restores the reveal timing and keeps the lift's own properties. */
.tm-on [data-reveal].tm-lift {
  transition:
    opacity var(--tl-t-slow) var(--tl-ease-out),
    transform var(--tl-t-slow) var(--tl-ease-out),
    box-shadow var(--tl-t-base), border-color var(--tl-t-fast);
  transition-delay: calc(var(--tm-i, 0) * 70ms);
}
/* ...but a stagger delay must never make a HOVER feel laggy: the third card
   would wait 140ms before lifting. Zeroed while hovered; the reveal keeps it. */
@media (hover: hover) { .tm-on [data-reveal].tm-lift:hover { transition-delay: 0s; } }

/* arrow nudge inside any control: <span class="tm-ar">→</span> */
.tm-on .tm-ar { display: inline-block; transition: transform var(--tl-t-base) var(--tl-ease-out); }
@media (hover: hover) { .tm-on :is(a, button):hover .tm-ar { transform: translateX(4px); } }

/* underline-draw link (inline links; replaces color-only hover) */
.tm-link { background: linear-gradient(currentColor, currentColor) no-repeat 0 100% / 0 1px; }
.tm-on .tm-link { transition: background-size var(--tl-t-base) var(--tl-ease-out); }
@media (hover: hover) { .tm-on .tm-link:hover { background-size: 100% 1px; } }

/* focus is NOT motion — the ring is instant and lives in the core system */

/* ---- 4. status ----------------------------------------------------------- */
/* three-dot thinking pulse (buttons in loading state, assistant typing) */
.tm-dots { display: inline-flex; gap: 5px; }
.tm-dots i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .35; }
.tm-on .tm-dots i { animation: tm-dot 900ms var(--tl-ease-in-out) infinite; }
.tm-on .tm-dots i:nth-child(2) { animation-delay: 150ms; }
.tm-on .tm-dots i:nth-child(3) { animation-delay: 300ms; }
@keyframes tm-dot { 0%, 100% { opacity: .35; transform: none; } 40% { opacity: 1; transform: translateY(-3px); } }

/* skeleton shimmer (app surfaces; marketing rarely waits) */
.tm-skeleton { background: var(--tl-surface-2); border-radius: var(--tl-r-sm); }
.tm-on .tm-skeleton { animation: tm-skel 1200ms var(--tl-ease-in-out) infinite; }
@keyframes tm-skel { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }

/* scroll progress hairline (articles): width driven by --tl-scroll-p from JS */
.tm-progress { position: fixed; top: 0; left: 0; height: 2px; width: 100%;
  transform-origin: 0 50%; transform: scaleX(var(--tl-scroll-p, 0));
  background: var(--tl-grad-display); z-index: 45; pointer-events: none; }

/* ---- 1. ambience — CSS half (the WebGL halo is tireless-halo.js) --------- */
/* breathing glow for hero frames / featured panels — the ONLY infinite
   decorative loop allowed outside the halo canvas, and it is subtle */
.tm-on .tm-breathe { animation: tm-breathe 7s var(--tl-ease-in-out) infinite; }
@keyframes tm-breathe { 0%, 100% { opacity: .85; transform: scale(1); } 50% { opacity: 1; transform: scale(1.04); } }

/* ---- ambient page-head bloom: BUILT, MEASURED, DROPPED (Task 3B) --------
   A soft violet-magenta radial wash behind each page header, breathing on an
   11s cycle. It looked good. It also cost Largest Contentful Paint far more
   than it was worth, measured on a local server at 1280x900, 3 runs, median:

              baseline   with bloom   bloom, no animation   no bloom
     about       120ms      200ms            156ms            108ms
     pricing     108ms      176ms            132ms            152ms
     article     144ms     1040ms            160ms            132ms

   The article is the one that decides it: 144ms -> 1040ms. Those pages exist
   to be found by strangers on phones and are judged on exactly this metric.
   An animated full-width gradient behind the header promotes a compositing
   layer and repaints across the text the browser is trying to report as the
   largest paint — the same harm section 2 forbids, arriving by a different
   route than opacity:0.
   Section 4.2 anticipated this outcome and permitted it. Phase 6 owns the
   page-head showpiece and can revisit ambient light with a budget for it. */

/* ---- page transitions: EXTRACTED (Phase 5 Task 2 §4) --------------------
   The @view-transition at-rule and its ::view-transition-* rules moved to
   redesign/motion/tireless-view-transitions.css. They are global and
   unconditional by construction: an at-rule cannot be scoped under .tm-on, so
   shipping them here would opt every page linking this file into
   cross-document page transitions — and the single-page parity gate cannot
   see that, because it only manifests when navigating BETWEEN two documents.
   Task 3 or Phase 6 owns the decision to ship them. */

/* ---- the collapse -------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  /* the ::view-transition reduced-motion collapse moved with them (§4). */
  /* .tm-on is never added by JS under reduced motion, but belt-and-braces: */
  .tm-on [data-reveal] { opacity: 1; transform: none; transition: none; }
  .tm-on .tm-breathe, .tm-on .tm-dots i, .tm-on .tm-skeleton { animation: none; }
  /* The scroll hairline is the one motion element that is NOT scoped under
     .tm-on — it is a real <div> in the article markup, so it exists for a
     reduced-motion visitor too. It renders nothing (scaleX(0) until JS writes
     --tl-scroll-p, and JS never writes it under reduced motion), but "renders
     nothing" is not the same as "is not there". Absent it is. */
  .tm-progress { display: none; }
  .tm-on .tm-lift, .tm-on .tm-press, .tm-on .tm-ar, .tm-on .tm-link { transition: none; }
}
