/* ==========================================================================
   BON JEU — homepage
   Built to design/comp-home.png. Every position and size below traces to a
   measured value in design/MEASUREMENTS.md, not to an eyeballed estimate.

   The comp is 1536 x 1024. `--u` is one design pixel, so at a 1536-wide
   viewport every calc() below resolves to its measured value exactly, and
   scales proportionally at other widths.
   ========================================================================== */

/* Palette, type stack and reset come from tokens.css. */

:root {
  --u: calc(100vw / 1536);

  --ink: #ffffff;
  --ink-dim: rgba(255, 255, 255, 0.45);

  /* Cap-height offsets. A text box's top sits above its cap line by an amount
     that depends on the font's metrics, so these are calibrated against the
     comp by scripts/verify_build.py rather than derived. */
  --nav-top: 32;
  --rail-l-top: 185;
  --rail-r-top: 484;
  --wm-top: 371;
  --player-top: 747;
  --scroll-top: 922;
  --sound-top: 921;
}

html { background: #000; }

body {
  color: var(--ink);
  background: #000;
}

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: calc(4 * var(--u));
}

/* --------------------------------------------------------------- hero shell */

.hero {
  position: relative;
  width: 100%;
  height: 100vh;
  min-height: 640px;
  overflow: hidden;
  isolation: isolate;
}

/* The stage is the plate's coordinate space. It keeps the image's exact 16:9
   ratio while covering the viewport, so a percentage inside it maps to the
   same point on the photograph at any window shape — which is what lets the
   garment hotspots stay locked to the garments. */
.hero__stage {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: max(100%, calc(100vh * 16 / 9));
  height: max(100%, calc(100vw * 9 / 16));
}

.hero__plates { position: absolute; inset: 0; }

.hero__plate {
  position: absolute; inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transition: opacity 900ms ease;
}
.hero__plate.is-active { opacity: 1; }

/* ------------------------------------------------------- garment focus pull */

.focus {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 550ms cubic-bezier(.4, 0, .2, 1);
}

.focus--jacket { background-image: url('../assets/hero-court-jacket.jpg'); }
.focus--short  { background-image: url('../assets/hero-court-short.jpg'); }

/* Hit zones, in stage percentages measured off the plate. */
.hotspot {
  position: absolute;
  display: block;
  cursor: pointer;
  /* Keeps the zone reachable without painting anything. */
  background: transparent;
}
.hotspot--jacket { left: 62.5%; top: 21%;  width: 16.5%; height: 32%; }
.hotspot--short  { left: 64.5%; top: 50%;  width: 14%;   height: 17%; }

@media (hover: hover) {
  .hero:has(.hotspot--jacket:hover) .focus--jacket,
  .hero:has(.hotspot--jacket:focus-visible) .focus--jacket,
  .hero:has(.hotspot--short:hover) .focus--short,
  .hero:has(.hotspot--short:focus-visible) .focus--short {
    opacity: 1;
  }

  /* The wordmark and the product label occupy the same space, so the hero
     yields to the product rather than stacking two lockups on top of
     each other. */
  .hero:has(.hotspot:hover) .wordmark,
  .hero:has(.hotspot:focus-visible) .wordmark {
    opacity: 0;
  }
}

/* Touch devices get the links without the focus-pull, which has no trigger. */
@media (hover: none) {
  .focus { display: none; }
}

/* Legibility scrim. Off by default: the shipped plate is the comp, which is
   already graded. Enable once real photography is in place. */
.hero[data-scrim='on']::before {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(to bottom, rgba(0,0,0,.55) 0%, rgba(0,0,0,0) 26%),
    linear-gradient(to top,    rgba(0,0,0,.55) 0%, rgba(0,0,0,0) 32%);
}

/* Comp mode (?comp=1): drop the photograph so scripts/verify_build.py measures
   the overlay's own geometry against a black plate. Test fixture only. */
