/* =============================================================
   UCI × Santini — Ride Your Colours
   UCI Solidarity Programme · 2026 UCI Road World Championships

   Built from Figma c1JlAXz64QxiE7d0WCLumG,
   frame "Homepage EN" (402:16), 1280 × 8412.

   SCALING MODEL
   1rem = viewport/128, so every Figma pixel maps to (px / 10)rem
   and the composition scales proportionally with no drift.
   Frozen at 1600px, centred full-bleed beyond. Below 1024px a
   single-column flow layout takes over.
   ============================================================= */

@font-face {
  font-family: 'Funnel Display';
  font-style: normal; font-weight: 400 700; font-display: swap;
  src: url('fonts/funnel-display-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Funnel Display';
  font-style: normal; font-weight: 400 700; font-display: swap;
  src: url('fonts/funnel-display-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/eb-garamond-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/eb-garamond-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal; font-weight: 700; font-display: swap;
  src: url('fonts/eb-garamond-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal; font-weight: 700; font-display: swap;
  src: url('fonts/eb-garamond-700-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Epilogue';
  font-style: normal; font-weight: 600; font-display: swap;
  src: url('fonts/epilogue-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Epilogue';
  font-style: normal; font-weight: 600; font-display: swap;
  src: url('fonts/epilogue-600-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --uci-blue: #004899;
  --bar-blue: #317fc6;
  --bar-red: #b70035;
  --bar-ink: #12100b;
  --bar-yellow: #ffec00;
  --bar-green: #5aa92b;
  --placeholder: #d9d9d9;
  --ink: #000;
  --paper: #fff;
  --sans: 'Funnel Display', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --serif: 'EB Garamond', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --util: 'Epilogue', var(--sans);
}

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

html {
  font-size: calc(100vw / 128);
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (min-width: 1600px) { html { font-size: 12.5px; } }

body {
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  line-height: 1.2;
  letter-spacing: -0.02em;
  overflow-x: hidden;
}

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

/* The rule above sets no width, so an <img> whose rem box lives on a WRAPPER —
   <picture>, the <h1> around the lockup, the .footer__social div — keeps its own
   width attribute instead, and that attribute is the 1x export of the 1280 canvas.
   Above a 1280 viewport the root font-size keeps growing and the image does not:
   it renders 1 - 1280/min(vw,1600) short, locked at 20% from 1600 up, flush-left
   in its box. A sheet the design centres therefore drifts LEFT by half the
   shortfall - 12.6rem for the 126.49rem flag rows, 4.19rem for the lockup, and
   the agenda grid loses its 32.1rem (128/4) pitch entirely. Filling the box makes
   every viewport >= 1280 render exactly the 1280 Figma canvas; verified identical
   to 0.06rem. Free: seven of these are SVG, and the two photos already fetch
   their @2x and were drawing it 20% small.
   Where the rem width sits on the <img> itself (.footer__partner, .agenda__jersey,
   .hero__rail, .project__pill) author CSS beats the attribute and nothing is wrong.
   Keep this list explicit: `.band > * > img` would tie on specificity with
   .hero__cta img and .discover img. Never remove the width/height attributes to
   "fix" this - they carry the CLS ratio, and mobile.css restates aspect-ratio
   precisely because they hold the desktop ratio for the -m SVG swap.
   Watch on edit: .project__flags-b clears its band by 1.52rem and .wall__marquee
   by 1.20rem, so a taller re-export of either would clip. */
.hero__mark img,
.hero__lockup img,
.project__flags-a img,
.project__flags-b img,
.agenda__lines img,
.santini__atelier img,
.voices__photo img,
.voices__rail img,
.wall__marquee img { width: 100%; }
.br-m { display: none; }   /* mobile-only line breaks, see mobile.css */
a { color: inherit; text-decoration: none; }
p, blockquote, figure { margin: 0; }
h1, h2, h3 { margin: 0; font-weight: inherit; font-size: inherit; }
ul { margin: 0; padding: 0; list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; }
:focus-visible { outline: 0.2rem solid var(--bar-yellow); outline-offset: 0.3rem; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; top: 0.8rem; left: 0.8rem; z-index: 100;
  padding: 0.8rem 1.4rem; background: var(--paper); color: var(--ink);
  font-size: 1.4rem; transform: translateY(-200%); transition: transform 0.2s;
}
.skip-link:focus { transform: none; }

/* ---------- canvas ---------- */
.canvas { position: relative; width: 100%; }

.band {
  position: relative; width: 100%; overflow: hidden;
  --gutter: max(0px, calc((100vw - 128rem) / 2));
}
.band > * { position: absolute; transform: translateX(var(--gutter)); }

/* Three layers. The photo is frozen at 128rem and centred, as the scaling model
   requires; beyond that width the band's flat colour used to show as grey bars
   either side — a third of the hero on a 2560 screen. Layers 2 and 3 are the
   photo's own outermost pixel column, stretched across each half, so the gutters
   continue the picture exactly: same vertical scale, same colour at every
   height, zero step at the seam. Scaling the photo up instead (cover) would
   bleed but crop 38% off the bottom at 2560, taking three of the five hero kits
   with it. At and below 128rem layer 1 covers the band and the others are never
   seen, so nothing changes at the design width. */
.plate {
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-position: center top, left top, right top;
  background-size: min(100%, 128rem) 100%, 50% 100%, 50% 100%;
}

.colourbar { position: relative; display: flex; width: 100%; height: 2rem; }
.colourbar--m { display: none; }   /* the mobile frame has one more bar than desktop */
.colourbar span { display: block; height: 100%; }
.cb-1 { flex: 0 0 19.765%; background: var(--bar-blue); }
.cb-2 { flex: 0 0 20.937%; background: var(--bar-red); }
.cb-3 { flex: 0 0 19.766%; background: var(--bar-ink); }
.cb-4 { flex: 0 0 19.766%; background: var(--bar-yellow); }
.cb-5 { flex: 1 1 auto;    background: var(--bar-green); }

/* ---------- type roles (measured in Figma) ---------- */
.heading {            /* section headings — EB Garamond 35 / 1.16 / -0.7 */
  font-family: var(--serif); font-weight: 500;
  font-size: 3.5rem; line-height: 1.16; letter-spacing: -0.07rem;
  text-align: center;
}
.pull {               /* keynote quotes — EB Garamond 26 / 1.01 / -0.52 */
  font-family: var(--serif); font-weight: 500;
  font-size: 2.6rem; line-height: 1.01; letter-spacing: -0.052rem;
  text-align: center;
}
.statement {          /* the blue section's editorial block */
  font-family: var(--serif); font-weight: 500;
  font-size: 2.8rem; line-height: 1.16; letter-spacing: -0.056rem;
  text-align: center;
}
.statement strong { font-weight: 700; }
.byline { font-size: 1.5rem; line-height: 1.25; letter-spacing: -0.026rem; text-align: center; }
.byline strong { display: block; font-weight: 700; text-transform: uppercase; }
.byline span { display: block; font-weight: 400; }
.prose { font-size: 1.4rem; line-height: 1.214; letter-spacing: -0.028rem; text-align: justify; }
.prose p + p { margin-top: 1.6rem; }
.lede { font-size: 1.6rem; line-height: 1.3; letter-spacing: -0.032rem; text-align: center; }
.lede strong { font-weight: 700; }
.discover {
  display: flex; align-items: center; gap: 1rem;
  font-weight: 600; font-size: 1.6rem; letter-spacing: -0.032rem;
  text-transform: uppercase; text-decoration: underline; text-underline-offset: 0.3rem;
  transition: opacity 0.25s, gap 0.25s;
}
.discover:hover { opacity: 0.65; gap: 1.6rem; }
.discover img { width: 1.6rem; }

.hotspots { position: relative; }
.hotspots > .hotspot { position: absolute; top: 0; height: 100%; border-radius: 0.4rem; transition: opacity 0.2s; }
.hotspots > .hotspot:hover { opacity: 0.55; background: rgba(255,255,255,0.08); }

/* ---------- 1 · nav (68px) ---------- */
.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 50; height: 6.8rem;
  background: rgba(248, 249, 250, 0.3);
  backdrop-filter: blur(1.2rem); -webkit-backdrop-filter: blur(1.2rem);
}
.nav__inner { position: relative; height: 100%; width: min(100%, 128rem); margin: 0 auto; }
.nav a, .nav__lang {
  position: absolute; top: 2.8rem;
  font-size: 1.8rem; line-height: 1.2; letter-spacing: -0.036rem; color: #fff;
}
.nav a { transition: opacity 0.2s; }
.nav a:hover { opacity: 0.6; }
.nav__project     { left: 8.1rem; }
.nav__partnership { left: 31.7rem; }
.nav__design      { left: 54.9rem; }
.nav__jerseys     { left: 75.4rem; }
.nav__press       { left: 96rem; }
.nav__lang { left: 114.1rem; display: flex; align-items: baseline; }
.nav__lang a { position: static; top: auto; }
.nav__lang [aria-current='true'] { padding: 0.15rem 0.55rem; border: 0.1rem solid #fff; }
.nav__lang em { font-style: normal; padding: 0 0.5rem; opacity: 0; }
.nav__toggle { display: none; }

/* ---------- 2 · hero (0 → 1668) ---------- */
.hero {
  height: 166.8rem; background-color: #8f9296;
  background-image: url('img/hero.jpg'), url('img/hero-edge-l.png'), url('img/hero-edge-r.png');
  background-image: -webkit-image-set(url('img/hero.jpg') 1x, url('img/hero@2x.jpg') 2x),
                    url('img/hero-edge-l.png'), url('img/hero-edge-r.png');
  background-image: image-set(url('img/hero.jpg') 1x, url('img/hero@2x.jpg') 2x),
                    url('img/hero-edge-l.png'), url('img/hero-edge-r.png');
}
.hero__mark   { left: 57.3rem; top: 14.5rem; width: 14.45rem; }
.hero__lockup { left: 43.7rem; top: 26.8rem; width: 41.81rem; }
.hero__cta {
  left: 44rem; top: 66.5rem; width: 40rem;
  display: flex; flex-direction: column; align-items: center; gap: 1rem;
  font-weight: 600; font-size: 1.3rem; letter-spacing: -0.026rem;
  text-transform: uppercase; text-align: center;
}
.hero__cta img { width: 1.8rem; animation: nudge 2.6s ease-in-out infinite; }
@keyframes nudge { 0%,100% { transform: translateY(0); } 50% { transform: translateY(0.6rem); } }
.hero__rail        { left: 8.1rem;   top: 113.2rem; width: 4.97rem; }
.hero__federations { left: 110.1rem; top: 118rem;   width: 9.6rem; }
.hero__supporting  { left: 58.4rem;  top: 159.7rem; width: 11.51rem; }

/* ---------- 3 · the project (blue, 1668 -> 2500) ---------- */
.project { height: 83.2rem; background: var(--uci-blue); }
.project__flags-a   { left: 1rem;    top: 0.4rem;  width: 126.49rem; }
.project__flags-b   { left: 1rem;    top: 76.9rem; width: 126.49rem; }
.project__lede      { left: 26.1rem; top: 18.2rem; width: 76rem; }
.project__pill      { left: 55.9rem; top: 26.6rem; width: 16.01rem; }
.project__statement { left: 26.1rem; top: 32.8rem; width: 76rem; }
.project__closing   { left: 26.1rem; top: 56.3rem; width: 76rem; text-align: center; }

/* ---------- 4 · media strip (black, 2500 -> 2946) ---------- */
.media { height: 44.6rem; background: var(--ink); }
.media__carousel { left: 0; top: 8.2rem; width: 128rem; }
.media__scroller {
  position: relative;
  width: 100%;
  display: flex; gap: 4.8rem; padding: 0 8.1rem;
  /* snapping ignores padding unless told otherwise, which would pull the first
     card flush against the viewport edge instead of leaving its 8.1rem inset */
  scroll-padding-inline-start: 8.1rem;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scrollbar-width: none;
}
.media__scroller::-webkit-scrollbar { display: none; }
.media__item {
  flex: 0 0 auto;
  min-width: 0;
  height: 23.7rem;
  scroll-snap-align: start;   /* the strip starts flush left, as the frame shows */
  overflow: hidden;
  background: #111;
}
.media__item img { width: 100%; height: 100%; object-fit: cover; }
.media__item--wide { width: 41.9rem; }
.media__item--tall { width: 19rem; }

/* Each card is a facade over a YouTube Short: the thumbnail is a local asset
   pre-cropped to the card, and the play glyph plus duration stand in for the
   badges that were baked into the placeholder stills. */
.media__video {
  position: relative;
  display: block;
  width: 100%; height: 100%;
  transition: opacity 0.25s;
}
.media__video:hover { opacity: 0.9; }
.media__video:focus-visible { outline: 0.3rem solid var(--bar-yellow); outline-offset: -0.3rem; }
.media__play {
  position: absolute; left: 50%; top: 50%;
  width: 4.4rem; height: 4.4rem; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.55);
  /* a hairline ring so the glyph still reads as a control over busy frames —
     a white-translucent disc like the arrows would vanish on the studio shots */
  box-shadow: inset 0 0 0 0.1rem rgba(255, 255, 255, 0.3);
  transform: translate(-50%, -50%);
  transition: background 0.25s, transform 0.25s;
}
.media__video:hover .media__play { background: rgba(0, 0, 0, 0.8); transform: translate(-50%, -50%) scale(1.08); }
.media__play svg { width: 2rem; height: 2rem; margin-left: 0.25rem; fill: var(--paper); }
.media__caption {
  position: absolute; inset: auto 0 0 0;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 0.8rem;
  padding: 3.4rem 1.1rem 1rem;
  /* a scrim rather than a solid bar, so the photography still reads underneath */
  background: linear-gradient(to top, rgba(0,0,0,0.85), rgba(0,0,0,0.45) 45%, rgba(0,0,0,0));
}
.media__title {
  font-weight: 600; font-size: 1.3rem; line-height: 1.14; letter-spacing: -0.026rem;
  text-transform: uppercase; color: var(--paper);
  /* two lines is what the 19rem card affords before the scrim eats the image */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.media__time {
  flex: 0 0 auto;
  padding: 0.25rem 0.5rem; border-radius: 0.3rem;
  background: rgba(0, 0, 0, 0.72);
  font-size: 1.1rem; line-height: 1.1; letter-spacing: 0; color: var(--paper);
}
.media__social { left: 8.1rem; top: 35rem; display: flex; gap: 1.8rem; }
.media__social a { display: block; }
.media__social svg { width: 2.6rem; height: 2.6rem; fill: currentColor; }
.media__link { left: 92.6rem; top: 36.9rem; font-size: 1.9rem; letter-spacing: -0.038rem; }

/* Arrow controls, shared behaviour with the jersey rail. On the black strip they
   read as light chips rather than the white ones used over photography. */
.media__arrow {
  position: absolute;
  top: 50%;
  width: 4.4rem;
  height: 4.4rem;
  margin-top: -2.2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.14);
  color: var(--paper);
  cursor: pointer;
  transition: background 0.2s, opacity 0.2s;
}
.media__arrow:hover { background: rgba(255, 255, 255, 0.28); }
.media__arrow[disabled] { opacity: 0; pointer-events: none; }
.media__arrow svg { width: 2rem; height: 2rem; fill: none; stroke: currentColor; stroke-width: 2; }
.media__arrow--prev { left: 2rem; }
.media__arrow--next { right: 2rem; }
.media__scroller:focus-visible { outline: 0.3rem solid var(--bar-yellow); outline-offset: -0.3rem; }

/* ---------- 5 · agenda 2030 (photo, 2946 -> 3817) ---------- */
.agenda {
  height: 87.1rem; background-color: #8b9097;
  background-image: url('img/agenda.jpg'), url('img/agenda-edge-l.png'), url('img/agenda-edge-r.png');
}
.agenda__lines   { left: -50.5rem; top: -30.7rem; width: 186.31rem; }
.agenda__heading { left: 23rem;   top: 4.7rem;  width: 82.5rem; }
/* Quote, byline and prose form one flow stack, so the byline always follows its
   quote and copy length can no longer slide one under the other — the French
   translation runs a line longer than the English and used to overlap. The
   offsets are the Figma's own, measured on the English page: the byline box sits
   12.15rem inside the quote column, the prose 0.1rem. The band keeps its fixed
   height: even the French stack ends 10.38rem short of it. */
.agenda__stack   { position: static; width: 55.6rem; margin: 16.6rem 0 0 8.1rem; }
.agenda__pull    { width: 100%; }
.agenda__byline  { width: 30rem;   margin: 1.62rem 0 0 12.15rem; }
.agenda__prose   { width: 55.5rem; margin: 4.95rem 0 0 0.1rem; line-height: 1.143; }
.agenda__jersey  { left: 78rem;   top: 18.2rem; width: 42.1rem; }

/* ---------- 6 · santini (black, 3837 → 4660) ---------- */
/* No fixed height: the left stack drives it. The French prose needs 8.7rem more
   than the Figma frame allotted and used to be clipped by overflow:hidden. The
   band is flat black, so growing it costs nothing visually. */
.santini { background: var(--ink); }
.santini__heading { left: 23rem;   top: 6.2rem;  width: 82.5rem; }
/* Same flow stack, with the Figma's own gaps read off the English page:
   2.67rem quote-to-byline, 1.85rem to the badge, 3.39rem to the prose. The
   7.52rem bottom margin is the tail the frame had under the prose. */
.santini__stack   { position: static; width: 56.8rem; margin: 16.8rem 0 7.52rem 8rem; }
.santini__pull    { width: 100%; }
.santini__byline  { width: 30rem;   margin: 2.67rem 0 0 13.6rem; }
.santini__badge   { width: 6.11rem; margin: 1.85rem 0 0 25.6rem; }
.santini__cols {
  width: 55.7rem; margin: 3.39rem 0 0 0.2rem;
  display: grid; grid-template-columns: 26.4rem 26.2rem; gap: 3.1rem;
}
/* The atelier photo sits inside the stack so it can flow on mobile. Here it
   returns to the right-hand column, absolute against the stack — the gutter
   transform on the stack makes it the containing block, so these coordinates
   are the Figma ones minus the stack origin (75.8 - 8, 16.8 - 16.8). */
.santini__atelier { position: absolute; left: 67.8rem; top: 0; width: 43.9rem; }

/* ---------- 7 · in the words of … (black, 4660 → 5463) ---------- */
.voices { height: 80.3rem; background: var(--ink); }
.voices__heading { left: 22.7rem; top: 1.6rem;  width: 82.5rem; }
.voices__photo   { left: 8.2rem;  top: 13.1rem; width: 55.9rem; }
.voices__rail    { left: 55.2rem; top: 30rem;   width: 2.31rem; }
/* The quotes live in one positioned column, so each byline flows under its own
   quote and no anchor depends on copy length. The gaps are the Figma's own,
   measured off the placeholder pair before it was removed: 3.35rem
   quote-to-byline, 11.05rem between blocks. Only one quote is live today; the
   sibling rule is kept so a second one is a pure markup addition. The byline's
   narrower Figma box was inset 1.8rem either side of the same centre line, so a
   single column width reproduces it exactly. */
.voices__quotes { left: 75.8rem; top: 21.1rem; width: 43.6rem; }
.voices__slot .byline { margin-top: 3.35rem; }
.voices__slot + .voices__slot { margin-top: 11.05rem; }

/* ---------- 8 · jersey wall (5483 → 6316) ---------- */
/* The Figma section is a 21,665px panorama of the 41 kits on a rail, of which
   the mockup shows only the first 1280px. Shipping that crop meant a stretched,
   low-resolution background. This is the same composition built from the HD
   per-kit photography in /maillots, which also makes all 41 reachable.
   Each source frame carries the rail across it, so the items butt together with
   no gap and the rail reads as continuous. --kit-focus trims the vertical
   framing in one place if the supplied crop sits high or low. */
.wall { height: 83.3rem; background: #e9eaea; --kit-focus: 58%; }
/* Full bleed rather than 128rem: the band's #e9eaea is lighter than the kit
   files' studio backdrop, so a capped rail read as two pale bars on a wide
   screen. Bleeding it also puts a fourth kit on screen at 2560. transform:none
   cancels the gutter shift .band > * applies. */
.wall__carousel { left: 0; top: 0; width: 100%; height: 100%; transform: none; }
.wall__rail {
  display: flex;
  gap: 0;
  height: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.wall__rail::-webkit-scrollbar { display: none; }
.wall__item {
  /* The lane pitch equals height x the tile aspect, so object-fit crops nothing
     and the rail joins exactly from one tile to the next — the tiles are
     contiguous slices of assets/img/caroussel.png, so any crop would break a
     continuous bar 41 times. 83.3 x 366/583 = 52.29rem. min-width:0 is required:
     without it a flex item cannot go below its content's min-content width, and a
     replaced element sized height:100% contributes height x aspect — which would
     silently override flex-basis. */
  flex: 0 0 52.29rem;
  min-width: 0;
  height: 100%;
  scroll-snap-align: start;
}
.wall__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;   /* the tile already matches the lane aspect: no crop applied */
  background: #e9eaea; /* a missing file reads as an empty panel, not a break */
}
.wall__arrow {
  position: absolute;
  top: 50%;
  width: 5.6rem;
  height: 5.6rem;
  margin-top: -2.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.82);
  color: var(--ink);
  cursor: pointer;
  transition: background 0.2s, opacity 0.2s;
}
.wall__arrow:hover { background: #fff; }
.wall__arrow[disabled] { opacity: 0; pointer-events: none; }
.wall__arrow svg { width: 2.4rem; height: 2.4rem; fill: none; stroke: currentColor; stroke-width: 2; }
.wall__arrow--prev { left: 2rem; }
.wall__arrow--next { right: 2rem; }
.wall__rail:focus-visible { outline: 0.3rem solid var(--bar-yellow); outline-offset: -0.3rem; }
/* Below the kits, not over them: at 10.2rem the button sat across the garments.
   The panorama tiles hang the garments lower than the old files did — measured
   across all 41, the lowest hem reaches 64.9rem — and the badge marquee starts at
   78.1rem, so 69rem is the clear band between them.
   Right-aligned at 120rem rather than anchored left at 80rem: the new wording is
   45rem wide in English and 48.6rem in French, and the French one overflowed the
   128rem canvas from the old anchor. 120rem mirrors the marquee's 8.1rem inset. */
.wall__link    { left: 74.9rem; top: 69rem;   color: var(--ink); z-index: 2; }
.fr .wall__link { left: 71.4rem; }   /* le libelle francais fait 3.5rem de plus */
.wall__marquee { left: 8.1rem; top: 78.1rem; width: 109.51rem; z-index: 2; }

/* ---------- 9 · participating national federations (white, 6316 -> 7146) ---------- */
.federations { height: 83rem; background: var(--paper); color: var(--ink); }
.federations__heading { left: 23rem;  top: 10.9rem; width: 82.5rem; }
/* Live text rather than the outlined SVG: each federation has to be clickable,
   and 41 irregular hotspots over a flat image would be unmaintainable. Measured
   off the reference: 9 justified lines, 39px caps, 47px pitch, 1115px block. */
.federations__names {
  left: 8.1rem; top: 25.6rem; width: 111.5rem;
  font-size: 3.9rem; line-height: 1.205; letter-spacing: -0.078rem;
  text-transform: uppercase; text-align: justify;
}
.federations__names .fed {
  display: inline;
  font: inherit; letter-spacing: inherit; color: inherit;
  background: none; border: 0; padding: 0; margin: 0;
  cursor: pointer;
  transition: opacity 0.18s;
}
.federations__names .fed:hover { opacity: 0.45; }
.federations__names .fed:focus-visible { outline: 0.2rem solid var(--bar-yellow); outline-offset: 0.2rem; }

/* ---------- 10 · vote (black, 7166 → 7990) ---------- */
/* The Fastory desktop embed renders a hard 800px-tall iframe — it takes its
   width from the container but never its height, at any viewport. So the frame
   is sized in px and the band is rem + px: 28.3rem of heading and copy above,
   the 800px game, then the 10.6rem tail the Figma frame had below it. */
.vote { height: calc(38.9rem + 800px); background: var(--ink); }
.vote__heading { left: 34rem; top: 9.7rem;  width: 60.1rem; }
.vote__prose   { left: 26rem; top: 19.1rem; width: 76rem; text-align: center; font-size: 2rem; letter-spacing: -0.04rem; }
.vote__frame   { left: 26rem; top: 28.3rem; width: 76rem; height: 800px; background: var(--placeholder); }
/* the Fastory runtime sets its own height:100%; it does not set a width */
.vote__frame #fs-root { width: 100%; height: 100%; }
/* The mobile-only way into the game; see the note in the markup. Desktop keeps
   the real embed, where a button that leaves the page would be a regression. */
.vote__play { display: none; }

/* ---------- 11 · footer (black, 7990 → 8412) ---------- */
.footer { height: 42.2rem; background: var(--ink); }
.footer__links {
  left: 8.2rem; top: 18.5rem; display: flex; gap: 3.4rem;
  font-family: var(--util); font-weight: 600;
  font-size: 2.5rem; line-height: 1.2; letter-spacing: -0.05rem; text-transform: uppercase;
}
.footer__links a { text-decoration: underline; text-underline-offset: 0.25rem; transition: opacity 0.2s; }
.footer__links a:hover { opacity: 0.6; }
.footer__social  { left: 8.8rem;  top: 25.2rem; width: 38.41rem; }
/* The sheet is exported at 1x of the 1280 canvas, so its width attribute (384px)
   caps it via max-width once the root grows past 10px — it would sit 20% short of
   its 38.41rem box above a 1280 viewport. The hotspots are percentages of that box,
   so the shortfall slid every hit area right of its icon. Fill the box instead. */
.footer__social img { width: 100%; }
.footer__partner { left: 85.1rem; top: 15.55rem; width: 34.6rem; }
.footer__legal {
  left: 0; top: 36rem; width: 128rem;
  font-size: 1.2rem; letter-spacing: 0; text-align: center; opacity: 0.45;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

@media (min-width: 1024px) and (max-width: 1180px) {
  .nav__partnership { left: 29rem; }
  .nav__design      { left: 50rem; }
  .nav__jerseys     { left: 71rem; }
  .nav__press       { left: 93rem; }
}

/* ---------- French version: swap the outlined list ---------- */
.federations__list--fr, .federations__list--en-m, .federations__list--fr-m { display: none; }
@media (min-width: 1024px) {
  .fr .federations__list--en { display: none; }
  .fr .federations__list--fr { display: block; }
}


/* =============================================================
   KIT LIGHTBOX
   Opened from the federations list. Uses the carousel photography,
   so it needs no assets of its own.
   ============================================================= */
.kit {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 5vh 6vw;
  background: rgba(0, 0, 0, 0.86);
  backdrop-filter: blur(0.8rem);
  -webkit-backdrop-filter: blur(0.8rem);
}
.kit[hidden] { display: none; }
.kit__frame {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.8rem;
}
/* The kit files are framed for the carousel lane: rail pinned at the top, garment
   in the upper half, open backdrop below for the badge marquee. Shown whole in a
   lightbox that lower half reads as dead space, so the window crops to the
   garment. The crop is measured, not guessed: re-measured across all 41 files with
   one detector, the garment spans x 0.2709-0.7310 and no hem falls below y 0.4712,
   so the window takes the middle 57% of the width and the top 55% of the height —
   7.9% clearance under the longest hem (Jordan), 4.9% either side of the widest
   kit (Uganda). No second asset set to prepare or upload.

   The note here used to claim 0.234-0.753 over 39 files. Two files were outside
   that sample and cyprus.jpg was one of them: its garment sat at x 0.4068-0.8555,
   centre 0.6312 against a population median of 0.5000 — 12.8 sigma out — so this
   window sliced 79px, a sixth of its width, off the right sleeve. The file has
   been re-framed 121px left; the set's centre-x now spans only 0.0119. Check any
   kit added later against this window before dropping it in.

   Height is capped inside min() rather than by max-height: a max-height would
   win over aspect-ratio and shorten the window while the image kept its scale,
   which is what used to slice the hem off on any screen under ~1000px tall.
   48.4vh is 74vh x the window ratio, so the cap never breaks the framing. */
.kit__window {
  position: relative;
  overflow: hidden;
  width: min(78vw, 48.4vh, 44rem);
  aspect-ratio: 600 / 917;   /* 57% x 55% of the 1052 x 1666 source */
  background: #e9eaea;
}
.kit__window img {
  position: absolute;
  width: 175.4%;             /* 1 / 0.57 */
  height: auto;
  left: -36.8%;              /* -0.21 / 0.57 */
  top: 0;
  max-width: none;
}
.kit__name {
  font-weight: 600;
  font-size: 1.8rem;
  letter-spacing: -0.036rem;
  text-transform: uppercase;
  text-align: center;
  color: var(--paper);
}
.kit__close {
  position: absolute;
  top: 2.4rem;
  right: 2.4rem;
  width: 4.8rem;
  height: 4.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--paper);
  cursor: pointer;
  transition: opacity 0.2s;
}
.kit__close:hover { opacity: 0.6; }
.kit__close svg { width: 2.4rem; height: 2.4rem; fill: none; stroke: currentColor; stroke-width: 2; }
body.kit-open, body.player-open { overflow: hidden; }


/* =============================================================
   VIDEO LIGHTBOX
   Opened from the media strip. All five films are vertical 9:16
   Shorts, so the window is portrait. Sized off the viewport with
   min() so the 9:16 ratio is always exact — never letterboxed.
   ============================================================= */
.player {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 5vh 6vw;
  background: rgba(0, 0, 0, 0.9);
  backdrop-filter: blur(0.8rem);
  -webkit-backdrop-filter: blur(0.8rem);
}
.player[hidden] { display: none; }
.player__frame {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.8rem;
}
/* 43.8vh is 78vh x 9/16: whichever of the three limits bites, the box keeps
   its ratio, so the player never letterboxes itself. */
.player__window {
  position: relative;
  overflow: hidden;
  width: min(86vw, 43.8vh, 36rem);
  aspect-ratio: 9 / 16;
  background: #000;
}
.player__window iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.player__name {
  font-weight: 600;
  font-size: 1.8rem;
  letter-spacing: -0.036rem;
  text-transform: uppercase;
  text-align: center;
  color: var(--paper);
}
.player__close {
  position: absolute;
  top: 2.4rem;
  right: 2.4rem;
  width: 4.8rem;
  height: 4.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--paper);
  cursor: pointer;
  transition: opacity 0.2s;
}
.player__close:hover { opacity: 0.6; }
.player__close svg { width: 2.4rem; height: 2.4rem; fill: none; stroke: currentColor; stroke-width: 2; }
