/* ==========================================================================
   AMERICAN BALLET AWARDS — MOTION LAYER
   --------------------------------------------------------------------------
   Everything here is *additive*. base.css alone produces a complete, static,
   readable page; this file plus motion.js layer the choreography on top.

   GATING — read before editing.
   `html.mo`      motion.js is alive. Nothing in this file applies without it,
                  so a no-JS visitor never sees a permanently-hidden element.
   `html.mo-off`  the visitor asked for reduced motion. motion.js sets this and
                  registers no scroll/pointer effects at all; the rules below
                  then hand every element straight to its finished state.

   PERFORMANCE CONTRACT
   · Only `transform` and `opacity` are animated. No layout, no paint-heavy
     filters inside a scroll loop.
   · `will-change` is applied by JS only while an element is actually in play
     and removed afterwards — a permanently promoted layer on twenty cards
     costs more than it saves.
   · Every custom property written per-frame (`--rx`, `--mx`, `--p`…) is a
     bare number or a length, so the compositor can consume it directly.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1 · (was SCROLL PROGRESS) — the gold reading hairline across the foot of the
   sticky nav was removed on request. Its markup (`.nav__prog`), its rules here
   and the `progress` branch of motion.js::scrollDriven all went with it; there
   is nothing left to re-enable.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   2 · SPLIT-TEXT HEADLINES
   motion.js rewrites the text nodes of `[data-split]` into per-word spans. The
   outer span is the mask, the inner one is what moves — each word hinges up
   from below its own baseline, which is the ballet equivalent of a curtain
   lift rather than a generic fade.

   The overflow trick: a plain `overflow:hidden` box clips descenders (the tail
   of a "g" or "y") because the line box is taller than the glyph box. So the
   mask is padded by .16em and pulled back by an equal negative margin — the
   clip edge sits below the descender while the text still sits on its
   original baseline.
   -------------------------------------------------------------------------- */
.sp-w{
  display:inline-block;vertical-align:top;
  padding-block:.16em;margin-block:-.16em;
  overflow:hidden;
  perspective:520px;                 /* the hinge needs depth on the PARENT */
}
.sp-i{display:inline-block;transform-origin:50% 0%;}
html.mo [data-split] .sp-i{
  opacity:0;
  transform:translateY(112%) rotateX(-78deg);
  transition:
    opacity .82s var(--ease-soft) calc(var(--i,0) * 42ms),
    transform 1.02s var(--ease) calc(var(--i,0) * 42ms);
}
html.mo [data-split].is-lit .sp-i{opacity:1;transform:none;}
html.mo-off [data-split] .sp-i{opacity:1 !important;transform:none !important;transition:none !important;}

/* --------------------------------------------------------------------------
   3 · REVEALS, IN THREE DIMENSIONS
   base.css fades `.rv` up 22px. With JS present that becomes a shallow hinge
   in Z: the block tips back a few degrees and settles. Six degrees is the
   whole trick — any more and cream text on velvet starts to shimmer as the
   subpixel grid rotates under it.
   -------------------------------------------------------------------------- */
html.mo .rv{
  transform:perspective(1100px) translateY(34px) rotateX(5.5deg) scale(.986);
  transition:
    opacity 1s var(--ease-soft) var(--d,0s),
    transform 1.15s var(--ease) var(--d,0s);
}
html.mo .rv.is-in{transform:none;}
html.mo-off .rv{opacity:1 !important;transform:none !important;transition:none !important;}

/* Grid children stagger. motion.js writes `--d` per child on any [data-stag]
   container, so a row of cards deals itself out left-to-right instead of
   arriving as one slab. */
[data-stag]{transform-style:preserve-3d;}

/* Hairline rules draw themselves in rather than fading.
   Each rule is observed individually. Keying this off an ancestor's `.is-in`
   instead would collapse every rule that does not happen to sit inside a
   revealed block — and leave it collapsed permanently. */
html.mo .rule{transform:scaleX(0);transform-origin:left;
  transition:transform 1.1s var(--ease) .12s;}
