/* =============================================================================
   05-motion.css — MOTION & VERTICAL RHYTHM
   =============================================================================

   The foundation layer every other section inherits. It ships five things:

     1. CURVES.  --ease-brand: cubic-bezier(0.2, 0.75, 0.2, 1) is the hero's
        curve, so it is every hover, focus and state change on the site.
        --ease-reveal is its long-tailed sibling: same family, same refusal to
        overshoot, but it spends its last third almost standing still. That
        tail is the whole difference between "it faded in" and "it arrived".

     2. A HIERARCHY OF SILENCE.  Not one interval — three, so the page reads
        as chapters rather than a metronome:

            chapter  clamp(132px, 15vw, 224px)   journey -> commerce, and the
                                                 run-out into the footer
            section  clamp(96px, 11.7vw, 176px)  between two commerce sections
            band     clamp(34px, 3.6vw, 56px)    inside a strip / plate

        At 1440 that is a 168px silence between two sections and 216px at a
        chapter break; at 768, 96px and 132px; at 390, 80px and 112px. The
        ratio between them holds at every width, which is what makes the
        hierarchy legible instead of accidental.

        Adjacent sections SHARE one silence: each side of a boundary gives
        --rhythm-half, which is exactly half of --rhythm at every width, so a
        gap is one interval and never two. Only the page's two register
        changes are stated as exceptions. A single interval repeated is what
        makes a long scroll feel composed; a considered exception at the
        chapter breaks is what stops it feeling mechanical.

     3. A REVEAL SYSTEM.  Elements rise, sharpen and settle into place as they
        enter the viewport, staggered across a row, choreographed inside a
        section header. Driven by IntersectionObserver in
        assets/js/parts/motion.js.

        No-JS safety is structural, not decorative: NOTHING here hides content
        until `html.ts-motion` exists, and that class is only ever written by
        JS. If the script never loads, never parses, or throws, the page is
        simply a page. Everything the hide rule covers is either observed or
        released outright by the script on the same tick (`.ts-still`), so a
        target can never be hidden by a watcher that is not watching it.

     4. AMPLITUDE CONTROL.  Every distance and duration is a base times a
        gain, and the gains live on the root element. That is how
        `html.ts-hurry` (set by the script while the viewer is scrolling fast)
        collapses the whole system to a short, flat fade in one declaration
        instead of re-stating every weight — nobody wants to scroll past a
        page of half-materialised cards.

     5. FOCUS + ANCHORS.  A two-tone focus-visible ring built on --focus-ring,
        and scroll-padding that keeps anchor targets clear of the sticky
        header (--header-h is measured by the script, static fallback here).

   The journey owns its own motion. Nothing here selects inside `.journey`.

   Loads first among the parts, so section workstreams can override the
   rhythm with a plain class selector — those rules are scoped through `body`
   and sit one class above a bare selector, no more. The reveal machinery is
   the opposite: its hide rule wraps its target list in :where(), so the list
   contributes nothing and the rule weighs exactly what the .ts-in rule
   weighs. The two are separated by state, not by cascade, and neither can
   win an argument with the other.
   ============================================================================= */


/* -----------------------------------------------------------------------------
   1. TOKENS
   -------------------------------------------------------------------------- */