html[data-comp='1'] .hero__plates { display: none; }
html[data-comp='1'] .hero { background: #000; }

/* ---------------------------------------------------------------------- nav */
/* Cap band y 35-46. MENU at x56, BAG right edge x1482, centre group 503-973. */

.nav {
  position: absolute;
  top: calc(var(--nav-top) * var(--u));
  left: 0; right: 0;
  padding: 0 calc(50 * var(--u)) 0 calc(56 * var(--u));
  display: flex;
  align-items: baseline;
  font-size: calc(18.2 * var(--u));
  font-variation-settings: 'wdth' 74;
  font-weight: 500;
  letter-spacing: calc(0.93 * var(--u));
  text-transform: uppercase;
  line-height: 1;
}

.nav__links {
  position: absolute;
  left: calc(503 * var(--u));
  display: flex;
  gap: calc(63.5 * var(--u));
}

.nav__bag  { letter-spacing: calc(1.8 * var(--u)); }

/* The comp sets the bag count's brackets at cap height — they sit inside the
   nav's 35-46 band rather than descending, as Archivo's do by default. */
.nav__paren {
  font-size: 0.75em;
  vertical-align: 0.1em;
}
.nav__bag { margin-left: auto; }

.nav__links a, .nav__bag {
  transition: opacity 200ms ease;
}
.nav__links a:hover, .nav__bag:hover { opacity: 0.62; }

/* --------------------------------------------------------------- side rails */
/* Vertical text runs top-to-bottom; letter-band analysis of the comp confirms
   reading order (8 bands + gap + 5 = "SPORTING" "GOODS"). */

.rail { position: absolute; }

/* Two stacked block spans in vertical-rl become two columns filling
   right-to-left, so line 1 sits outermost — which is how the comp reads.
   Line-height is the column pitch here, not the leading. */
.rail__text {
  writing-mode: vertical-rl;
  font-size: calc(12 * var(--u));
  font-weight: 400;
  text-transform: uppercase;
}

.rail__text span { display: block; }

.rail--left {
  top: calc(var(--rail-l-top) * var(--u));
  left: calc(47.25 * var(--u));
  width: calc(43 * var(--u));
}
.rail--left .rail__text {
  line-height: calc(21.5 * var(--u));
  letter-spacing: calc(3.45 * var(--u));
}
.rail--left .rail__text span:nth-child(1) { letter-spacing: calc(3.30 * var(--u)); }
.rail--left .rail__text span:nth-child(2) { letter-spacing: calc(3.64 * var(--u)); }

.rail--right {
  top: calc(var(--rail-r-top) * var(--u));
  right: calc(45.75 * var(--u));
  width: calc(41 * var(--u));
}
.rail--right .rail__text {
  line-height: calc(20.5 * var(--u));
  letter-spacing: calc(3.13 * var(--u));
}

/* Vertical rule: x65, 1px, y380-463, fading downward. Positioned relative to
   the rail's own top so the two move together. */
.rail__rule {
  position: absolute;
  left: calc(17.75 * var(--u));
  top: calc(195 * var(--u));
  width: 1px;
  height: calc(110 * var(--u));
  background: linear-gradient(to bottom,
    rgba(255,255,255,.95) 0%, rgba(255,255,255,.62) 76%, rgba(255,255,255,0) 100%);
}

/* Slide dots: centre x65, diameter 14, pitch 32.5, first filled. */
.dots[hidden] { display: none; }

.dots {
  position: absolute;
  left: calc(10.75 * var(--u));
  top: calc(310 * var(--u));
  display: flex;
  flex-direction: column;
  gap: calc(18.3 * var(--u));
}

.dot {
  width: calc(14 * var(--u));
  height: calc(14 * var(--u));
  border: 1px solid rgba(255,255,255,.9);
  border-radius: 50%;
  background: transparent;
  padding: 0;
  transition: background 300ms ease;
}
.dot.is-active { background: var(--ink); }

/* ----------------------------------------------------------------- wordmark */
/* Both lines are justified to an identical 415px block (x547-961), so each is
   a flex row with space-between rather than tracked text. That pins the block
   geometry to the comp regardless of the font's own glyph widths. */

.wordmark {
  position: absolute;
  top: 50%;
  transform: translateY(calc(-50% - 19 * var(--u)));
  left: calc(538 * var(--u));
  width: calc(437 * var(--u));
  font-family: var(--font-display);
  /* Anybody at wdth 150 renders BON ~15% wider than the comp's 415px block,
     which would overflow the justified row. wdth 129 puts the glyph sum at
     the comp's 384px, leaving exactly its measured 15px inter-letter gaps. */
  font-variation-settings: 'wdth' 129;
  font-weight: 500;
  font-size: calc(146.2 * var(--u));
  line-height: calc(122 * var(--u));
  letter-spacing: 0;
  pointer-events: none;
  transition: opacity 500ms ease;
}

.wordmark__line {
  display: flex;
  justify-content: space-between;
  width: 100%;
}

.wordmark__line--2 {
  margin-left: calc(2 * var(--u));
  width: calc(435 * var(--u));
}

.wordmark__line i { font-style: normal; }

/* ------------------------------------------------------------------- player */

.player {
  position: absolute;
  bottom: calc(48 * var(--u));
  left: calc(56 * var(--u));
  width: calc(351 * var(--u));
}

.player__eyebrow {
  font-size: calc(11 * var(--u));
  letter-spacing: calc(2.76 * var(--u));
  text-transform: uppercase;
  line-height: 1;
}

.player__title {
  margin-top: calc(21 * var(--u));
  font-size: calc(21 * var(--u));
  font-weight: 400;
  letter-spacing: calc(2.6 * var(--u));
  text-transform: uppercase;
  line-height: 1;
}

.player__sub {
  margin-top: calc(12 * var(--u));
  font-size: calc(14 * var(--u));
  font-weight: 300;
  letter-spacing: calc(2.22 * var(--u));
  text-transform: uppercase;
  line-height: 1;
}

/* Scrub bar: track x56-406 (351 wide), centred on y859. */
.player__scrub { margin-top: calc(28 * var(--u)); }


.scrub {
  position: relative;
  height: calc(12 * var(--u));
  cursor: pointer;
  touch-action: none;
}

.scrub__track, .scrub__fill {
  position: absolute;
  top: 50%;
  left: 0;
  height: 1px;
  transform: translateY(-50%);
}
.scrub__track { width: 100%; background: var(--ink-dim); }
.scrub__fill  { width: 0; background: var(--ink); }

.scrub__knob {
  position: absolute;
  top: 50%;
  left: 0;
  width: calc(10 * var(--u));
  height: calc(10 * var(--u));
  margin-left: calc(-5 * var(--u));
  border-radius: 50%;
  background: var(--ink);
  transform: translateY(-50%);
}

.player__times {
  display: flex;
  justify-content: space-between;
  margin-top: calc(12 * var(--u));
  font-size: calc(11 * var(--u));
  letter-spacing: calc(0.8 * var(--u));
  line-height: 1;
}

/* Controls sit on measured centres (73, 146.5, 229.5, 314.5, 389 from the
   player's left edge at x56), which is not an even distribution. */
.player__controls {
  position: relative;
  height: calc(78 * var(--u));
  margin-top: calc(9 * var(--u));
}

.ctrl {
  position: absolute;
  top: 50%;
  display: grid;
  place-items: center;
  transform: translate(-50%, -50%);
  transition: opacity 200ms ease;
}
.ctrl:hover { opacity: 0.65; }

.ctrl svg {
  display: block;
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  overflow: visible;
}
.ctrl svg .fill { fill: var(--ink); stroke: none; }

#btn-shuffle { left: calc(17 * var(--u));  width: calc(29 * var(--u)); }
#btn-prev    { left: calc(90.5 * var(--u)); width: calc(18 * var(--u)); }
#btn-play    { left: calc(173.5 * var(--u)); width: calc(78 * var(--u)); height: calc(78 * var(--u)); }
#btn-next    { left: calc(258.5 * var(--u)); width: calc(18 * var(--u)); }
#btn-repeat  { left: calc(333 * var(--u)); width: calc(29 * var(--u)); }

#btn-shuffle svg, #btn-repeat svg { width: calc(29 * var(--u)); }
#btn-prev svg, #btn-next svg { width: calc(18 * var(--u)); }

.ctrl--play .ctrl__ring {
  position: absolute;
  inset: 0;
  border: 1px solid rgba(255,255,255,.9);
  border-radius: 50%;
}
.ctrl--play svg { width: calc(18 * var(--u)); position: relative; }

/* Icon swaps with real playback state — never decorative. */
.ctrl--play .icon-play { display: none; }
.ctrl--play[data-state='paused'] .icon-pause { display: none; }
.ctrl--play[data-state='paused'] .icon-play  { display: block; }

/* Shuffle and repeat read at full white in their default state, as the comp
   draws them. Their "on" state is a dot beneath the icon rather than a
   brightness change, which would otherwise be the only cue and is too weak. */
#btn-shuffle::after, #btn-repeat::after {
  content: '';
  position: absolute;
  bottom: calc(-9 * var(--u));
  left: 50%;
  width: calc(3 * var(--u));
  height: calc(3 * var(--u));
  margin-left: calc(-1.5 * var(--u));
  border-radius: 50%;
  background: var(--ink);
  opacity: 0;
  transition: opacity 200ms ease;
}
#btn-shuffle[aria-pressed='true']::after,
#btn-repeat[aria-pressed='true']::after { opacity: 1; }