html.mo .rule.is-in{transform:scaleX(1);}
html.mo-off .rule{transform:none !important;}

/* --------------------------------------------------------------------------
   4 · PARALLAX MEDIA
   JS writes `--py` (a px offset) and nothing else. The scale is baked in here
   so the photograph is always over-sized enough that the offset can never
   expose an edge: at the maximum travel of ±7% of the frame height, 1.16 is
   the smallest scale with margin to spare.
   -------------------------------------------------------------------------- */
html.mo [data-para]{
  transform:translate3d(0,var(--py,0px),0) scale(var(--pscale,1.16));
  will-change:transform;
}
html.mo-off [data-para]{transform:none !important;}

/* .frame draws its gold keyline with a ::before offset OUTSIDE the box, so the
   frame itself can never clip. The photograph gets its own window instead. */
.frame__win{overflow:hidden;border-radius:var(--r-lg);line-height:0;}
.frame__win img{border-radius:0;}

/* --------------------------------------------------------------------------
   5 · POINTER TILT
   Cards lean toward the cursor. `--rx`/`--ry` are lerped in JS (a raw
   pointermove value snaps and reads as cheap); `--mx`/`--my` place the sheen.
   Fine pointers only — a tilt that can never fire is dead weight on a phone.
   -------------------------------------------------------------------------- */
html.mo [data-tilt]{
  position:relative;                 /* the sheen ::after is positioned to this */
  transform:
    perspective(var(--tp,1000px))
    rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))
    translate3d(0,var(--ty,0px),0);
  transform-style:preserve-3d;
  transition:box-shadow .45s var(--ease);
}
/* The sheen: a soft gold wash that tracks the cursor across the card face.
   `screen` keeps it inside the palette — it lightens what is already there
   rather than introducing a new hue. */
html.mo [data-tilt]::after{
  content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  border-radius:inherit;
  background:radial-gradient(62% 62% at var(--mx,50%) var(--my,50%),
    rgba(216,190,134,.20) 0%,rgba(185,154,91,.07) 42%,rgba(185,154,91,0) 72%);
  opacity:0;transition:opacity .4s var(--ease);
  mix-blend-mode:screen;
}
html.mo [data-tilt].is-hot::after{opacity:1;}
html.mo [data-tilt].is-hot{box-shadow:0 26px 60px -28px rgba(11,7,9,.9);}
html.mo-off [data-tilt]{transform:none !important;}
html.mo-off [data-tilt]::after{display:none;}

/* The ordinal watermark on category cards already owns ::after, and the tilt
   sheen would overwrite it. Those cards get the lean without the wash. */
html.mo .card--ord[data-tilt]::after{
  background:none;mix-blend-mode:normal;opacity:1;
}

/* --------------------------------------------------------------------------
   6 · MAGNETIC BUTTONS
   The primary call to action drifts a few pixels toward the cursor as it
   approaches, then springs back. Capped at 7px: enough to feel alive, small
   enough that the click target never moves out from under the pointer.
   -------------------------------------------------------------------------- */
html.mo [data-mag]{
  transform:translate3d(var(--gx,0px),var(--gy,0px),0);
  transition:transform .5s var(--ease);
}
html.mo [data-mag].is-pull{transition:transform .12s linear;}
html.mo-off [data-mag]{transform:none !important;}

/* --------------------------------------------------------------------------
   7 · HERO
   -------------------------------------------------------------------------- */
/* NO pointer-tracked light. A warm radial follow-spot used to track the
   pointer across the hero photograph; the client circled it and asked for it
   to go. With a custom cursor on the page it read as a glow attached to the
   cursor rather than as a stage light, which is a fair reading. The cursor is
   now the dot and the trailing ring and nothing else. */

/* The whole hero recedes and dims as it leaves — the page reads as the stage
   receding rather than a block scrolling off the top. */
/* Only .hero__body. .hero__foot carries `.hrv`, whose entrance animation uses
   fill-mode `forwards`; an animated value outranks a normal declaration for as
   long as the animation exists, so anything written here would never apply. */