:root {
  /* --- Curves -----------------------------------------------------------
     One curve for interaction, one for arrival. Both are the hero's family:
     a firm start, no overshoot, a settle. --ease-reveal simply holds the
     settle far longer, which is what reads as weight. */
  --ease-brand:  cubic-bezier(0.2, 0.75, 0.2, 1);
  --ease-reveal: cubic-bezier(0.16, 0.84, 0.24, 1);
  --ease-exit:   cubic-bezier(0.4, 0.0, 0.7, 1);

  /* --- The silence hierarchy -------------------------------------------- */

  /* A chapter break. Used where the page changes register: out of the
     cinematic journey into commerce, and out of the last section into the
     footer. The biggest silence on the page, and there are only ever two.
     Held at ~1.28x the section interval at EVERY width — the two clamps
     track each other rather than each bottoming out on its own, which is
     what stops the hierarchy flattening into one value on a tablet. */
  --rhythm-chapter:      clamp(132px, 15vw, 224px);
  /* 125px at 1440 read as a hair under the band it follows; the chapter
     break now clears 132px there, which is the deepest silence on the page
     and the one interval a reader is meant to feel. */
  --rhythm-chapter-half: clamp(84px, 9.2vw, 136px);

  /* A section break. The page's default interval, and the one that repeats.
     --rhythm-half is exactly half of --rhythm at every width, so a shared
     boundary measures one interval and never 1.16 of one. */
  --rhythm:        clamp(96px, 11.7vw, 176px);
  --rhythm-half:   clamp(48px, 5.85vw, 88px);

  /* Inside a band — a strip, a plate, a row of trust marks. A band is not a
     chapter; its own padding stays tight so the silence AROUND it is what
     the eye measures. */
  --rhythm-band:   clamp(34px, 3.6vw, 56px);

  --rhythm-tight:  clamp(64px, 7vw, 112px);
  --rhythm-loose:  clamp(128px, 15vw, 232px);

  /* Rhythm inside a section: header -> content, and lede -> header. */
  --rhythm-gutter: clamp(34px, 3.9vw, 62px);

  /* The foot of a grid. A pagination rail 56px under 1,200px of product grid
     read as the last row's caption; at 96px it is what it is — the end of the
     page, ruled, with the next page on the other side of it. */
  --rhythm-grid-foot: clamp(64px, 6.7vw, 96px);
  --rhythm-lede:   clamp(12px, 1.3vw, 22px);

  /* --- Reveal -----------------------------------------------------------
     Bases are what a weight class overrides on the element; gains are what
     the root scales globally. Keeping them separate is what lets --ease and
     amplitude be re-timed for a fast scroll without restating a single
     distance. */
  --reveal-dur-base:   820ms;
  --reveal-rise-base:  26px;
  --reveal-blur-base:  0px;
  --reveal-scale-base: 1;
  --reveal-step:       78ms;
  --reveal-lead:       0ms;

  --reveal-gain:   1;   /* distance / blur multiplier */
  --reveal-gain-t: 1;   /* duration / delay multiplier */

  /* The opacity a pending reveal target sits at. Flipped to 1 — not
     overridden per selector — by reduced motion and by print, which is how
     the hide rule below stays the ONLY place the target list is written. */
  --reveal-hidden: 0;

  /* Sticky-header offset for anchors; JS overwrites with the measured value. */
  --header-h: 156px;

  /* Every interactive transition on the brand curve. main.css declares this
     token and the whole sheet reads from it, so re-declaring it here re-times
     every hover, focus and state change on the site at once. */
  --transition:
      color 0.24s var(--ease-brand),
      background-color 0.24s var(--ease-brand),
      border-color 0.24s var(--ease-brand),
      fill 0.24s var(--ease-brand),
      box-shadow 0.42s var(--ease-brand),
      transform 0.42s var(--ease-brand),
      opacity 0.32s var(--ease-brand);

  /* A hairline of brand green with a soft halo behind it, rather than one
     chunky 3px slab. Used by main.css and woocommerce.css on focused fields
     as well as by the :focus-visible rule below. */
  --focus-ring:
      0 0 0 1px rgba(var(--color-primary-rgb), 0.85),
      0 0 0 5px rgba(var(--color-primary-rgb), 0.16);
}


/* -----------------------------------------------------------------------------
   2. ANCHOR SCROLLING
   -------------------------------------------------------------------------- */

html {
  scroll-behavior: smooth;
  /* The header is position: sticky, so an anchor that lands at scroll-top
     lands underneath it. Clear the header plus a breath. */
  scroll-padding-top: calc(var(--header-h, 156px) + var(--rhythm-lede));
}

/* Same clearance for anything focused or targeted programmatically. */
:target,
[id]:focus {
  scroll-margin-top: calc(var(--header-h, 156px) + var(--rhythm-lede));
}

/* The skip link should land on the top of the content, not below it. */
#main-content { scroll-margin-top: 0; }


/* -----------------------------------------------------------------------------
   3. FOCUS
   -------------------------------------------------------------------------- */

:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
}

/* The halo goes on the things a keyboard actually lands on. Kept off the
   generic rule so a stray focus target never loses its own elevation. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[role="button"]:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
  box-shadow: var(--focus-ring);
}

/* Never leave a focus ring on a target reached by the skip link. */
#main-content:focus,
#main-content:focus-visible {
  outline: none;
  box-shadow: none;
}


/* -----------------------------------------------------------------------------
   4. VERTICAL RHYTHM

   The model is subtractive, not additive. Every section contributes HALF an
   interval on each of its sides, so two stacked sections naturally sum to
   exactly one interval and no rule has to cancel another one out. Only the
   places where the page changes register are stated explicitly: the two
   chapter breaks, and the band.

   Applied through `body` so the base interval survives a section workstream
   that re-declares padding with a bare class — and stays overridable by
   anything more specific than that.

   `.features-strip` is deliberately NOT in this list. It is a band: a plate
   with its own ground and border, holding four trust marks. It was carrying
   a chapter's worth of padding around a 42px icon, which read as an empty
   room. A band keeps its content close and lets the silence around it do the
   spacing.
   -------------------------------------------------------------------------- */

body :is(
  .categories-section,
  .products-section,
  .promo-split-section,
  .blog-section,
  .shop-page,
  .single-product-page,
  .blog-page,
  .search-results-page,
  .page-content-section,
  .error-404,
  .u-rhythm
) {
  padding-top: var(--rhythm-half);
  padding-bottom: var(--rhythm-half);
}

/* --- Chapter openings ----------------------------------------------------
   The journey hands over to commerce; a page header plate hands over to its
   content. Those are the moments the register of the page changes, and they
   get the deepest silence on the page. Read only as siblings — nothing in
   this file styles the outro, the plate or the breadcrumb bar. */
body :is(
  .categories-section,
  .products-section,
  .promo-split-section,
  .blog-section,
  .shop-page,
  .single-product-page,
  .blog-page,
  .search-results-page,
  .page-content-section,
  .error-404,
  .u-rhythm
):first-child,
body :is(.journey-outro, .page-banner, .sp-breadcrumb-bar) + :is(
  .categories-section,
  .products-section,
  .promo-split-section,
  .blog-section,
  .shop-page,
  .single-product-page,
  .blog-page,
  .search-results-page,
  .page-content-section,
  .error-404,
  .u-rhythm
) {
  padding-top: var(--rhythm-chapter-half);
}

/* --- The run-out ---------------------------------------------------------
   The last section before the footer. The page should exhale before it
   lands, the way the hero holds its horizon before the scroll takes it. In a
   browser without :has() this rule is simply dropped and the page closes on
   a half interval — quieter than intended, never broken. */
body :is(
  .categories-section,
  .products-section,
  .promo-split-section,
  .blog-section,
  .shop-page,
  .single-product-page,
  .blog-page,
  .search-results-page,
  .page-content-section,
  .u-rhythm
):not(:has(+ :is(
  .categories-section,
  .products-section,
  .promo-split-section,
  .blog-section,
  .shop-page,
  .single-product-page,
  .blog-page,
  .search-results-page,
  .page-content-section,
  .u-rhythm
))) {
  padding-bottom: var(--rhythm-chapter);
}

/* --- The band ------------------------------------------------------------
   Tight inside, spaced outside. The margin carries the silence so that the
   boundary rules above — which only ever set padding — can never reach in
   and inflate the plate itself. */
body .features-strip {
  margin-top: var(--rhythm-half);
  margin-bottom: 0;
  padding-top: var(--rhythm-band);
  padding-bottom: var(--rhythm-band);
}

body :is(.journey-outro, .page-banner) + .features-strip {
  margin-top: var(--rhythm-chapter-half);
}

/* A band introduces the chapter under it, so it sits closer to what follows
   than two sections ever sit to each other. */
body .features-strip + :is(
  .categories-section,
  .products-section,
  .promo-split-section,
  .blog-section,
  .u-rhythm
) {
  padding-top: var(--rhythm-tight);
}