/* -------------------------------------------------------------- sound meter */
/* SOUND y923-932; ON y938-952; 9 bars, 8px pitch, x1417-1482, baseline y952. */

/* The three parts sit on measured offsets rather than in flow: the comp puts
   ON's baseline and the bar baseline both on y952, which flex alignment can't
   express (a text box's baseline sits above its box bottom by its descender). */
.sound {
  position: absolute;
  bottom: calc(72 * var(--u));
  right: calc(53 * var(--u));
  width: calc(98 * var(--u));
  height: calc(31 * var(--u));
  text-align: left;
  font-size: calc(15.3 * var(--u));
  font-variation-settings: 'wdth' 78;
  letter-spacing: calc(0.75 * var(--u));
  text-transform: uppercase;
  line-height: 1;
}

.sound__row { display: contents; }

.sound__label { position: absolute; left: 0; top: 0; }

.sound__state {
  position: absolute;
  left: 0;
  bottom: calc(-3 * var(--u));
  font-size: calc(13.8 * var(--u));
  letter-spacing: calc(0.5 * var(--u));
  line-height: 1;
}

.eq {
  position: absolute;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: flex-end;
  gap: calc(7.1 * var(--u));
  height: calc(23 * var(--u));
}

.eq span {
  display: block;
  width: 1px;
  background: var(--ink);
  transform-origin: bottom;
}

