/* SportWear — alana.minima.work/sportwear
   Every rule here answers to :SportWear/WEB.md. The four brand values are
   the only brand colour; the scene colours below are imagery, which is
   where WEB.md puts the cool end of the moodboard the palette cannot
   express. No moodboard image is used: the plates are unlicensed stock,
   so the set vocabulary is rendered instead. */

:root {
  /* BRAND.md — measured. Nothing else is brand colour. */
  --black: #000000;
  --plum:  #493361;
  --rose:  #BC96A2;
  --white: #FFFFFF;

  /* WEB.md — rose corrected until it can carry text: 8.38:1 on the black
     ground, where raw rose reads 2.62:1 against white and cannot. */
  --rose-text: #C9A3AF;

  --measure: 34ch;
  --radius: 14px;
  --pad: clamp(20px, 5vw, 64px);
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--black);
  color: var(--white);
  font-family: 'Outfit', system-ui, sans-serif;
  font-weight: 300;
  font-size: clamp(15px, 0.9rem + 0.25vw, 18px);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }

:focus-visible {
  outline: 3px solid var(--rose-text);
  outline-offset: 4px;
}

/* The poster register: Outfit 500, uppercase, tight. The second family
   stays banned — display must be unmistakably not the Uniwars logotype. */
.display {
  margin: 0;
  font-size: clamp(34px, 2rem + 3.4vw, 72px);
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  text-wrap: balance;
}

.display--section {
  font-size: clamp(26px, 1.4rem + 1.8vw, 44px);
}

/* The eyebrow borrows the lockup's own tracking — 0.1em of the tagline —
   which is the only tracking value the identity actually states. */
.eyebrow {
  margin: 0;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

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

/* The hero is the identity file's cover on screen — node 3008:103, 1920×960,
   measured. Its third pass removed the invented display block that competed
   with the real mark, leaving three elements, and these are their fractions
   of that frame: mark ink centred at 47.9% of the height and 38.6% of the
   width, Aurora band from 69.2% for 16.7% of the height, meta strip on a
   6.67% side margin with its baseline 8.5% off the bottom. */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100dvh;          /* dvh: the iOS address bar counts into vh */
  background: var(--black);
  overflow: hidden;
}

/* The lockup is artwork, never live text — Uniwars is commercial and
   unlicensed. BRAND.md's placement rule: centered horizontally. On the
   cover it is the only thing in the upper two thirds. */
/* Centred on `transform`, not on `translate`: the intro animation owns the
   `translate` property, and the two compose instead of overwriting. The
   extra -6.2% is the artwork's own padding — its ink sits that far below
   the middle of the PNG, so centring the file leaves the mark low. */
.hero__mark {
  position: absolute;
  left: 50%;
  top: 47.9%;
  transform: translate(-50%, -56.2%);
  margin: 0;
  width: clamp(260px, 46vw, 720px);
}

.hero__mark img { width: 100%; }

/* the meta strip: brand, address, state — the cover's own three labels */
.hero__meta {
  position: absolute;
  left: 6.67%;
  right: 6.67%;
  bottom: 8.5%;
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: 16px;
  /* raw Dusty Rose on Blackout is 8.02:1 — the one text role WEB.md gives
     the rose, and the value the cover sets these in */
  color: var(--rose);
  /* measured off the cover: cap height 1.14% of the frame, and the string
     runs 32.8 caps wide — which is where the 0.34em comes from */
  font-size: clamp(10px, 0.6vw + 5.2px, 15px);
  letter-spacing: 0.34em;
  text-transform: uppercase;
  line-height: 1;
}

/* three labels across a phone collide; the strip stacks and centres */
@media (max-width: 640px) {
  .hero__meta {
    flex-direction: column;
    align-items: center;
    gap: 7px;
  }
}

/* ------------------------------------------------------- rendered stage */

/* The set vocabulary that repeats across all 67 moodboard images:
   cylindrical podium, arch, column, sphere, bubble, cloud, reflection,
   infinite backdrop. Rendered, so nothing unlicensed ships. */

.stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background:
    radial-gradient(100% 70% at 50% 108%, var(--lit, #6E4F5C) 0%, transparent 58%),
    linear-gradient(180deg, var(--c1, #17121a) 0%, var(--c2, #000) 76%);
}

/* Everything in the set sits behind air. The moodboard's rooms have no
   hard edges anywhere, so distance is carried by blur and opacity rather
   than by perspective. */

/* the infinite backdrop's horizon: no edge, just where the light lands */
.stage__horizon {
  position: absolute;
  left: 50%; bottom: 12%;
  width: 96%; height: 42%;
  translate: -50% 0;
  background: radial-gradient(50% 50% at 50% 60%, var(--lit, #6E4F5C), transparent 68%);
  opacity: 0.42;
  filter: blur(40px);
}

.stage__podium {
  position: absolute;
  left: 50%; bottom: 15%;
  width: 20%; aspect-ratio: 1.9 / 1;
  translate: -50% 0;
  background: linear-gradient(180deg, var(--pod-top, #F6F1F4) 0%, var(--pod-bot, #B6A7B0) 100%);
  /* one radius, split: half the width horizontally against a shallow
     vertical — an ellipse capping a straight-sided cylinder */
  border-radius: 50% / 17%;
  box-shadow: 0 18px 40px rgba(0, 0, 0, .35);
}

/* the reflection: the podium again, softened and cut short */
.stage__podium::after {
  content: "";
  position: absolute;
  left: 6%; right: 6%; top: 96%;
  height: 70%;
  background: linear-gradient(180deg, var(--pod-bot, #B6A7B0), transparent 70%);
  border-radius: 50% 50% 40% 40% / 40% 40% 60% 60%;
  opacity: 0.22;
  filter: blur(7px);
}

/* the arch reads as architecture only when it is far away and unlit */
.stage__arch {
  position: absolute;
  border-radius: 50% 50% 0 0 / 30% 30% 0 0;
  background: linear-gradient(180deg, var(--arch-1, #E9DBE3), var(--arch-2, #A08A97));
  opacity: 0.26;
  filter: blur(3px);
}

.stage__sphere {
  position: absolute;
  aspect-ratio: 1;
  border-radius: 50%;
  background:
    radial-gradient(58% 58% at 34% 28%, #fff 0%, transparent 56%),
    radial-gradient(100% 100% at 50% 50%, var(--sph-1, #D7B9C4), var(--sph-2, #6E5460));
  box-shadow: 0 16px 34px rgba(0, 0, 0, .4);
}

.stage__bubble {
  position: absolute;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(58% 58% at 34% 30%, rgba(255,255,255,.72), rgba(255,255,255,.06) 60%);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.28);
}

.stage__cloud {
  position: absolute;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(255,255,255,.55), transparent 68%);
  filter: blur(40px);
}

/* ---------------------------------------------------------------- aurora */

/* The Aurora — the brand's chromatic progression as one field. It is the
   `Aurora boreal` entry from the identity's own element list, and the only
   place the moodboard's cool end is allowed to live. Imagery: no text ever
   sits on it. */
/* The band is sized to the cover's rendered light, not to its node box:
   the node is 16.7% of the frame tall, but what it paints is a bell
   centred at 78% with a half-height width of 10.9% and a total bloom of
   20.8%. Height plus blur reproduce that curve. */
.hero__aurora {
  position: absolute;
  left: 0;
  right: 0;
  top: 72.5%;
  height: 10.9%;
}

/* The field is exactly the band's box, so the blur runs off its own four
   edges: it blooms into the black above and below, and the ends taper
   instead of butting into the viewport. Measured on the cover, that taper
   is 55% of the interior luminance at the frame edge and full by 6% in. */
.aurora__field {
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    #BC96A2 0%,      /* rose    — group 1 */
    #8B6FB0 24%,     /* purple  — group 8 */
    #C46FA8 46%,     /* magenta — group 3 */
    #D9CFE6 70%,     /* pastel  — group 4 */
    #7FD3E0 100%     /* cyan    — group 5 */
  );
  filter: blur(clamp(20px, 3.3vh, 40px));
  opacity: 1;
}

/* On a phone the meta strip stacks, and a 44px stack where the cover had a
   20px line walks the first label into the band's tail — measured 53/255
   of lit ground under it against 3/255 on the desktop layout, and no text
   sits on the Aurora. The band moves up by that difference. This override
   has to live after the rule it overrides: same specificity, so the media
   query does not win on its own — it lost silently once already. */
@media (max-width: 640px) {
  .hero__aurora { top: 69%; }
}

/* ----------------------------------------------------------- statement */

/* Mono discipline: one ground, one ink. Dusty Rose under Blackout —
   8.02:1, the one text-bearing role WEB.md gives the rose. */
.statement {
  padding: clamp(80px, 16vh, 180px) var(--pad);
  display: grid;
  justify-items: center;
  gap: 24px;
  text-align: center;
  background: var(--rose);
  color: var(--black);
}

.statement .display { max-width: 16ch; }

/* :not(.eyebrow) — a type-and-class selector outranks the plain .eyebrow
   class, so without this the eyebrows inherit the display size */
.statement p:not(.eyebrow) {
  /* min(): a ch-based measure can exceed a 390px phone on its own */
  max-width: min(var(--measure), 100%);
  margin: 0;
  font-size: clamp(17px, 1rem + 0.4vw, 21px);
  font-weight: 300;
  line-height: 1.5;
  text-wrap: pretty;
}

/* --------------------------------------------------------------- world */

/* Plum ground, white text: 10.88:1. One of the two grounds WEB.md clears
   for reading text. */
.world {
  padding: clamp(64px, 12vh, 120px) 0;
  background: var(--plum);
}

.world__head {
  padding: 0 var(--pad);
  display: grid;
  gap: 12px;
  max-width: min(62ch, 100%);
}

/* raw rose reads 4.16 on plum and this label is 11px; the corrected rose
   reads 4.84. WEB.md: rose grounds text, it does not write it. */
.world__head .eyebrow { color: var(--rose-text); }

.world__head p:not(.eyebrow) {
  margin: 0;
  max-width: min(46ch, 100%);
  color: rgba(255, 255, 255, 0.82);
  text-wrap: pretty;
}

/* The moodboard is ordered as a chromatic progression and WEB.md makes
   that order the page's own: warm at the start, cool at the end. */
.strip {
  margin-top: clamp(28px, 5vh, 48px);
  padding: 4px var(--pad) 20px;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: min(74vw, 300px);
  gap: 16px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* the snapport has to start after the padding, or a mandatory snap
     scrolls the first card flush to the viewport edge on load */
  scroll-padding-inline: var(--pad);
  overscroll-behavior-x: contain;
}

.strip:focus-visible { outline-offset: -3px; }

/* Five cards fit no viewport, so how much of the row is left is state rather
   than decoration — and it is the part the hidden scrollbar took away. Bound
   to scroll position, never played: a scrubbed thing must not lag the finger,
   so it carries no transition. */
.strip__rail {
  /* Invisible until the handler that fills it is alive: a full bar on a page
     whose scroll nobody is tracking would report a row already read. It keeps
     its box either way — `display: none` would shift the section by 8px the
     moment the script ran, which is layout shift bought for nothing. */
  visibility: hidden;
  margin: 6px var(--pad) 0;
  height: 2px;
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.14);
  overflow: hidden;
}

.strip__rail.is-live { visibility: visible; }

.strip__rail span {
  display: block;
  height: 100%;
  /* both numbers come from the scroll handler: --thumb is the visible
     fraction of the row, --x how far that thumb has travelled in its own
     widths. Percentage translation, so the track's width never enters CSS. */
  width: calc(var(--thumb, 1) * 100%);
  background: var(--rose-text);
  transform: translateX(calc(var(--x, 0) * 100%));
}

.scene { scroll-snap-align: start; }

/* Interior parallax. The set pieces sit at different depths, so they answer
   the strip's scroll at different rates — the cloud drifts with the room, the
   podium runs against it. That difference is what says these are rooms and
   not pictures. `--px` is written per scene by the same scroll handler and
   runs -1.5 to 1.5 across the strip's own viewport. */
.scene .stage__cloud  { transform: translateX(calc(var(--px, 0) *   6px)); }
.scene .stage__arch   { transform: translateX(calc(var(--px, 0) *   4px)); }
.scene .stage__sphere { transform: translateX(calc(var(--px, 0) * -10px)); }
.scene .stage__podium { transform: translateX(calc(var(--px, 0) * -14px)); }

.scene__frame {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius);
  overflow: hidden;
}

/* Nested corners are concentric: the label sits outside the frame, so the
   frame keeps the full radius and nothing inside repeats it. */
.scene__label {
  margin: 12px 2px 0;
  color: rgba(255, 255, 255, 0.72);
  font-size: 13px;
  text-wrap: pretty;
  transition: color 300ms ease;
}

/* The pointer lights the room rather than lifting the card. A card that
   rises under the cursor promises a click, and these scenes have nowhere to
   go — so the answer is the set's own light growing, which is ambience and
   reads as nothing to press. */
.scene .stage__horizon { transition: opacity 300ms cubic-bezier(.16, 1, .3, 1); }

@media (hover: hover) {
  .scene:hover .stage__horizon { opacity: 0.66; }
  .scene:hover .scene__label { color: rgba(255, 255, 255, 0.94); }
}

.scene__label b {
  display: block;
  color: var(--white);
  font-weight: 500;
}

.scene--rose    .stage { --c1: #C9A3AF; --c2: #6E4F5C; --lit: #E7CBD3; }
.scene--purple  .stage { --c1: #8B6FB0; --c2: #493361; --lit: #B69BD8; }
.scene--magenta .stage { --c1: #C46FA8; --c2: #6B2F55; --lit: #F0A6CE; }
.scene--pastel  .stage { --c1: #D9CFE6; --c2: #A9B8D8; --lit: #FFFFFF; }
.scene--cyan    .stage { --c1: #7FD3E0; --c2: #2E5F73; --lit: #C7F0F7; }

.scene--purple  .stage { --arch-1: #E9DEF6; --arch-2: #8E76B4; }
.scene--pastel  .stage { --pod-top: #FFFFFF; --pod-bot: #C3CDE4; }
.scene--cyan    .stage { --pod-top: #F2FBFD; --pod-bot: #9CC6D3; }

.scene .stage__podium { width: 54%; bottom: 16%; }
.scene .stage__arch.is-left  { left: 10%; bottom: 34%; width: 26%; aspect-ratio: 0.6; }
.scene .stage__arch.is-right { right: 8%; bottom: 34%; width: 18%; aspect-ratio: 0.46; }
.scene .stage__sphere        { right: 14%; top: 16%; width: 22%; }
.scene .stage__bubble.is-a   { left: 12%;  top: 22%; width: 18%; }
.scene .stage__bubble.is-b   { left: 34%;  top: 10%; width: 11%; }
.scene .stage__cloud         { left: 8%; top: 20%; width: 60%; aspect-ratio: 2; }

/* ------------------------------------------------------------- palette */

/* The colorway moment: four values with names, the way a shoe box names its
   colours — but at area rather than at chip size, full bleed. It is also the
   page's third composition anchor: everything else is centred or left, and a
   run of centred sections reads as one long section however the copy varies. */
.palette {
  padding: clamp(64px, 12vh, 120px) 0;
  display: grid;
  justify-items: center;
  gap: clamp(28px, 5vh, 52px);
  text-align: center;
  background: var(--black);
}

.palette__head {
  padding: 0 var(--pad);
  display: grid;
  justify-items: center;
  gap: 16px;
}

.palette .eyebrow { color: var(--rose-text); }

/* Hairline by gap, not by border: a 1px grid gap over the rim colour draws
   every divider at once and never doubles one where two fields meet. The
   Blackout field survives on a black page by that rim alone, which is
   honest — it is the value, not a swatch of it. */
.swatches {
  margin: 0;
  padding: 0;
  list-style: none;
  width: 100%;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: rgba(255, 255, 255, 0.14);
  border-block: 1px solid rgba(255, 255, 255, 0.14);
}

/* Each field carries its own ink, and both pairings are BRAND.md's: Chalk on
   Blackout 21:1 and on Violet Hour 10.88:1, Blackout on Dusty Rose 8.02:1
   and on Chalk 21:1. */
.swatch {
  display: grid;
  align-content: end;
  gap: 4px;
  min-height: clamp(150px, 24vh, 240px);
  padding: 16px;
  text-align: left;
  font-size: 14px;
}

.swatch b { font-weight: 500; }

.swatch__hex {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  opacity: 0.75;
}

.swatch--blackout { background: var(--black); color: var(--white); }
.swatch--violet   { background: var(--plum);  color: var(--white); }
.swatch--rose     { background: var(--rose);  color: var(--black); }
.swatch--chalk    { background: var(--white); color: var(--black); }

/* four fields across a phone leave no room for a name */
@media (max-width: 640px) {
  .swatches { grid-template-columns: repeat(2, 1fr); }
}

/* ----------------------------------------------------------- colophon */

.colophon {
  padding: clamp(64px, 12vh, 120px) var(--pad);
  display: grid;
  justify-items: center;
  gap: 18px;
  text-align: center;
  background: var(--black);
  /* hairline, never a solid line: palette and colophon share a ground */
  border-top: 1px solid rgba(255, 255, 255, 0.10);
}

.colophon img { width: clamp(150px, 26vw, 220px); }

.colophon p {
  margin: 0;
  color: var(--rose-text);
}

/* ---------------------------------------------------------------- motion */

@media (prefers-reduced-motion: no-preference) {
  /* Every hidden state on the page hangs off `.reveal-ready`, and only the
     head script sets it — so nothing can be hidden on a page whose JS is not
     going to bring it back. Without JS, without an observer, or under a
     reduced-motion preference, the class never lands and this whole block
     stops applying. The class is inside the media query as well, so a
     preference flipped mid-session reveals everything rather than freezing
     it. */
  .reveal-ready [data-reveal] {
    opacity: 0;
    translate: 0 14px;
    transition: opacity 600ms ease, translate 600ms cubic-bezier(.16, 1, .3, 1);
  }

  .reveal-ready [data-reveal].is-in {
    opacity: 1;
    translate: 0 0;
  }

  /* The display line prints instead of fading: it wipes up out of its own
     box, which is the page's one borrowing of the poster gesture the hero
     gave back. 900ms because it travels a full cap height — distance sets
     duration. The insets run negative at rest so the clip clears descenders;
     `.display` sits on 1.05 leading, which leaves a `y` nothing to spare. */
  .reveal-ready [data-reveal="wipe"] {
    opacity: 1;
    translate: 0 0;
    clip-path: inset(-0.12em -0.12em 100% -0.12em);
    transition: clip-path 900ms cubic-bezier(.16, 1, .3, 1);
  }

  .reveal-ready [data-reveal="wipe"].is-in {
    clip-path: inset(-0.12em -0.12em -0.18em -0.12em);
  }

  /* siblings arrive 60ms apart — a stagger, not a queue */
  .strip .scene:nth-child(2),  .swatch:nth-child(2) { transition-delay: 60ms; }
  .strip .scene:nth-child(3),  .swatch:nth-child(3) { transition-delay: 120ms; }
  .strip .scene:nth-child(4),  .swatch:nth-child(4) { transition-delay: 180ms; }
  .strip .scene:nth-child(5) { transition-delay: 240ms; }

  /* The Aurora's ambient. The band's ends taper because the blur runs off
     its own edges, so it cannot drift sideways without dragging a taper
     into view — it breathes instead. Opacity only, off the main thread. */
  .aurora__field {
    animation: aurora-breathe 16s ease-in-out infinite alternate;
  }

  /* the swing straddles the still value rather than sitting under it —
     the band measured 0.89 of its raw gradient on the cover, and that is
     where it has to spend most of its time */
  @keyframes aurora-breathe {
    from { opacity: 0.88; }
    to   { opacity: 1; }
  }

  /* Intro choreography — CSS keyframes, so it runs with JS disabled and
     the reduced branch never hides anything. The cover assembles in its
     own reading order: the mark, then the light, then the file meta. */
  .hero__mark,
  .hero__aurora,
  .hero__meta {
    animation: hero-fade 900ms cubic-bezier(.16, 1, .3, 1) both;
  }

  .hero__aurora { animation-delay: 400ms; }
  .hero__meta   { animation-delay: 700ms; }

  @keyframes hero-fade {
    from { opacity: 0; translate: 0 14px; }
    to   { opacity: 1; translate: 0 0; }
  }
}