/* --- Rhythm inside a section --------------------------------------------- */

body :is(
  .categories-section,
  .products-section,
  .blog-section
) .section-header,
body .products-section .products-section-header,
body .blog-section .blog-section-header {
  margin-bottom: var(--rhythm-gutter);
}

/* Title + lede pairs read as one unit, so the gutter moves to the lede. */
body .categories-section .section-header { margin-bottom: var(--rhythm-lede); }
body .categories-section .categories-section-subtitle { margin-bottom: var(--rhythm-gutter); }
body .products-section .products-section-header .section-title,
body .blog-section .blog-section-header .section-title { margin-bottom: var(--rhythm-lede); }
body .products-section .products-section-subtitle,
body .blog-section .blog-section-subtitle { margin-bottom: 0; }

/* Single product: the two halves of the layout, then the tabs, then related. */
body .single-product-layout { margin-bottom: var(--rhythm-half); }
body .single-product-page .product-tabs { margin-bottom: var(--rhythm-half); }
body .single-product-page .sp-related { margin-top: var(--rhythm-half); }

/* Shop: the pagination is the foot of the grid — ruled off it, and far enough
   below the last row that it is never read as part of it. */
body .shop-page .shop-pagination,
body .blog-page .shop-pagination,
body .search-results-page .shop-pagination { margin-top: var(--rhythm-grid-foot); }

/* The account page holds one short card. The generic run-out left 216px of
   nothing between it and the footer — half a screen of dead band on the page a
   customer visits most. It closes on the tight interval instead. */
body.woocommerce-account .page-content-section,
body.page-template-page-wishlist .page-content-section {
  padding-bottom: var(--rhythm-tight);
}

/* --- Utilities for the other workstreams --------------------------------- */

.u-rhythm-top     { padding-top: var(--rhythm); }
.u-rhythm-bottom  { padding-bottom: var(--rhythm); }
.u-rhythm-tight   { padding-top: var(--rhythm-tight); padding-bottom: var(--rhythm-tight); }
.u-rhythm-loose   { padding-top: var(--rhythm-loose); padding-bottom: var(--rhythm-loose); }
.u-rhythm-chapter { padding-top: var(--rhythm-chapter); padding-bottom: var(--rhythm-chapter); }
.u-rhythm-band    { padding-top: var(--rhythm-band); padding-bottom: var(--rhythm-band); }
.u-rhythm-flush   { padding-top: 0; padding-bottom: 0; }
.u-gutter         { margin-bottom: var(--rhythm-gutter); }
.u-lede           { margin-bottom: var(--rhythm-lede); }


/* -----------------------------------------------------------------------------
   5. REVEAL

   The target list is written ONCE, in the hide rule below, and mirrored in
   assets/js/parts/motion.js. Everything after it keys off `.ts-in`, which the
   script only ever writes onto something it is actually watching — so the two
   halves cannot drift into a state where content is hidden by a watcher that
   is not watching it. The reduced-motion and print cases flip the
   --reveal-hidden token rather than restating the list.

   Opt in from any template with `data-reveal` or `class="u-reveal"`.
   -------------------------------------------------------------------------- */

@keyframes ts-reveal {
  from {
    opacity: 0;
    transform: translate3d(0, calc(var(--reveal-rise-base) * var(--reveal-gain)), 0)
               scale(var(--reveal-scale-base));
    filter: blur(calc(var(--reveal-blur-base) * var(--reveal-gain)));
  }
  /* Legibility lands before the movement does: opacity is finished at 46%
     of the timeline, and the rest of the reveal is a position settling. The
     eye reads the content while the element is still coming to rest, which
     is the difference between expensive motion and a fade. Durations are cut
     to where the curve stops doing visible work — a transform held past that
     point buys nothing and only delays the element's own hover. */
  46% { opacity: 1; }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
    filter: blur(0px);
  }
}

/* A hairline drawing itself in — for kickers and section rules. */
@keyframes ts-reveal-rule {
  from { opacity: 0; transform: scaleX(0); }
  to   { opacity: 1; transform: none; }
}