html.mo .hero__body{
  transform:translate3d(0,calc(var(--hy,0) * -1px),0);
  opacity:calc(1 - var(--hy,0) / 160);
}
html.mo-off .hero__body{transform:none !important;opacity:1 !important;}

/* --------------------------------------------------------------------------
   7b . THE HERO FACE / NAV BUTTON COLLISION
   --------------------------------------------------------------------------
   The dancer's head sits only 8% from the top of the hero photograph and the
   nav occupies the top ~7.5vh, so the two collided: her face was behind the
   "Register a dancer" button.

   `object-position` cannot fix it. At hero proportions on a wide screen the
   photo is cover-cropped VERTICALLY, so the x value has no effect at all (the
   same trap as the band photograph, decisions log D14), and there is under 60px
   of vertical slack to move within.

   The parallax scale is the lever that does work. Anchoring the zoom to the
   TOP-RIGHT corner rather than the centre pushes the subject DOWN and LEFT
   instead of pulling her up past the nav, and because the top and right edges
   stay flush no edge is ever exposed. She now sits around 67% across and clears
   the nav by 25-30px at every desktop width.

   Desktop only: below 1024px the hero is photograph-on-top with a burger nav,
   and there is no button to collide with.
   -------------------------------------------------------------------------- */
@media (min-width:1025px){
  html.mo .hero__media img{
    --pscale:1.24;
    transform-origin:100% 0%;
  }
}

/* --------------------------------------------------------------------------
   8 · THE PINNED TOUR SLIDER, IN DEPTH
   base.css + app.js already translate the track horizontally from scroll
   position. This adds the third dimension: each card is rotated and pushed
   back in proportion to how far its centre sits from the centre of the
   viewport, so the row reads as a carousel curving away rather than a strip
   sliding past. JS writes `--cd` per card: -1 hard left, 0 centred, +1 right.
   -------------------------------------------------------------------------- */
html.mo .pin__viewport{perspective:1500px;perspective-origin:50% 50%;}
html.mo .stopc:hover,html.mo .stopc:focus-within{--lift:-7px;}
html.mo .pin__track{transform-style:preserve-3d;}
/* `--lift` folds the card's own hover rise into this transform. Leaving the
   page's `.stopc:hover{transform:...}` rule to fight it would simply mean one
   of the two never applies. */
html.mo .pin__track > *{
  /* (1 - cdz) * 132px, NOT cdz * -132px. Both give the centre card the nearest
     depth, but only this one keeps every card at Z >= 0. Negative Z put the
     outer cards BEHIND their own parent's plane, and inside `preserve-3d` a
     child behind the parent is occluded by it for HIT-TESTING as well as for
     painting: `.pin__track` swallowed every pointer event and not one of the
     `.stopc:hover` rules could fire. Do not flip the sign back. */
  transform:
    rotateY(calc(var(--cd,0) * -16deg))
    translateZ(calc((1 - var(--cdz,0)) * 132px))
    translateY(var(--lift,0px))
    scale(calc(1 - var(--cdz,0) * .07));
  /* NO opacity fade. It was `1 - cdz * .42`, which let the #7A1E22 ground show
     through a solid-black card and painted #270e10-#2a1112 — a red variation,
     which is the one thing the palette is not allowed to produce. Rotation,
     scale and Z already carry the depth on their own. */
  transition:box-shadow .3s var(--ease);
  backface-visibility:hidden;
}
/* Touch and reduced-motion get the flat swipeable scroller. Depth on a
   horizontally-scrolled strip on a phone is nausea, not luxury. */
@media (max-width:1024px),(pointer:coarse),(prefers-reduced-motion:reduce){
  html.mo .pin__viewport{perspective:none;}
  html.mo .pin__track > *{transform:none !important;opacity:1 !important;}
}

/* --------------------------------------------------------------------------
   9 · THE PRODUCT SHOT
   The rendered booklet/card/medal is a photograph of metal and foil, so the
   honest way to animate it is a light moving across it. A single specular
   sweep fires when it enters view, and the whole figure counter-rotates a
   little against the scroll.
   -------------------------------------------------------------------------- */
