/* ==========================================================================
   OSTUGRUPP — CHOREOGRAPHY
   How things ARRIVE, how they UNFOLD, and how they LEAVE.

   WHERE THIS FILE STOPS AND motion.css BEGINS
   assets/css/motion.css owns the press layer: `:active` feedback, the hover
   fence for touch screens, the two loading states, and the duration tokens
   --og-press / --og-move / --og-ease (motion.css:57-61). It answers a FINGER.
   Nothing in this file re-declares any of that, and no selector here carries
   `:active` or `:hover`.

   This file answers a different question. Before it, the shop had entrances
   for exactly three things — the mini-cart panel (cart-widget.css:57), the
   mega menu (nav.css:209) and the compare bar (wishlist.css:203) — and an
   arrival for nothing else. Twenty product cards landed in one frame, an
   accordion snapped open, and every overlay in the shop took exactly as long
   to leave as it took to arrive.

   THREE MOMENTS, NOT FORTY SMALL ONES
     2 · ARRIVAL     the product grid comes in as a wave, never as a slab
     3 · UNFOLDING   what opens, opens — accordion bodies and the order peek
     4 · DEPARTURE   everything leaves at ~65% of the time it took to arrive,
                     and stops eating taps the moment it starts leaving

   FOUR RULES THIS FILE OBEYS, AND THE REASON FOR EACH
   • transform and opacity ONLY. Not one declaration here animates width,
     height, top, left or any other property that forces layout. Mobile
     PageSpeed 91 is an asset (CLAUDE.md rule 7) and CLS is measured.
   • NOTHING ABOVE THE FOLD FADES IN. An entrance on content that is already
     on screen at load delays Largest Contentful Paint by delay + duration,
     every cold load. Section 2 never touches an element that is inside the
     viewport when the script runs — that decision lives in JS, because it is
     a measurement, and CSS cannot measure. assets/css/hero-polish.css reached
     the same conclusion independently for the front-page h1 — see its
     section 6, "THE HEADLINE FADES NOTHING".
   • NO HIDDEN-BY-DEFAULT CONTENT. `.og-choreo-armed` is added by script and
     only ever to elements below the viewport. With JavaScript off, or if the
     script throws before it runs, every product card is simply visible — the
     behaviour that exists today. A stylesheet that hides content and waits
     for JS to give it back is a blank page waiting to happen.
   • prefers-reduced-motion is COMPLETE. Section 5 removes the movement and
     keeps the RESULT: the cards are there, the accordion is open, the panel
     is gone. main.css:146-148 and a11y.css:241-248 crush every duration to
     ~0.01ms, but a crushed duration does NOT crush an animation-DELAY — an
     armed card would have sat blank for up to 250ms and then snapped in.
     That is why section 5 kills the animation by name instead of by timing.

   PAYMENT (CLAUDE.md invariant 1). Not one selector in this file can reach the
   checkout's right-hand order column. The only checkout class named here is
   `.og-peek*`, which inc/checkout-steps.php:297 renders ABOVE <form
   class="checkout"> — stated as the scope rule of that module in
   checkout-steps.css:12-14. It is a theme-owned class emitted by exactly one
   file, so it cannot match markup produced by a gateway. No rule here selects
   a WooCommerce form row either — the generic row class that WooCommerce also
   uses for the terms checkbox INSIDE the gateway container, and which is
   therefore the one selector that can restyle a gateway while naming nothing
   forbidden.

   LOAD ORDER. inc/choreography.php enqueues this at priority 100 with every
   queued ostugrupp- handle (including ostugrupp-motion, which runs at 99) as a
   dependency, so this file prints LAST. Section 4 needs that: it re-times
   rules that other stylesheets already declare, and it does so at EQUAL
   specificity rather than by escalating past them, so a component author can
   still override it locally.
   ========================================================================== */


