/* =====================================================================
   DIO Interior Laboratory — motion layer.

   Loaded after css/style.css, and paired with js/motion.js. Two rules kept
   this file honest:

   1. Nothing here may be load-bearing. Every state that HIDES something is
      gated behind `html.motion`, a class js/motion.js only adds once GSAP has
      actually arrived and the visitor has not asked for less motion. With no
      JS, a blocked CDN or a paused visitor, none of those rules match and the
      page is exactly the page css/style.css describes.
   2. The three new components below (.progress, .marquee, .approach) are
      styled to look finished WITHOUT any animation. The scroll bar sits at
      zero, the ticker sits still, the band keeps its CSS gradient. Motion is
      the bonus, never the floor.

   The tokens all come from css/style.css's :root.
   ===================================================================== */

/* Registered so they can be interpolated: an unregistered custom property is
   a string to the cascade and jumps between values instead of transitioning.
   --py is a percentage of the slot's own height, not a pixel count, so the
   parallax travel stays inside the scale below at every card size. */
@property --py{syntax:'<percentage>';inherits:true;initial-value:0%}
@property --pz{syntax:'<number>';inherits:true;initial-value:1}

/* ---------------------------------------------------------------------
   1. Scroll progress
   A 2px gradient hairline over the top edge. scaleX, so it composites; the
   gradient compresses with it, which reads as intended rather than wrong.
   --------------------------------------------------------------------- */
.progress{position:fixed;top:0;left:0;right:0;height:2px;z-index:50;pointer-events:none;opacity:0;transition:opacity .4s var(--ease)}
html.motion .progress{opacity:1}
.progress span{display:block;height:100%;transform:scaleX(0);transform-origin:0 50%;background:var(--grad-terra);will-change:transform}

/* ---------------------------------------------------------------------
   2. Materials ticker
   Two identical rows in one max-content flex line; js/motion.js slides the
   pair by exactly one row's width and wraps, so the seam never shows. The
   mask fades both ends into the page instead of ending on a hard edge.
   --------------------------------------------------------------------- */
.marquee{
  position:relative;overflow:hidden;
  padding:clamp(16px,2.2vw,28px) 0;
  border-top:1px solid var(--line);border-bottom:1px solid var(--line);
  background-image:linear-gradient(90deg,rgba(217,199,176,.4),rgba(234,232,227,0) 42%,rgba(158,79,61,.12));
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
}
.marquee__vp{display:flex;width:max-content;will-change:transform}
.marquee__row{display:flex;align-items:center}
.marquee__row li{
  display:flex;align-items:center;gap:clamp(16px,2.4vw,36px);padding-right:clamp(16px,2.4vw,36px);
  white-space:nowrap;font-family:var(--serif);font-weight:400;
  font-size:clamp(1.5rem,4vw,2.9rem);line-height:1.12;color:var(--ink);
}
/* #7E3A2B, not --terracotta: at .32em these are 8-15px, and --terracotta is
   only 3.63:1 on the ticker's own warm band. Decorative and aria-hidden, so WCAG
   would exempt them — but this project's rule is every text, so: 5.22:1. */
.marquee__row li span{font-family:var(--sans);font-size:.32em;line-height:1;color:#7E3A2B}

/* ---------------------------------------------------------------------
   3. The approach band
   Full-bleed dark. .approach__bg holds js/fog.js's canvas and,
   above it, a scrim that is what actually holds the contrast floor here.

   Not the tone choice: the shader (js/fog.js, ported from Vanta) ends on
   mix(base, colour, f*f*f + .6*f*f + .5*f), and with blurFactor .62 that
   factor reaches ~2.09 — so it EXTRAPOLATES past highlightColor rather than
   stopping at it. Measured brightest pixel is rgb(139,79,56), already lighter
   than the #7A4632 highlight; the shader's ceiling is about rgb(215,116,78).
   The scrim is sized against that ceiling, not against the measurement, so
   the floor survives a future change to blurFactor / speed / zoom:
   over rgb(215,116,78) at the thinnest point it gives --bone 7.1:1,
   --sand 4.8:1 and the .82 body 5.4:1. Do not thin it.
   --------------------------------------------------------------------- */
.approach{
  position:relative;isolation:isolate;overflow:hidden;
  color:var(--bone);background:#241C18;background-image:var(--grad-dusk);
  margin-top:clamp(56px,8vw,112px);
}
.approach__bg{position:absolute;inset:0;z-index:-1}
.approach__fog{position:absolute;inset:0}
/* js/fog.js sets no inline geometry on its canvas, so this is what sizes it —
   and pinning it is also what stops a resize from leaving a canvas wider than
   the section and starting a horizontal scrollbar. */
.approach__fog canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
.approach__bg::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(20,15,12,.62),rgba(20,15,12,.48) 44%,rgba(20,15,12,.7));
}
.approach__in{
  position:relative;
  padding-top:clamp(64px,9vw,128px);padding-bottom:clamp(64px,9vw,128px);
}
.approach .sec-head h2.label::before{background:var(--grad-rule-light)}
.approach__headline{max-width:22ch;margin-top:4px;text-wrap:balance}
.approach__text{max-width:46ch;margin-top:clamp(18px,2.4vw,28px);font-size:clamp(1.02rem,1.35vw,1.2rem);color:rgba(245,244,241,.88)}
.approach__principles{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(20px,3vw,48px);margin-top:clamp(36px,5vw,72px);
}
.approach__principles li{position:relative;display:grid;gap:8px;align-content:start;padding-top:16px}
.approach__principles li::before{content:"";position:absolute;top:0;left:0;right:0;height:1px;background:var(--grad-rule-light)}
.approach__principles .n{font-size:12px;font-weight:600;letter-spacing:.08em;color:var(--sand);font-variant-numeric:tabular-nums}
.approach__principles b{font-family:var(--serif);font-weight:400;font-size:clamp(1.25rem,1.9vw,1.65rem);line-height:1.15}
.approach__principles .t{font-size:14px;line-height:1.6;color:rgba(245,244,241,.82);max-width:34em}