/* NOTE: the rotation goes on the <img>, not on .propshot. The figure already
   carries `.rv`, which owns `transform` for the reveal, and the page CSS adds a
   hover transform on top of that — a third rule on the same property would win
   and silently kill the reveal. The image inside owns no transform at all. */
html.mo .propshot img{
  transform:perspective(1400px) rotateX(calc(var(--pr,0) * 1deg)) translate3d(0,var(--pty,0px),0);
}
/* NO specular sweep across the product shot. A travelling highlight used to
   run across it; the client asked for it to go. On a photograph of foil and
   struck metal a moving glass glare reads as a screen effect laid OVER the
   object rather than as light falling on it, and the render already carries
   its own baked specular. The figure keeps only the scroll counter-rotation. */
html.mo-off .propshot img{transform:none !important;}

/* --------------------------------------------------------------------------
   10 · SECTION-EDGE GOLD SEAM
   Each major section draws a gold hairline across its top edge as it arrives.
   It is the cheapest possible way to give a long page a sense of chapters.
   -------------------------------------------------------------------------- */
html.mo .mo-seam{
  position:absolute;top:0;left:0;right:0;height:1px;z-index:4;
  background:linear-gradient(90deg,
    rgba(185,154,91,0) 0%,rgba(185,154,91,.55) 22%,
    rgba(216,190,134,.8) 50%,rgba(185,154,91,.55) 78%,rgba(185,154,91,0) 100%);
  transform:scaleX(0);transform-origin:center;
  transition:transform 1.5s var(--ease);
  pointer-events:none;
}
html.mo .mo-seam.is-in{transform:scaleX(1);}
html.mo-off .mo-seam{display:none;}

/* --------------------------------------------------------------------------
   11 · CURTAIN
   base.css parts the halves. With motion.js the monogram now lifts away as it
   goes, and the page behind it eases up into place — so the load reads as one
   continuous move instead of two unrelated ones.
   -------------------------------------------------------------------------- */
html.mo .curtain__mark{transition:opacity .34s ease,transform .7s var(--ease);}
html.mo .curtain.is-open .curtain__mark{transform:translateY(-26px) scale(.9);}
html.mo .curtain.is-open ~ * .hero__media{animation:heroSettle 2.2s var(--ease) both;}
@keyframes heroSettle{from{transform:scale(1.09);}to{transform:scale(1);}}
html.mo-off .curtain.is-open ~ * .hero__media{animation:none;}

/* --------------------------------------------------------------------------
   12 · SMOOTH SCROLL
   motion.js drives the scroll position itself on fine pointers, so the
   browser's own `scroll-behavior:smooth` must get out of the way or the two
   ease against each other and the page feels rubbery.
   -------------------------------------------------------------------------- */
html.mo-smooth{scroll-behavior:auto;}
html.mo-smooth body{overscroll-behavior-y:none;}

/* --------------------------------------------------------------------------
   13 · REDUCED MOTION — the final word
   base.css already flattens durations globally. This makes sure nothing in
   THIS file can leave an element hidden, transformed or mid-animation.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  .sp-i,[data-split] .sp-i{opacity:1 !important;transform:none !important;}
  [data-para],[data-tilt],[data-mag],.propshot img,.pin__track > *{
    transform:none !important;opacity:1 !important;
  }
  .mo-seam,.rule{transform:scaleX(1) !important;}
  .hero__body{transform:none !important;opacity:1 !important;}
}


/* --------------------------------------------------------------------------
   14 . THE 3D GALLERY PLANE  (#stage)
   --------------------------------------------------------------------------
   Three photographs on one shared plane that rotates out of the page as the
   section arrives and settles flat when it reaches the centre of the viewport.
   `--gz` runs +1 (section still below the fold, plane tipped away) through 0
   (flat, dead centre) to -1 (leaving above, tipped the other way), so the
   movement reverses exactly on the way back up.

   The perspective is on the STAGE and the rotation on the GRID inside it. The
   figures are children of that rotated plane and carry only their own pointer
   tilt, so no two rules ever write the same property. `preserve-3d` on the grid
   is what puts the figures INTO the plane's space rather than flattening them
   into it - without it the whole thing collapses into a 2D skew.
   -------------------------------------------------------------------------- */