/* ==========================================================================
   1 · ROLES
   The pack ships three motion tokens (tokens.css:295-297): --dur-fast 120ms
   for a press, --dur 200ms for hover and focus, and --ease. It ships no
   entrance, no stagger and no exit, because until now the shop had none.

   Per the pack's first rule (README section 3: "Uut väärtust ei lisata … on
   puudu ROLL"), these are added as ROLES, not as loose numbers, and the ease
   is NOT a new curve — every one of them uses the pack's own --ease.

   Every var() carries its literal fallback: the theme deploys as a hand-built
   zip, and a custom property that cannot resolve does not fall back to an old
   value — it becomes the guaranteed-invalid value and takes every consumer
   with it.
   ========================================================================== */
:root {
  /* Arrival */
  --og-enter:   260ms;   /* one card settling into place                     */
  --og-stagger:  36ms;   /* gap between neighbours in a wave                 */
  --og-rise:     12px;   /* how far below its home a card starts             */

  /* Departure. Exit is ~65% of enter, everywhere, on purpose: a thing that
     leaves as slowly as it arrived is what makes an interface feel slow. The
     four pairs are not four opinions — they are the four durations the shop
     ALREADY declares, each with its 65% twin. The enter value is repeated
     here so section 4 can restore it on the open state after re-timing the
     base rule; it is never a new number. */
  --og-in-drawer: 300ms;  --og-out-drawer: 195ms;  /* nav.css:341, filters.css:422 */
  --og-in-scrim:  280ms;  --og-out-scrim:  180ms;  /* nav.css:331, filters.css:396 */
  --og-in-modal:  250ms;  --og-out-modal:  165ms;  /* quickview.css:85, :101       */
  --og-in-menu:   200ms;  --og-out-menu:   130ms;  /* nav.css:136                  */
}


/* ==========================================================================
   2 · ARRIVAL — the product grid comes in as a wave
   --------------------------------------------------------------------------
   The shop grid is the surface a shopper spends the most time looking at, and
   it is the one that had no arrival at all: `ul.products` was replaced whole
   by assets/js/filters.js:168-180 on every sort and every filter, and the new
   twenty cards appeared in a single frame. That reads as a page reload, which
   is exactly what the AJAX filter exists to avoid.

   TWO TRIGGERS, ONE ANIMATION
     a) Scrolling down.  Cards BELOW the viewport when the script runs are
        armed and play once, as they come into view. First view only — an
        element is unobserved the moment it plays, so scrolling back up and
        down again never re-fires it. Anything already on screen is never
        touched at all, so no card the shopper can see is ever hidden.
     b) Results the shopper ASKED for.  A filter, a sort or "load more"
        swaps the grid; those cards play wherever they are, because the
        shopper just requested them and the wave is the answer.

   THE INDEX IS PER BATCH, NOT PER PAGE. `--og-i` is written inline by the
   script, 0…7, and reset for each batch that enters together. On a phone that
   is two cards per row, on a desktop four — so the wave always reads as a
   row sweeping left to right, never as card #47 waiting 1.7 seconds. The cap
   at 7 is what keeps the longest possible delay at 252ms; past that the wave
   would stop being choreography and start being lag.
   ========================================================================== */

/* Armed: below the fold, waiting. `:not(.og-choreo-in)` is belt-and-braces —
   the animation below wins over this declaration while it runs (animations
   outrank normal author declarations), but it does NOT fill forwards, so if
   the class were ever left behind the card would snap back to invisible when
   the animation ended. The script does remove it — this makes a leftover
   harmless rather than fatal. */
.og-choreo-armed:not(.og-choreo-in) {
  opacity: 0;
}

@keyframes og-choreo-arrive {
  from { opacity: 0; transform: translate3d(0, var(--og-rise, 12px), 0); }
  to   { opacity: 1; transform: none; }
}

/* `backwards`, not `both`: the card holds the FIRST frame during its delay
   (so a staggered card is invisible rather than flickering at full strength
   and then dropping), and holds NOTHING afterwards — no lingering transform,
   which means no lingering containing block for any fixed-position child. */
.og-choreo-in {
  animation: og-choreo-arrive var(--og-enter, 260ms) var(--ease, cubic-bezier(.2, .6, .3, 1)) backwards;
  animation-delay: calc(var(--og-i, 0) * var(--og-stagger, 36ms));
}