/* ---------------------------------------------------------------------
   4. The motion switch (footer bar)
   Hidden in the markup and unhidden by js/motion.js — with no motion layer
   there is nothing for it to pause.
   --------------------------------------------------------------------- */
/* [hidden] first, and it has to be here: an author `display` outranks the UA
   sheet's [hidden]{display:none}, so without this the button stays visible and
   focusable in exactly the two cases it must not be — under
   prefers-reduced-motion, and when GSAP never arrives and it has no handler. */
.motion-toggle[hidden]{display:none}
.motion-toggle{
  display:inline-flex;align-items:center;gap:8px;
  min-height:32px;padding:4px 12px;border:1px solid var(--line);border-radius:999px;
  font-size:12px;font-weight:600;color:var(--ink-2);
  transition:color .2s var(--ease),border-color .2s var(--ease);
}
.motion-toggle:hover{color:var(--ink);border-color:var(--ink)}
.motion-toggle__dot{width:7px;height:7px;border-radius:50%;flex:none;background:var(--terracotta)}
html.motion-off .motion-toggle__dot{background:none;box-shadow:inset 0 0 0 1px currentColor}
@media (prefers-reduced-motion:no-preference){
  html.motion:not(.motion-off) .motion-toggle__dot{animation:dio-pulse 2.6s var(--ease) infinite}
}
@keyframes dio-pulse{0%,72%,100%{opacity:1}86%{opacity:.28}}

/* ---------------------------------------------------------------------
   5. Reveal
   The hidden half only exists under html.motion. js/motion.js drops the
   data-reveal attribute the moment an element finishes arriving, so nothing
   keeps an inline opacity or a rule that could strand it at zero.
   --------------------------------------------------------------------- */
html.motion [data-reveal]{opacity:0}
/* Tabbing into a card that has not been revealed yet scrolls it into view and
   fires its trigger, but that still leaves ~0.85s with the focus ring sitting on
   nothing. Focus wins over the reveal, always. */
html.motion [data-reveal]:focus-within{opacity:1}

/* The split spans are plain inline text EXCEPT while the word is in flight.
   That matters twice over: an inline-block is its own text run, and VoiceOver
   reads a line of them word-by-word with a pause at every gap — so the mask
   geometry, like the will-change, is held for the ~1s of the animation and then
   handed back. (111 permanently promoted spans is also exactly the case the
   "do not declare will-change on many elements" advice is about.)
   The padding / negative-margin pair gives descenders somewhere to live so
   overflow:hidden does not shave the tail off a "g". */
.wm,.wi{display:inline}
.wm.is-moving{display:inline-block;overflow:hidden;vertical-align:bottom;padding-bottom:.14em;margin-bottom:-.14em}
.wm.is-moving .wi{display:inline-block;will-change:transform}
/* The italic accents are gradient TEXT: transparent glyphs over a background clipped to
   them (css/style.css). A word in flight is its own inline-block layer, and the <em>'s clip
   does not reach into it — so the accent rose as invisible text and appeared all at once at
   the end, the one line on each statement that is meant to land last and best. While it
   is in the air, each word carries the gradient itself; on landing it hands back to the em. */