.gal__stage{perspective:1500px;perspective-origin:50% 42%;}
html.mo .gal__grid{
  transform-style:preserve-3d;
  transform:rotateX(calc(var(--gz,0) * 7deg)) translateZ(calc(var(--gzz,0) * -80px));
  will-change:transform;
}
html.mo .gal__fig{backface-visibility:hidden;}
/* TRAP: the lifted figures used to carry translateZ(56px) as well. Inside the
   stage's `perspective:1500px` a positive Z scales an element UP — ~4% here —
   and pushes it away from the perspective origin, so the two lifted figures
   rendered wider and taller than the two beside them, ate the grid gaps and
   dropped their captions off the shared baseline. The row must stay on ONE
   plane: the composition comes from the Y offset alone. */
html.mo .gal__fig--lift{
  transform:translateY(calc(var(--gal-lift) * -1));
}

/* These figures sit INSIDE a preserve-3d plane, which imposes two rules that
   apply nowhere else a tilt is used on this page:

   1. NO nested `perspective()`. The stage above already supplies it, and a
      perspective inside a preserve-3d parent is exactly where browsers' 3D
      hit-testing stops agreeing with their painting.
   2. No part of a figure may rotate BEHIND the parent's plane. A child behind
      its preserve-3d parent is occluded by it for HIT-TESTING as well as for
      painting, and the parent then swallows the pointer. That killed :hover on
      these cards outright — the pointer entered, then fired `pointerleave`
      20px later without ever leaving the card. A 333px card at 6deg swings
      about ±17px in Z, so a 26px base push clears it with margin.
      (Identical failure to the tour plaques — see the .pin__track note above.)
   Verified by logging the pointer event sequence, not by reasoning about it. */
html.mo .gal__fig[data-tilt]{
  transform:
    translateZ(26px)
    rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))
    translate3d(0,var(--ty,0px),0);
}
html.mo .gal__fig--lift[data-tilt]{
  transform:
    translateY(calc(var(--gal-lift) * -1))
    translateZ(26px)
    rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))
    translate3d(0,var(--ty,0px),0);
}
@media (max-width:900px),(pointer:coarse),(prefers-reduced-motion:reduce){
  .gal__stage{perspective:none;}
  html.mo .gal__grid{transform:none !important;}
  html.mo .gal__fig--lift,
  html.mo .gal__fig[data-tilt],
  html.mo .gal__fig--lift[data-tilt]{transform:none !important;}
}
html.mo-off .gal__grid{transform:none !important;}


/* --------------------------------------------------------------------------
   15 . THE CURSOR
   --------------------------------------------------------------------------
   A gold dot that tracks the pointer exactly, and a ring that trails it. The
   lag between the two IS the effect: two elements moving at different rates
   read as weight and momentum, where a single ring locked to the pointer just
   reads as a re-skinned arrow.

   Over a link or button the ring opens out and the dot collapses into it, so
   the cursor tells you a thing is clickable before you have to guess. Over the
   pinned tour slider it shows a horizontal cue, because that is the one place
   on the page where the scroll does something sideways.

   `cursor:none` is applied by JS (`html.mo-cur`) and ONLY after the element is
   in the document, so a failure part way through can never leave a page with
   no pointer at all. Fine pointers only; text fields keep the real caret.
   -------------------------------------------------------------------------- */
html.mo-cur,html.mo-cur *{cursor:none !important;}
html.mo-cur input,html.mo-cur textarea,html.mo-cur select,
html.mo-cur [contenteditable]{cursor:auto !important;}

.cur{
  position:fixed;left:0;top:0;z-index:200;pointer-events:none;
  opacity:0;transition:opacity .3s var(--ease);
  mix-blend-mode:screen;                 /* keeps it inside the palette */
}
.cur.is-live{opacity:1;}