/* THE ONLY PLACE CONTENT IS HIDDEN.
   :where() keeps the list at zero specificity, so this rule and the .ts-in
   rule below weigh exactly the same and are mutually exclusive by state
   rather than by cascade. `.ts-still` is the script's release valve for a
   target it has decided not to animate (one holding a sticky child, or
   living inside the journey) — such an element must never stay hidden. */
html.ts-motion :where(
  [data-reveal],
  .u-reveal,

  /* --- The chapter plate ------------------------------------------------
     Every page that is not the home page opens on `.page-banner`, and it is
     the first thing in the viewport. Without it a reveal system that only
     watches home sections and shop grids is, on most of the site, watching
     nothing at all: an account page, a static page or a single post held no
     target whatsoever and the script stood itself down. The container is the
     target rather than the title, because the plate's lede, its framed
     photograph and its foot index are generated content on that same box —
     one target moves the whole composition, and none of its parts can arrive
     without the others. */
  .page-banner .container,

  /* --- Section wrappers -------------------------------------------------
     The ground a chapter resolves out of. Weighted to a pure fade further
     down, so the wrapper never repeats the movement its own parts carry;
     what it does is stop a section from being fully painted while the row
     inside it is still assembling. */
  .features-strip,
  .categories-section,
  .products-section,
  .promo-split-section,
  .blog-section,

  /* --- Layout columns ---------------------------------------------------
     The two halves of the shop, the product and the account. A column that
     holds a sticky child is released by the script rather than animated;
     that check is `matches` as well as `querySelector`, because the sticky
     element here IS the column. */
  .shop-page .shop-layout > *,
  .single-product-page .single-product-layout > *,
  .account-page .account-layout > *,
  .account-page .account-panel,
  .page-content-section .page-article,
  .page-content-section .page-comments,

  /* --- The closing plate and the directory ------------------------------ */
  .site-footer .footer-statement,
  .site-footer .footer-signup,
  .site-footer .footer-index > li,
  .site-footer .footer-grid > *,

  .features-strip .feature-item,
  .categories-section .section-header,
  .categories-section .categories-section-subtitle,
  .categories-section .category-card,
  .products-section .products-section-header,
  .products-section .products-section-card,
  .promo-split-section .promo-split-card,
  .blog-section .blog-section-header,
  .blog-section .blog-section-card,
  .shop-page .shop-toolbar,
  .shop-page .sidebar-widget,
  .blog-page .sidebar-widget,
  .search-results-page .sidebar-widget,
  .single-product-page .sidebar-widget,
  .shop-page .products-grid > *,
  .shop-page .shop-pagination,
  .shop-page .shop-empty,
  .single-product-page .product-info,
  .single-product-page .product-tabs,
  .single-product-page .sp-related,
  .blog-page .blog-card,
  .blog-page .shop-pagination,
  .search-results-page .search-results-group,
  .search-results-page .shop-pagination,
  .page-content-section > .container > *,
  .site-footer .footer-index-item,
  .error-404-code,
  .error-404-title,
  .error-404-text,
  .error-404-search,
  .error-404-actions
):not(.ts-in) {
  opacity: var(--reveal-hidden);
}

/* `backwards` fill holds the from-state through the stagger delay and then
   releases the element completely, so the animation leaves no transform and
   no filter behind to fight a hover lift or trap a sticky child. */
html.ts-motion .ts-in {
  animation:
    ts-reveal
    calc(var(--reveal-dur-base) * var(--reveal-gain-t))
    var(--ease-reveal)
    calc((var(--reveal-i, 0) * var(--reveal-step) + var(--reveal-lead)) * var(--reveal-gain-t))
    backwards;
}

/* Released, not animated. */
html.ts-motion .ts-still { animation: none; }

/* --- Weights -------------------------------------------------------------
   Type leads and blurs in from soft focus; panels follow with the faintest
   scale so an image settles rather than slides; chrome barely moves at all.
   Nothing here can hide content — these rules only set amplitudes. */

