/* ============================================================================
   Stoimera storefront enhancement layer
   Loaded by every static export under /public/storefronts.

   These 29 storefronts are compiled Next.js exports with no source in this
   repo, so polish has to arrive as a shared layer rather than a rebuild.
   That constrains how this file is allowed to behave:

   1. Almost every rule is wrapped in :where(), which contributes ZERO
      specificity. A storefront's own CSS therefore always wins. The layer
      fills gaps; it never imposes a look. That matters because the 29 brands
      are deliberately different (dark gaming, warm luxury furniture, clean
      skincare) and a shared stylesheet is exactly how they would get flattened
      into one.

   2. !important appears in precisely two places, both justified below:
      the reveal repair (must beat a React inline style) and the
      reduced-motion block (the accessibility floor outranks brand).

   3. Nothing here sets a colour, font-family, or spacing value. Those are
      brand decisions and belong to each storefront.

   Companion: storefront-enhance.js
   ========================================================================= */


/* ----------------------------------------------------------------------------
   1. Reveal repair
   ----------------------------------------------------------------------------
   8 of the 29 storefronts ship 383 elements frozen at opacity:0 — entire
   product catalogues, and on interior-furniture the hero headline itself.
   The exports contain a scroll-reveal hook that renders
   style="opacity:0;transform:translateY(20px)" server-side and then never
   clears it client-side, so the content is permanently invisible.

   The value is in React's props, not just the SSR HTML, so React rewrites it
   on every render. An author !important declaration is the only thing that
   outranks a non-important inline style, which is why it is used here.

   storefront-enhance.js decides *which* pages are actually broken before
   tagging anything — skinfront's reveal works correctly and must be left
   alone. These rules only ever apply to elements the script has tagged.
   -------------------------------------------------------------------------- */

/* An element moves through: judging → (ok | in).

   `judging` and `ok` appear here only as documentation — they deliberately
   have NO rules. An element the script is still measuring, or has cleared as
   healthy, must be styled exactly as its own storefront styles it. Giving
   those states so much as a transition would change the behaviour of elements
   this layer has decided not to touch.

   `in` fades the element in; `shown` is the same resting state with the
   transition removed, and the script promotes every element to it on a timer.
   That second state looks redundant and is the whole reason this works.

   Three shapes were tried before this one, and all three failed the same way —
   they made the content's visibility depend on something that has to keep
   running, and a hidden tab or an unsized iframe stops all three:

   - Pin-then-release across two frames rides on requestAnimationFrame, which
     is suspended outright while a document is hidden. The pin lands, the
     release never arrives, and the layer strands content behind its own
     !important — strictly worse than the bug being fixed.
   - A keyframe animation with `both` fill fails identically: suspended
     animations hold the element at its 0% frame, i.e. opacity 0.
   - A transition alone fails even with the resting value marked !important.
     Measured in Chrome: a transition started while the document was hidden
     sits at playState "running", currentTime 0, and does not resume when the
     page becomes visible again. getComputedStyle reports the interpolated
     value — 0 — so the element stays invisible indefinitely. Removing the
     transition snaps it to 1, which is what proves the cascade was never the
     problem.

   So the transition here is decoration with a deadline: `shown` arrives after
   it should have finished, cancels it outright, and pins the element visible
   whether or not a single frame of it ever rendered. Fails toward visible,
   which is the only acceptable direction for a layer whose entire job is
   content that would not otherwise appear.

   The element's own authored transform is what the transition runs from, so
   each storefront keeps its intent — rise, slide from the left, draw in. What
   is unified is the curve, the duration and the stagger: one expo-out across
   all 29, which is what makes the portfolio read as one studio's work rather
   than 29 unrelated easings. */
[data-sf-reveal="in"],
[data-sf-reveal="shown"] {
  opacity: 1 !important;
  transform: none !important;
}

[data-sf-reveal="in"] {
  transition:
    opacity 520ms cubic-bezier(0.16, 1, 0.3, 1) var(--sf-reveal-delay, 0ms),
    transform 520ms cubic-bezier(0.16, 1, 0.3, 1) var(--sf-reveal-delay, 0ms);
}

[data-sf-reveal="shown"] {
  transition: none !important;
}

/* Fail-safe for scripting disabled. Without JS nothing gets tagged above, so
   the frozen elements would stay invisible. Excludes the two inline-opacity
   patterns that are *deliberately* hidden: the off-screen announcer
   (pointer-events:none) and the collapsed announcement bar (height:0px),
   which framer-motion opens correctly on its own. */
@media (scripting: none) {
  [style*="opacity:0"]:not([style*="pointer-events:none"]):not([style*="height:0px"]),
  [style*="opacity: 0"]:not([style*="pointer-events: none"]):not([style*="height: 0px"]) {
    opacity: 1 !important;
    transform: none !important;
  }
}


/* ----------------------------------------------------------------------------
   2. Reduced motion — the accessibility floor
   ----------------------------------------------------------------------------
   Only 4 of 29 storefronts honour prefers-reduced-motion. The other 25 run
   entrance animations, parallax and infinite marquees at full strength for
   users who have asked the OS not to. This is the one place the layer is
   allowed to override brand outright.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  [data-sf-reveal="in"] {
    transition: none !important;
  }


  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    /* Stops infinite marquees and looping ken-burns without removing
       animations that only run once and communicate state. */
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}


/* ----------------------------------------------------------------------------
   3. Typography
   -------------------------------------------------------------------------- */

/* 0 of 29 set this. text-wrap: balance is already present everywhere for
   headings; pretty is its counterpart for body copy and kills the one-word
   last line that makes a paragraph look unfinished. */
:where(p, li, dd, blockquote, figcaption, summary) {
  text-wrap: pretty;
}

/* Prices and specs sit in grids and columns where digits are read against
   each other, so they need fixed-width figures. Only 2 of 29 storefronts set
   this. No storefront uses a `price` class, so storefront-enhance.js finds
   these by matching text content and adds the hook. */
:where(.sf-num) {
  font-variant-numeric: tabular-nums;
}


/* ----------------------------------------------------------------------------
   4. Interaction
   -------------------------------------------------------------------------- */

/* Only 3 of 29 give any pressed response. An interactive element that looks
   identical while held reads as broken, so this supplies the third state
   (rest / hover / active) where a storefront hasn't defined one. Zero
   specificity, so any storefront with its own press state keeps it. */
@media (hover: hover) and (pointer: fine) {
  :where(button, [role="button"], [type="button"], [type="submit"]) {
    transition: transform 120ms ease-out;
  }

  :where(button, [role="button"], [type="button"], [type="submit"]):not(:disabled):active {
    transform: scale(0.98);
  }
}

/* Native image dragging inside a link, carousel or slider steals the pointer
   and leaves a ghost image mid-gesture. 0 of 29 storefronts prevent it. */
:where(a, button, [role="button"], [class*="carousel"], [class*="slider"], [class*="swiper"], [class*="marquee"])
  :where(img, picture, svg) {
  -webkit-user-drag: none;
  -webkit-user-select: none;
  user-select: none;
}