.cur__dot,.cur__ring{
  position:absolute;left:0;top:0;border-radius:50%;
  transform:translate3d(var(--x,-100px),var(--y,-100px),0) translate(-50%,-50%)
            scale(var(--s,1));
}
.cur__dot{
  width:7px;height:7px;background:var(--gold-lt);
  transition:width .3s var(--ease),height .3s var(--ease),opacity .3s var(--ease);
}
.cur__ring{
  width:34px;height:34px;
  border:1px solid rgba(216,190,134,.7);
  transition:width .38s var(--ease),height .38s var(--ease),
             border-color .38s var(--ease),background-color .38s var(--ease);
}
/* over something clickable: the ring opens, the dot gets out of the way */
.cur.is-over .cur__ring{
  width:58px;height:58px;
  border-color:var(--gold-lt);
  background:rgba(185,154,91,.10);
}
.cur.is-over .cur__dot{width:3px;height:3px;opacity:.5;}
/* pressed */
.cur.is-down .cur__ring{width:26px;height:26px;background:rgba(185,154,91,.2);}

/* the horizontal cue over the pinned slider */
.cur__hint{
  position:absolute;left:0;top:0;
  transform:translate3d(var(--x,-100px),var(--y,-100px),0) translate(-50%,-50%);
  width:58px;height:58px;display:grid;place-items:center;
  opacity:0;transition:opacity .3s var(--ease);
}
.cur__hint svg{width:22px;height:22px;stroke:var(--gold-lt);fill:none;stroke-width:1.3;}
.cur.is-drag .cur__hint{opacity:1;}
.cur.is-drag .cur__ring{width:58px;height:58px;border-color:rgba(216,190,134,.45);}
.cur.is-drag .cur__dot{opacity:0;}

@media (hover:none),(pointer:coarse),(prefers-reduced-motion:reduce){
  .cur{display:none !important;}
  html.mo-cur,html.mo-cur *{cursor:auto !important;}
}


/* --------------------------------------------------------------------------
   16 . THE PHOTOGRAPHS, ON HOVER
   --------------------------------------------------------------------------
   A slow zoom and a small lift in brightness when the container is hovered.
   Every one of these images sits in a wrapper with `overflow:hidden`, so the
   zoom is clipped by the frame and nothing spills.

   `--hz` is a registered custom property so it can be transitioned as a
   NUMBER. The framed images also carry `data-para`, and its parallax rewrites
   the transform every frame — a `transition:transform` there would make the
   parallax visibly lag the scroll. Animating the property instead lets the
   transform update instantly while the zoom still eases.

   Not applied to the product shot: its ground is colour-locked to the section,
   and scaling it would drag the locked edge into view. It keeps its own lift.
   -------------------------------------------------------------------------- */
@property --hz{syntax:'<number>';inherits:false;initial-value:1;}

html.mo .frame__win img,
html.mo .div__pic img,
html.mo .gal__fig img{
  transition:--hz .85s var(--ease),filter .85s var(--ease);
  will-change:transform;
}
/* These two own no other transform, so they can also transition `transform`
   directly — which is the fallback anywhere @property is unavailable. */
html.mo .div__pic img,
html.mo .gal__fig img{
  transform:scale(var(--hz,1));
  transition:transform .85s var(--ease),--hz .85s var(--ease),filter .85s var(--ease);
}
/* The parallax transform gains the zoom as a second scale factor rather than a
   competing rule — two rules writing `transform` means one never happens. */
html.mo [data-para]{
  transform:translate3d(0,var(--py,0px),0) scale(calc(var(--pscale,1.16) * var(--hz,1)));
}

html.mo .frame:hover .frame__win img,
html.mo .frame:focus-within .frame__win img,
html.mo .div:hover .div__pic img,
html.mo .div:focus-within .div__pic img,
html.mo .gal__fig:hover img,
html.mo .gal__fig:focus-within img{
  --hz:1.055;
  filter:saturate(1.05) contrast(1.04) brightness(1.07);
}

@media (prefers-reduced-motion:reduce){
  .frame__win img,.div__pic img,.gal__fig img{
    --hz:1 !important;filter:none !important;transition:none !important;
  }
}