/* ==========================================================================
   3 · UNFOLDING — what opens, opens
   --------------------------------------------------------------------------
   Three collapsibles in this shop are native <details>, chosen so they work
   with JavaScript off:
     .ostu-acc              Tarne · Tagastus · Garantii   inc/single-product.php:437
     .ostu-psec__collapse   "Kirjuta esimene arvustus"    inc/product-sections.php:160
     .og-peek               Tellimuse kokkuvõte           inc/checkout-steps.php:297

   All three snap. The pack draws the accordion open (D7, "Neli seisundit, üks
   avatud") and states the one motion decision it carries: "Nool pöördub, ei
   vahetu teise ikooni vastu" — the arrow turns, it is not swapped for another
   icon. Both chevrons already rotate (single.css:275, checkout-steps.css:257);
   what neither has is any acknowledgement in the BODY that just appeared.

   WHY THE PANEL HEIGHT IS NOT ANIMATED, AND WILL NOT BE.
   A native <details> can only be height-animated through ::details-content
   with interpolate-size, i.e. by animating `height` — a layout property, on a
   product page with the whole description and the review section below it.
   That is the one thing this file will not do. The panel takes its height in
   one frame, as it does today; the CONTENT then settles into it. The height
   change is a layout the browser was going to do anyway, and it happens
   within 500ms of a tap, so it is outside the CLS window either way.

   DIRECTION MEANS SOMETHING. The panel grows DOWNWARD, so its content enters
   from ABOVE (-6px): it reads as sliding out from under the head that was
   just tapped. Section 2 is the opposite — a card arrives from BELOW, which
   reads as coming up from deeper in the page. Two directions, two meanings,
   used consistently.
   ========================================================================== */

@keyframes og-choreo-unfold {
  from { opacity: 0; transform: translate3d(0, -6px, 0); }
  to   { opacity: 1; transform: none; }
}

/* The child selector is load-bearing on .ostu-acc: a product page renders
   several accordions in one wrapper, and a descendant selector would replay
   every open body whenever any sibling was toggled. */
.ostu-acc[open] > .ostu-acc__body,
.ostu-psec__collapse[open] > .ostu-psec__body {
  animation: og-choreo-unfold var(--og-enter, 260ms) var(--ease, cubic-bezier(.2, .6, .3, 1)) backwards;
}

/* The chevrons already turn — they just turn on a curve nothing else in the
   shop uses (.25s ease, single.css:271). Re-timed onto the pack's own token so
   the rotation and the body that follows it move to the same rhythm. Equal
   specificity (0,1,0); this file prints later. */
.ostu-acc__chevron {
  transition: transform var(--dur, 200ms) var(--ease, cubic-bezier(.2, .6, .3, 1));
}

/* ---- The checkout's order summary unfolds row by row ----------------------
   .og-peek is the one class in this file that lives on the checkout, and it
   is called out here so an auditor finds it in one search rather than by
   accident. inc/checkout-steps.php:297 renders it ABOVE <form class="checkout">
   — the scope rule its own stylesheet states in checkout-steps.css:12-14 — and
   it is a theme-owned class emitted by exactly one file, so it cannot match
   markup any gateway produces. Nothing here reaches the order column.

   Eight steps of 30ms, then flat. A cart with four items unfolds as four
   rows; a cart with twenty unfolds as eight and then arrives. Pure CSS,
   because this list is always in view when it opens and the item count is
   already in the DOM — nth-child needs no script to count it. */
.og-peek[open] .og-peek__item {
  animation: og-choreo-unfold var(--og-enter, 260ms) var(--ease, cubic-bezier(.2, .6, .3, 1)) backwards;
}
.og-peek[open] .og-peek__item:nth-child(1) { animation-delay: 0ms;   }
.og-peek[open] .og-peek__item:nth-child(2) { animation-delay: 30ms;  }
.og-peek[open] .og-peek__item:nth-child(3) { animation-delay: 60ms;  }
.og-peek[open] .og-peek__item:nth-child(4) { animation-delay: 90ms;  }
.og-peek[open] .og-peek__item:nth-child(5) { animation-delay: 120ms; }
.og-peek[open] .og-peek__item:nth-child(6) { animation-delay: 150ms; }
.og-peek[open] .og-peek__item:nth-child(7) { animation-delay: 180ms; }
.og-peek[open] .og-peek__item:nth-child(n+8) { animation-delay: 210ms; }