/* Type. The longest tail on the page. */
html.ts-motion :is(
  .categories-section .section-header,
  .products-section .products-section-header,
  .blog-section .blog-section-header,
  .error-404-code,
  .error-404-title,
  [data-reveal="head"]
) {
  --reveal-rise-base: 34px;
  --reveal-blur-base: 7px;
  --reveal-dur-base:  1000ms;
}

/* Panels and cards. A 1.4% scale is below the threshold of being seen as a
   zoom and above the threshold of being felt as depth. */
html.ts-motion :is(
  .categories-section .category-card,
  .promo-split-section .promo-split-card,
  .products-section .products-section-card,
  .blog-section .blog-section-card,
  .blog-page .blog-card,
  .shop-page .products-grid > *,
  .search-results-page .search-results-group,
  .single-product-page .product-info,
  .single-product-page .product-tabs,
  .single-product-page .sp-related,
  .account-page .account-panel,
  .site-footer .footer-signup,
  [data-reveal="card"]
) {
  --reveal-rise-base:  22px;
  --reveal-blur-base:  4px;
  --reveal-scale-base: 0.986;
  --reveal-dur-base:   820ms;
}

/* Chrome. Present, never performed. */
html.ts-motion :is(
  .features-strip .feature-item,
  .shop-page .shop-toolbar,
  .shop-page .shop-pagination,
  .blog-page .shop-pagination,
  .categories-section .categories-section-subtitle,
  .error-404-text,
  .error-404-search,
  .error-404-actions,
  .site-footer .footer-index > li,
  .site-footer .footer-grid > *,
  [data-reveal="chrome"]
) {
  --reveal-rise-base: 11px;
  --reveal-blur-base: 0px;
  --reveal-dur-base:  620ms;
}

/* --- Grounds -------------------------------------------------------------
   A section wrapper and a layout column are not objects that arrive; they
   are the ground the objects arrive ON. They resolve — opacity only, no
   distance, no blur — and they resolve FAST, so the parts inside them are
   never waiting on their container. Nothing here doubles a movement that
   something else is already making. */
html.ts-motion :is(
  .features-strip,
  .categories-section,
  .products-section,
  .promo-split-section,
  .blog-section,
  .shop-page .shop-layout > *,
  .single-product-page .single-product-layout > :not(.product-info),
  .account-page .account-layout > *,
  [data-reveal="ground"]
) {
  --reveal-rise-base:  0px;
  --reveal-blur-base:  0px;
  --reveal-scale-base: 1;
  --reveal-dur-base:   460ms;
  --reveal-step:       0ms;
}

/* --- The chapter plate ---------------------------------------------------
   The page's own opening. It carries the hero's weight class — the long
   1000ms tail with real distance and a blur coming out of soft focus —
   because on every page but the home page this IS the first thing seen, and
   it has to feel like the hero's sibling rather than like a banner sliding
   in. The framed photograph is generated content on the same box, so the
   picture and the headline arrive as one object. */
html.ts-motion .page-banner .container {
  --reveal-rise-base: 30px;
  --reveal-blur-base: 6px;
  --reveal-dur-base:  1080ms;
  --reveal-lead:      60ms;
}

/* Its parts step down in reading order off the container's own .ts-in,
   exactly as a section header's do: trail, then kicker + title. The lede,
   the plate and the foot index are pseudo-elements of the container and
   travel with it. */
html.ts-motion .page-banner .container.ts-in > * {
  --reveal-rise-base: 22px;
  --reveal-blur-base: 5px;
  animation:
    ts-reveal
    calc(940ms * var(--reveal-gain-t))
    var(--ease-reveal)
    calc((var(--reveal-inner, 0ms) + 90ms) * var(--reveal-gain-t))
    backwards;
}

/* The statement in the footer plate is display type; give it the type tail. */
html.ts-motion .site-footer .footer-statement {
  --reveal-rise-base: 30px;
  --reveal-blur-base: 6px;
  --reveal-dur-base:  980ms;
}

/* --- Header choreography -------------------------------------------------
   A section header is a group, not an object. The group itself only fades;
   its parts carry the movement and step down in reading order — title, then
   lede, then the link out. Two beats where there was one, with no template
   change and no second observer: the parts key off the parent's own .ts-in.
   The parent's fade and the child's fade multiply, which is exactly the
   wanted shape — the type resolves out of the section's ground. */