/* Bars animate only while audio is genuinely playing and unmuted. */
.sound[data-playing='true'] .eq span {
  animation: eq-pulse 900ms ease-in-out infinite alternate;
}

@keyframes eq-pulse {
  from { transform: scaleY(0.45); }
  to   { transform: scaleY(1); }
}

@media (prefers-reduced-motion: reduce) {
  .sound[data-playing='true'] .eq span { animation: none; }
  .hero__plate { transition: none; }
  .focus { transition: opacity 200ms ease; }
}

/* ------------------------------------------------------------------ mobile */
/* The hero composition is a desktop layout. Below 900px it reflows rather
   than scaling down, which would make the rails illegible. */

@media (max-width: 900px) {
  :root { --u: calc(100vw / 480); }

  .hero { min-height: 100vh; }
  .hero::before { opacity: 1; }

  .rail { display: none; }

  .nav {
    top: calc(24 * var(--u));
    padding: 0 calc(20 * var(--u));
    font-size: calc(11 * var(--u));
    letter-spacing: calc(1 * var(--u));
  }
  .nav__links { display: none; }

  .wordmark {
    top: auto;
    bottom: calc(300 * var(--u));
    left: calc(20 * var(--u));
    width: calc(440 * var(--u));
    font-size: calc(160 * var(--u));
    line-height: calc(130 * var(--u));
  }

  .player {
    top: auto;
    bottom: calc(96 * var(--u));
    left: calc(20 * var(--u));
    width: calc(440 * var(--u));
  }


  .sound {
    top: auto;
    bottom: calc(28 * var(--u));
    right: calc(20 * var(--u));
  }
}