/* ==========================================================================
   4 · DEPARTURE — leaving is faster than arriving, and it stops eating taps
   --------------------------------------------------------------------------
   Every overlay in this shop takes exactly as long to leave as it took to
   arrive, because each one declares a single `transition` on its base rule and
   the open class only flips the transform. That is the default, and it is the
   single biggest reason an interface feels sluggish: arriving is information,
   leaving is an instruction that has already been given.

   So: enter unchanged, exit at ~65%. One decision, applied to all five.

   HOW IT IS DONE, AND WHY IT IS SAFE
   When the open class is REMOVED, the element's style comes from the base
   rule — so the base rule's duration governs the exit, and the open rule's
   duration governs the entrance. Re-timing therefore means writing the short
   value on the base selector and restoring the original on the open selector.
   Every pair below matches the specificity of the rule it re-times and wins
   on source order alone (see the load-order note in the file header).

   ⚠️ ONLY `transition-duration` IS TOUCHED — never `transition`, never
   `transition-property`. Two of these elements carry a two-property list
   whose second entry is a `visibility` flip with a delay, and that delay is
   what keeps the panel out of the tab order without cutting the slide short
   (documented at filters.css:416-420 and cart-sticky-bar.css:43-49). Both are
   re-timed as PAIRS below so the flip still lands exactly when the slide ends.

   ⚠️ .og-cartbar IS DELIBERATELY ABSENT. Its enter is already 200ms — the
   pack's --dur — and its visibility delay is written in the same token, so
   shortening its exit would mean hard-coding two numbers over a rule that
   currently has none. It is short enough. Left alone on purpose, not missed.
   ========================================================================== */

/* --- 4a · The dead window: an overlay that has left must stop taking taps ---
   This is a correctness fix, and it is what makes 4b safe to ship.

   Every close routine in the theme removes the open class and then removes the
   node on a fixed timer — nav.js:243 (300ms), filters.js:500 (300ms),
   quickview.js:127 (260ms). Between those two moments the backdrop is at
   `opacity: 0` and still `position: fixed; inset: 0` at z-index 1190-1200, and
   an element at zero opacity still receives pointer events. So for ~300ms
   after the drawer visually closes, a full-screen invisible sheet swallows
   every tap. On a phone that is one whole tap thrown away, and the shopper
   has no way to know why.

   nav.css:137 already solved this for the mega panel; three overlays missed
   it. `:not(.is-open)` covers the closing state AND the resting state, so the
   dead window is inert whether it is 300ms long or, after 4b, 420ms.

   It is also what "interruptible" means here: a tap during the slide-out
   lands on the page underneath instead of being eaten by the thing that is
   already on its way out. */
.og-drawer-backdrop:not(.is-visible),
.ostu-filters-backdrop:not(.is-visible),
.ostu-qv:not(.is-open),
.og-drawer:not(.is-open) {
  pointer-events: none;
}

/* --- 4b · Exit at 65% ------------------------------------------------------ */

/* Mobile navigation drawer — nav.css:336-345 */
.og-drawer {
  transition-duration: var(--og-out-drawer, 195ms);
}
.og-drawer.is-open {
  transition-duration: var(--og-in-drawer, 300ms);
}

/* Its backdrop — nav.css:328-334 */
.og-drawer-backdrop {
  transition-duration: var(--og-out-scrim, 180ms);
}
.og-drawer-backdrop.is-visible {
  transition-duration: var(--og-in-scrim, 280ms);
}

/* Quick view — quickview.css:78-103. The overlay and the dialog are re-timed
   together; a dialog that outran its own scrim would read as two objects. */
.ostu-qv__overlay,
.ostu-qv__dialog {
  transition-duration: var(--og-out-modal, 165ms);
}
.ostu-qv.is-open .ostu-qv__overlay,
.ostu-qv.is-open .ostu-qv__dialog {
  transition-duration: var(--og-in-modal, 250ms);
}