html.ts-motion :is(
  .categories-section .section-header,
  .products-section .products-section-header,
  .blog-section .blog-section-header
) {
  --reveal-rise-base:  0px;
  --reveal-blur-base:  0px;
  --reveal-dur-base:   520ms;
}

html.ts-motion :is(
  .categories-section .section-header,
  .products-section .products-section-header,
  .blog-section .blog-section-header
).ts-in > * {
  --reveal-rise-base: 30px;
  --reveal-blur-base: 6px;
  animation:
    ts-reveal
    calc(900ms * var(--reveal-gain-t))
    var(--ease-reveal)
    calc((var(--reveal-i, 0) * var(--reveal-step) + var(--reveal-inner, 0ms)) * var(--reveal-gain-t))
    backwards;
}

/* Beat offsets for a group's parts. Set on every .ts-in child rather than
   only on headers, because the value is inert anywhere it is not read — only
   the header rule above consumes --reveal-inner. */
html.ts-motion .ts-in > *:nth-child(2) { --reveal-inner: 110ms; }
html.ts-motion .ts-in > *:nth-child(3) { --reveal-inner: 195ms; }
html.ts-motion .ts-in > *:nth-child(n + 4) { --reveal-inner: 260ms; }

/* --- The first screen ----------------------------------------------------
   Everything already on screen at load is one composed entrance rather than
   several coincidental ones: the script indexes it top to bottom across the
   whole viewport, not per row, and this widens the step so it reads as a
   single sweep down the page. The lead lets the header settle first. */
html.ts-motion [data-ts-first] {
  --reveal-step: 104ms;
  --reveal-lead: 150ms;
}

/* --- Hurry ---------------------------------------------------------------
   Set by the script while the viewer is scrolling fast. Two numbers collapse
   the entire system to a short flat fade, because nothing looks cheaper than
   a screenful of content still assembling itself as it flies past. */
html.ts-hurry {
  --reveal-gain:   0.34;
  --reveal-gain-t: 0.46;
}

/* --- Opt-in variants for the other workstreams --------------------------- */
html.ts-motion [data-reveal="fade"] { --reveal-rise-base: 0px; --reveal-blur-base: 0px; }
html.ts-motion [data-reveal="rule"].ts-in {
  transform-origin: left center;
  animation-name: ts-reveal-rule;
  animation-duration: calc(1s * var(--reveal-gain-t));
}

/* Manual stagger without touching JS: data-reveal-i="0..7". */
html.ts-motion [data-reveal-i="1"] { --reveal-i: 1; }
html.ts-motion [data-reveal-i="2"] { --reveal-i: 2; }
html.ts-motion [data-reveal-i="3"] { --reveal-i: 3; }
html.ts-motion [data-reveal-i="4"] { --reveal-i: 4; }
html.ts-motion [data-reveal-i="5"] { --reveal-i: 5; }
html.ts-motion [data-reveal-i="6"] { --reveal-i: 6; }
html.ts-motion [data-reveal-i="7"] { --reveal-i: 7; }


/* -----------------------------------------------------------------------------
   6. SCROLL-LINKED (opt-in)

   Where the browser can tie an animation to scroll position rather than to a
   clock, a section can ask for it by hand. Strictly additive: these hooks are
   never applied by this file, they animate transform only, and a browser
   without view timelines simply renders the element where it already is. Use
   them for depth, never for content that must be readable.

     data-motion="drift"     type or a panel rises a few px across its pass
     data-motion="parallax"  media lags its frame; put it on the <img>
     data-motion="settle"    a panel comes to rest as it reaches the middle
   -------------------------------------------------------------------------- */

@keyframes ts-drift    { from { transform: translate3d(0, 14px, 0); }
                         to   { transform: translate3d(0, -14px, 0); } }
@keyframes ts-parallax { from { transform: translate3d(0, -5%, 0) scale(1.1); }
                         to   { transform: translate3d(0, 5%, 0)  scale(1.1); } }