@supports ((-webkit-background-clip:text) or (background-clip:text)){
  .intro p em .wm.is-moving .wi,
  .studio__text .h-l em .wm.is-moving .wi{background-image:var(--grad-terra);-webkit-background-clip:text;background-clip:text}
  .approach__headline em .wm.is-moving .wi{background-image:var(--grad-terra-light);-webkit-background-clip:text;background-clip:text}
}
/* The offset start state is deliberately NOT set here. A transform in the
   stylesheet is a computed matrix in pixels by the time GSAP reads it, and GSAP
   keeps that as the word's base transform — so the word lands one line-height
   low and stays clipped by its own mask. js/motion.js sets yPercent itself,
   synchronously, in the same task that splits the words. */

/* ---------------------------------------------------------------------
   6. Parallax inside the image slots
   The slot's contents ride a scrubbed --py and sit on a --pz that the hover
   lifts. Doing it through custom properties (rather than GSAP writing
   transform directly) is what lets the scroll parallax and the hover zoom
   share one transform without overwriting each other.
   The 1.08 base scale is the overhang the ±3.5% travel needs; keep them in
   step if either changes.
   --------------------------------------------------------------------- */
html.motion .pcard .ph,html.motion .story .ph,html.motion .studio .ph{
  --pz:1.08;
  transition:--pz 1.1s var(--ease);
}
html.motion .pcard:hover .ph,html.motion .pcard:focus-visible .ph,
html.motion .story:hover .ph,html.motion .story:focus-visible .ph{--pz:1.13}
html.motion .pcard .ph .scene,html.motion .pcard .ph img,
html.motion .story .ph .scene,html.motion .story .ph img,
html.motion .studio .ph .scene,html.motion .studio .ph img{
  transform:translate3d(0,var(--py),0) scale(var(--pz));
  transition:none;               /* the scrub is the animation; a transition would smear it */
}

/* ---------------------------------------------------------------------
   7. Section in view
   js/motion.js marks the nav link for the section you are looking at. It cannot
   be style.css's underline sweep: the nav links wear .glass-pill, and
   hero-glass.css blanks `.nav .glass-pill::after` in BOTH the hero and the
   scrolled state, so that rule could never have drawn a pixel.

   Over the hero, hero-glass.css's own `.glass-pill[aria-current=page]` handles it.
   This is the cream-bar half, and it is deliberately the same "selected is ink"
   language style.css already uses for the filter pills and the EN/RO switch —
   same tokens, same deliberately weak specular, so the cream label keeps 11:1.
   motion.css loads last, which is the only reason this outranks style.css's own
   `.nav.is-page .nav__in .glass-pill`.
   --------------------------------------------------------------------- */
html.motion .nav.is-page .nav__in .glass-pill[aria-current]{
  color:var(--cream);
  background:var(--glass-ink);
  border-color:var(--glass-ink);
  box-shadow:var(--glass-lift-dark);
}
html.motion .nav.is-page .nav__in .glass-pill[aria-current]::before{
  background:
    radial-gradient(60% 80% at 22% 8%,rgba(255,255,255,.12),transparent 62%),
    linear-gradient(to bottom,rgba(255,255,255,.06),transparent 50%,rgba(0,0,0,.1));
}

/* ---------------------------------------------------------------------
   8. Responsive
   --------------------------------------------------------------------- */
@media (max-width:960px){
  .approach__principles{grid-template-columns:1fr 1fr}
}
@media (max-width:640px){
  .approach__principles{grid-template-columns:1fr;gap:22px}
  .approach__headline{max-width:none}
}

/* ---------------------------------------------------------------------
   9. Less motion
   The media query is the belt; js/motion.js never adds html.motion in the
   first place under it, and strips it again when the switch is turned off.
   These rules are the braces — they also cover the frame between first paint
   and the motion layer booting.
   --------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  /* .motion in the selector is load-bearing: html [data-reveal] is (0,1,1) and
     loses to html.motion [data-reveal] at (0,2,1), so without it this block only
     ever "worked" because js/motion.js declines to add the class under reduce —
     which is a different mechanism, not this one. */
  html [data-reveal],html.motion [data-reveal]{opacity:1}
  .progress{display:none}
  .motion-toggle,.marquee__vp,.progress span{transition:none}
  /* and .motion is load-bearing here for the same reason: html .pcard .ph is (0,2,1)
     against html.motion .pcard .ph at (0,3,1), so the plain selectors alone lost every
     time and this block was resting entirely on js/motion.js not adding the class. */
  html .pcard .ph,html .story .ph,html .studio .ph,
  html.motion .pcard .ph,html.motion .story .ph,html.motion .studio .ph{--pz:1;transition:none}
  html .pcard .ph .scene,html .pcard .ph img,
  html .story .ph .scene,html .story .ph img,
  html .studio .ph .scene,html .studio .ph img,
  html.motion .pcard .ph .scene,html.motion .pcard .ph img,
  html.motion .story .ph .scene,html.motion .story .ph img,
  html.motion .studio .ph .scene,html.motion .studio .ph img{transform:none}
}