/* Mega menu panel — nav.css:126-140 */
.og-mega__panel {
  transition-duration: var(--og-out-menu, 130ms);
}
.og-mega.is-open .og-mega__panel {
  transition-duration: var(--og-in-menu, 200ms);
}

/* Shop filter backdrop — filters.css:391-399 */
.ostu-filters-backdrop {
  transition-duration: var(--og-out-scrim, 180ms);
}
.ostu-filters-backdrop.is-visible {
  transition-duration: var(--og-in-scrim, 280ms);
}

/* The off-canvas filter panel itself.
   Written INSIDE the same media query as the rule it re-times, and that is
   not tidiness. At >=1024px the panel is a static sidebar with no transition
   declared at all, which means `transition-property` is its initial value
   `all` — and a bare `transition-duration` on it would have made every
   property change on the desktop sidebar animate over 195ms.

   The list is [transform, visibility] (filters.css:422). Both entries are
   given explicitly so the visibility flip still lands exactly when the slide
   ends: on the way out at 195ms, on the way in at 0s. Getting this wrong in
   either direction is a real bug — too early and the panel disappears
   mid-slide, too late and 24 off-screen checkboxes stay in the tab order. */
@media (max-width: 1023.98px) {
  .ostu-filters {
    transition-duration: var(--og-out-drawer, 195ms), 0s;
    transition-delay: 0s, var(--og-out-drawer, 195ms);
  }
  .ostu-filters.is-open {
    transition-duration: var(--og-in-drawer, 300ms), 0s;
    transition-delay: 0s;
  }
}


/* ==========================================================================
   5 · prefers-reduced-motion — COMPLETE, NOT PARTIAL
   --------------------------------------------------------------------------
   main.css:146-148 and a11y.css:241-248 already crush every animation and
   transition DURATION to ~0.01ms. For section 4 that is the whole answer: an
   exit at 0.01ms is an exit, the panel is gone, nothing moved.

   For sections 2 and 3 it is not, and this is the trap. A crushed duration is
   not a crushed DELAY. `animation-delay` is untouched by those blocks, and
   `backwards` fill holds the first frame — opacity 0 — for the whole delay.
   An armed card would therefore have sat blank for up to 252ms and then
   appeared instantly: a flash of missing content, produced by the very rule
   meant to protect the person who asked for less motion.

   So the animations are removed BY NAME rather than by timing, and what
   replaces them is the finished state: the card is there, the accordion body
   is there, the order summary is there. This section removes movement, never
   information.

   The script in inc/choreography.php also refuses to arm anything at all when
   the query matches, and disarms live if the OS setting is switched during a
   session. This block is the safety net under that, not the only guard.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {

  .og-choreo-armed,
  .og-choreo-armed:not(.og-choreo-in),
  .og-choreo-in {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }

  .ostu-acc[open] > .ostu-acc__body,
  .ostu-psec__collapse[open] > .ostu-psec__body,
  .og-peek[open] .og-peek__item {
    animation: none !important;
  }

  /* The chevron still turns — it just turns instantly. A rotation that is
     removed entirely would leave an open accordion pointing the same way as a
     closed one, which is information lost, not motion removed. The duration is
     already crushed by main.css:147; naming it here keeps that true even if
     that blanket rule is ever narrowed. */
  .ostu-acc__chevron {
    transition-duration: 0s;
  }
}


/* ==========================================================================
   6 · PRINT — nothing may print blank
   A card armed at `opacity: 0` that has not yet scrolled into view would
   print as an empty box, and print.css cannot know about a class that only
   exists at run time. Everything this file can hide is un-hidden here.
   ========================================================================== */
@media print {
  .og-choreo-armed,
  .og-choreo-armed:not(.og-choreo-in),
  .og-choreo-in,
  .ostu-acc[open] > .ostu-acc__body,
  .ostu-psec__collapse[open] > .ostu-psec__body,
  .og-peek[open] .og-peek__item {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}