@keyframes ts-settle   { from { transform: scale(1.045); }
                         to   { transform: scale(1); } }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    html.ts-motion [data-motion~="drift"] {
      animation: ts-drift linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    html.ts-motion [data-motion~="parallax"] {
      animation: ts-parallax linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
      will-change: transform;
    }
    html.ts-motion [data-motion~="settle"] {
      animation: ts-settle linear both;
      animation-timeline: view();
      animation-range: entry 10% cover 45%;
    }
  }
}


/* -----------------------------------------------------------------------------
   7. SMALL SCREENS

   Part files load after every media query in main.css, so the breakpoints
   have to live here.
   -------------------------------------------------------------------------- */

@media (max-width: 991px) {
  :root {
    --header-h: 118px;
    --reveal-step: 62ms;
  }
  html.ts-motion [data-ts-first] { --reveal-step: 84ms; }
}

@media (max-width: 767px) {
  :root {
    /* Shorter viewport, shorter silence — 11vw of a phone is not a rhythm.
       The hierarchy is kept, but it is NOT compressed uniformly: a straight
       scale-down put the chapter break within a few px of an ordinary
       section boundary, which is the same as not having one. The chapter
       values keep more of their size so the two breaks still read as breaks
       on a phone, where a section boundary is 88px and a chapter is 108. */
    --rhythm:              clamp(80px, 18vw, 112px);
    --rhythm-half:         clamp(40px, 9vw, 56px);
    --rhythm-chapter:      clamp(112px, 26vw, 156px);
    --rhythm-chapter-half: clamp(66px, 15vw, 92px);
    --rhythm-band:         clamp(26px, 6vw, 38px);
    --rhythm-tight:        clamp(52px, 11vw, 72px);
    --rhythm-loose:        clamp(88px, 20vw, 132px);
    --rhythm-gutter:       clamp(26px, 5.4vw, 38px);
    --header-h:            104px;

    /* A phone is one column: the rise is the whole effect, and the blur is
       the whole cost. Drop the blur, shorten the throw. */
    --reveal-gain:      0.62;
    --reveal-gain-t:    0.82;
    --reveal-blur-base: 0px;
  }

  html.ts-motion :is(
    .categories-section .section-header,
    .products-section .products-section-header,
    .blog-section .blog-section-header,
    .categories-section .category-card,
    .promo-split-section .promo-split-card,
    .products-section .products-section-card,
    .blog-section .blog-section-card,
    .blog-page .blog-card,
    .shop-page .products-grid > *,
    .error-404-code,
    .error-404-title,
    [data-reveal="head"],
    [data-reveal="card"]
  ) {
    --reveal-blur-base: 0px;
  }

  html.ts-motion :is(
    .categories-section .section-header,
    .products-section .products-section-header,
    .blog-section .blog-section-header
  ).ts-in > * {
    --reveal-blur-base: 0px;
  }
}


/* -----------------------------------------------------------------------------
   8. REDUCED MOTION

   main.css declares its own reduce block, but this file loads after it and
   re-declares --transition and scroll-behavior at the same specificity, so
   the overrides have to be repeated here or they would be lost.

   The reveal is neutralised at the token, not per selector: --reveal-hidden
   goes to 1, so the one rule that hides anything stops hiding, and the two
   rules that animate are switched off by name. Nothing here needs to know
   what the target list contains, which is why it cannot fall out of step
   with it.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  :root {
    --transition: none;
    --reveal-hidden: 1;
    --reveal-gain: 0;
    --reveal-gain-t: 0;
  }

  html { scroll-behavior: auto; }

  /* Belt and braces: the script never adds .ts-motion under this setting, but
     if a viewer flips it mid-session the class is already on the element. */
  html.ts-motion .ts-in,
  html.ts-motion .ts-in > *,
  html.ts-motion [data-motion] {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    animation: none !important;
  }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}


/* -----------------------------------------------------------------------------
   9. PRINT
   -------------------------------------------------------------------------- */

@media print {
  :root { --reveal-hidden: 1; }

  html.ts-motion .ts-in,
  html.ts-motion .ts-in > *,
  html.ts-motion [data-motion] {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    animation: none !important;
  }
}
