/* ==========================================================================
   Tempo — design lab stylesheet
   Single source of truth for the visual language. Iterate here.
   ========================================================================== */

/* --- 1. Tokens ----------------------------------------------------------- */

:root {
  /* Surfaces. Navy, not black — black reads cheap on OLED phones. */
  --bg:        #070B13;
  --bg-raise:  #0C121D;
  --bg-panel:  #111926;
  --bg-inset:  #0A0F19;

  /* Hairlines instead of card borders everywhere. */
  --line:      rgba(255, 255, 255, 0.08);
  --line-mid:  rgba(255, 255, 255, 0.14);
  --line-hi:   rgba(255, 255, 255, 0.24);

  /* Ink. The ratio shown is the WORST case across all four surfaces below,
     which is always --bg-panel — the lightest one. Sizing a muted grey against
     --bg alone is the trap: --ink-3 was #6A7789, which looked fine at 4.33:1
     on --bg and was really 3.87:1 on a panel. All three now clear AA (4.5:1)
     everywhere. Re-run the check if you add a surface lighter than --bg-panel. */
  --ink:       #F4F7FC;  /* 16.4:1 */
  --ink-2:     #9FADC0;  /*  7.7:1 */
  --ink-3:     #7A8799;  /*  4.8:1 */

  /* Product accents. Overridden per page by [data-product]. */
  --blue:      #2E9BFF;
  --blue-hi:   #8CCBFF;
  --blue-deep: #0B4FA8;
  --amber:     #FF8A3C;
  --amber-hi:  #FFC08A;
  --amber-deep:#A34C11;

  --accent:      var(--blue);
  --accent-hi:   var(--blue-hi);
  --accent-deep: var(--blue-deep);
  --accent-tint: rgba(46, 155, 255, 0.12);
  --on-accent:   #04101F;

  /* Discord embed semantics — mirrors bot/ui/colors.py */
  --ok:   #2ECC71;
  --warn: #F1C40F;
  --bad:  #E74C3C;
  --info: #3498DB;

  /* Discord's own mobile palette, for the phone frame only. Deliberately not
     wired to our theme: the frame has to read as a screenshot of their client,
     and the moment it takes our navy it reads as an illustration of one.
     The chrome sits a step above near-black so the header reads as chrome
     rather than as a bezel that swallowed the top of the screen. */
  --dc-chrome: #2B2D31;
  --dc-body:   #313338;
  --dc-card:   #2B2D31;
  --dc-dim:    #949BA4;

  /* Type */
  --font-display: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --font-body:    "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, "SF Mono", Consolas, monospace;

  /* Capped at 5rem rather than 5.5. The home hero is a two-column grid, so the
     headline never gets the whole width to run in: at 5.5rem it wrapped to three
     lines inside its column, which pushed the hero past the bottom of a 1080p
     screen and cut the product shot in half. 5rem holds it to two.

     The middle term is 6vw rather than 7.4 so the size starts easing off before
     the cap does. At 7.4vw it sat pinned at the maximum all the way down to
     1080px, where the column can no longer hold two lines of it. */
  --fs-display: clamp(2.75rem, 6vw, 5rem);
  --fs-h1:      clamp(2.1rem, 5.2vw, 3.5rem);
  --fs-h2:      clamp(1.75rem, 3.8vw, 2.6rem);
  --fs-h3:      clamp(1.2rem, 2.2vw, 1.5rem);
  --fs-lead:    clamp(1.05rem, 1.5vw, 1.3rem);
  --fs-body:    1.0625rem;
  --fs-sm:      0.9375rem;
  --fs-label:   0.75rem;

  /* Space */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 24px;
  --s6: 32px; --s7: 48px; --s8: 64px; --s9: 96px; --s10: 128px;

  --gutter:  clamp(20px, 5vw, 40px);
  --section: clamp(64px, 9vw, 128px);
  /* 1360, not 1200. At 1200 a 1920 screen carries 353px of empty gutter on each
     side and the hero's copy column is squeezed to 517px, which is what made the
     page read as a narrow band floating in the middle of the monitor. 1360 is
     still short of the point where body text outruns a comfortable measure —
     the lede and prose have their own ch-based caps — and it leaves a 40px
     inset at 1440, so it does not go edge-to-edge on a laptop. */
  --max:     1360px;
  --radius:  14px;
  --radius-sm: 9px;

  /* Discord's mark, as a mask source. Held as a token so the path is written
     once for the whole site: it is used as a mask rather than as an <img>, so
     it takes its colour from currentColor and follows the product accent
     without a second copy existing in amber. */
  --discord-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M20.317 4.3698a19.7913 19.7913 0 0 0-4.8851-1.5152.0741.0741 0 0 0-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 0 0-.0785-.037 19.7363 19.7363 0 0 0-4.8852 1.515.0699.0699 0 0 0-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 0 0 .0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 0 0 .0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 0 0-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 0 1-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 0 1 .0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 0 1 .0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 0 1-.0066.1276 12.2986 12.2986 0 0 1-1.873.8914.0766.0766 0 0 0-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 0 0 .0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 0 0 .0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 0 0-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z'/%3E%3C/svg%3E");
}

/* Hoops is amber, Football is electric blue, and that is the fastest way to
   tell which product you are looking at. A whole page switches via
   [data-product]; .is-hoops does the same for one element on a page that shows
   both, so a Hoops button never renders in Football's colour. */
[data-product="hoops"],
.is-hoops {
  --accent:      var(--amber);
  --accent-hi:   var(--amber-hi);
  --accent-deep: var(--amber-deep);
  --accent-tint: rgba(255, 138, 60, 0.12);
  --on-accent:   #1B0A00;
}

/* --- 2. Base ------------------------------------------------------------- */

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
body.is-locked { overflow: hidden; }

h1, h2, h3, h4, .lab-type {
  font-family: var(--font-display);
  font-weight: 800;
  line-height: 1.04;
  letter-spacing: -0.025em;
  margin: 0;
  text-wrap: balance;
}
p { margin: 0; }
a { color: inherit; text-decoration: none; }
img, svg { display: block; max-width: 100%; }
ul { margin: 0; padding: 0; list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

:focus-visible {
  outline: 2px solid var(--accent-hi);
  outline-offset: 3px;
  border-radius: 4px;
}

::selection { background: var(--accent); color: var(--on-accent); }

/* --- 3. Layout primitives ------------------------------------------------ */

.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap-narrow { max-width: 760px; }

.section { padding-block: var(--section); }
.section-tight { padding-block: clamp(48px, 6vw, 80px); }
.section + .section { border-top: 1px solid var(--line); }
.section-raise { background: var(--bg-raise); }

.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--s4);
  display: flex;
  align-items: center;
  gap: 10px;
}
.eyebrow::before {
  content: "";
  width: 22px;
  height: 2px;
  background: currentColor;
  flex: none;
}
.eyebrow-plain::before { display: none; }

.lede {
  font-size: var(--fs-lead);
  line-height: 1.55;
  color: var(--ink-2);
  max-width: 56ch;
  text-wrap: pretty;
}

.section-head { max-width: 720px; margin-bottom: clamp(40px, 5vw, 68px); }
.section-head h2 { font-size: var(--fs-h2); margin-bottom: var(--s4); }
.section-head-center { margin-inline: auto; text-align: center; }
.section-head-center .lede { margin-inline: auto; }
.eyebrow-center { justify-content: center; }

.stack-4 > * + * { margin-top: var(--s4); }
.stack-5 > * + * { margin-top: var(--s5); }
.stack-6 > * + * { margin-top: var(--s6); }

/* --- 4. Brand mark ------------------------------------------------------- */

/* The mark and the TEMPO lettering are the artwork on file in assets/brand.
   They are fixed blue/chrome, so the lockup never takes the product accent —
   only the product tag beside it does. `--bm` scales the whole lockup. */
.brandmark { --bm: 30px; display: flex; align-items: center; gap: 10px; color: var(--ink); }
@media (max-width: 560px) { .site-header .brandmark { --bm: 26px; } }
.brandmark .bm-mark { height: var(--bm); width: auto; flex: none; }
.brandmark .bm-type { height: calc(var(--bm) * 0.5); width: auto; flex: none; }
.brandmark .product {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
  align-self: center;
  padding-left: 10px;
  margin-left: 2px;
  border-left: 1px solid var(--line-mid);
  line-height: 1;
}

/* --- 5. Header / navigation ---------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 60;
  background: rgba(7, 11, 19, 0.82);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}
.nav {
  display: flex;
  align-items: center;
  gap: var(--s6);
  height: 68px;
}
.nav-links { display: flex; align-items: center; gap: 28px; margin-left: auto; }
.nav-links a {
  font-size: var(--fs-sm);
  color: var(--ink-2);
  transition: color 0.15s;
  position: relative;
  padding-block: 6px;
}
.nav-links a:hover { color: var(--ink); }
.nav-links a[aria-current="page"] { color: var(--ink); }
.nav-links a[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--accent);
}
.nav-cta { margin-left: 4px; }
.burger {
  margin-left: auto;
  display: none;
  width: 42px; height: 42px;
  align-items: center; justify-content: center;
  border: 1px solid var(--line-mid);
  border-radius: 10px;
}
.burger span {
  display: block; width: 17px; height: 1.5px; background: var(--ink);
  position: relative;
}
.burger span::before, .burger span::after {
  content: ""; position: absolute; left: 0; width: 17px; height: 1.5px; background: var(--ink);
}
.burger span::before { top: -5.5px; }
.burger span::after  { top:  5.5px; }

.mobile-menu {
  position: fixed;
  inset: 68px 0 0 0;
  background: var(--bg);
  z-index: 55;
  padding: var(--s6) var(--gutter) var(--s8);
  display: none;
  flex-direction: column;
  overflow-y: auto;
}
.mobile-menu.is-open { display: flex; }
.mobile-menu a.m-link {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.75rem;
  letter-spacing: -0.02em;
  padding-block: var(--s4);
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between;
}
.mobile-menu a.m-link[aria-current="page"] { color: var(--accent); }
.mobile-menu a.m-link .m-tag {
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.16em;
  color: var(--ink-3); text-transform: uppercase; font-weight: 400;
}
.mobile-menu .m-foot { margin-top: var(--s7); display: grid; gap: var(--s3); }

@media (max-width: 900px) {
  .nav .nav-links, .nav .nav-cta { display: none; }
  .nav .burger { display: flex; }
}

/* --- 6. Buttons ---------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  height: 48px;
  padding-inline: 22px;
  border-radius: 10px;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: -0.005em;
  white-space: nowrap;
  transition: transform 0.14s ease, background 0.14s, border-color 0.14s, color 0.14s;
}
.btn:active { transform: translateY(1px); }
.btn svg { flex: none; }

.btn-primary { background: var(--accent); color: var(--on-accent); }
.btn-primary:hover { background: var(--accent-hi); }

/* The install button — the one action the whole site exists to produce. It gets
   treatment nothing else does, on purpose: a flat accent fill made it a peer of
   every other primary button on the page, and on the homepage split it sat in
   the same row as "Explore Football" looking like the same weight of decision.

   The Discord mark is carried as a mask rather than inlined markup, so the glyph
   lives once here instead of being pasted into eighteen anchors, and it inherits
   currentColor — which means it flips with the product accent for free instead
   of needing a second amber copy. The glow is mixed from --accent, so Hoops
   throws an amber one and Football a blue one without either being named. */
.btn-install {
  background: linear-gradient(180deg, var(--accent-hi), var(--accent) 62%);
  color: var(--on-accent);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.28),
    0 10px 26px -12px color-mix(in srgb, var(--accent) 75%, transparent);
  transition: transform 0.14s ease, box-shadow 0.2s, filter 0.14s;
}
.btn-install::before {
  content: "";
  width: 1.2em; height: 1.2em; flex: none;
  background: currentColor;
  -webkit-mask: var(--discord-mark) center / contain no-repeat;
  mask: var(--discord-mark) center / contain no-repeat;
}
.btn-install:hover {
  filter: saturate(1.06);
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.34),
    0 16px 34px -14px color-mix(in srgb, var(--accent) 88%, transparent);
}
.btn-install:active { transform: translateY(1px); box-shadow: inset 0 1px 0 rgba(255,255,255,0.2); }

/* Two install buttons side by side are two products, not a primary and a
   fallback, so neither is allowed to outrank the other: they share a row and
   match widths rather than shrink-wrapping to their labels. */
.btn-row .btn-install + .btn-install { margin-left: 2px; }

@media (prefers-reduced-motion: reduce) {
  .btn-install, .btn-install:hover { transform: none; transition: box-shadow 0.2s; }
}

.btn-secondary { border: 1px solid var(--line-hi); color: var(--ink); }
.btn-secondary:hover { border-color: var(--ink); background: rgba(255,255,255,0.04); }

.btn-feature {
  border: 1px solid color-mix(in srgb, var(--accent) 60%, var(--line-hi));
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  color: var(--accent-hi);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.08);
}
.btn-feature:hover {
  border-color: var(--accent-hi);
  background: color-mix(in srgb, var(--accent) 21%, transparent);
  box-shadow: 0 10px 28px -18px var(--accent);
}

.btn-ghost { padding-inline: 0; height: auto; color: var(--accent); }
.btn-ghost:hover { color: var(--accent-hi); }
.btn-ghost:hover svg { transform: translateX(3px); }
.btn-ghost svg { transition: transform 0.14s; }
/* Dropping the button height is what makes this read as a link rather than a
   third button, but it leaves a 25px-tall target sitting beside a 56px one,
   and on a phone the two are hit with the same thumb. Growing the hit area
   with a pseudo-element buys the ~48px a touch target wants without moving
   any ink or changing the layout. Scoped to coarse pointers, so a mouse still
   gets a hover region that matches exactly what is underlined. */
@media (pointer: coarse) {
  .btn-ghost { position: relative; }
  .btn-ghost::after { content: ""; position: absolute; inset: -12px -10px; }
}

.btn-sm { height: 40px; padding-inline: 16px; font-size: 0.875rem; }
.btn-lg { height: 56px; padding-inline: 30px; font-size: 1.0625rem; }
.btn-block { width: 100%; }

/* Placeholder: an action the real site will wire up later. Drops any solid fill
   it inherits from .btn-primary — a dimmed label on a live-looking accent button
   reads as broken rather than as not-yet-wired. */
.btn-todo,
.btn-todo:hover {
  background: transparent;
  border: 1px dashed var(--line-hi);
  color: var(--ink-2);
  cursor: not-allowed;
}
.btn-todo:active { transform: none; }

.btn-row { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: center; }

/* On a phone the large CTAs go full width — a thumb-sized target beats a
   tidy shrink-wrapped one, and it keeps the primary/secondary order obvious. */
@media (max-width: 540px) {
  .btn-lg { width: 100%; }
  .btn-row .btn-ghost { width: auto; }
}

/* --- 7. Chips & tags ----------------------------------------------------- */

.chip {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 5px 10px;
  border-radius: 6px;
  border: 1px solid var(--line-mid);
  color: var(--ink-2);
  line-height: 1;
}
.chip-accent { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); background: var(--accent-tint); }
.chip-soon   { color: var(--ink-3); border-style: dashed; }
.chip-dot::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none;
}

.cmd {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--accent-tint);
  color: var(--accent-hi);
  padding: 2px 7px;
  border-radius: 5px;
  white-space: nowrap;
}

/* --- 8. Hero ------------------------------------------------------------- */

/* The padding is capped well below the section rhythm elsewhere on the site,
   because the hero is the one block whose height competes with the fold. At
   104/120 the section ran 996px tall and, under a 69px header, finished below
   the bottom of a 1080p window: the buttons and the product shot were both
   things you had to scroll to find. */
.hero {
  position: relative;
  padding-block: clamp(56px, 6vw, 80px) clamp(64px, 6.5vw, 88px);
  overflow: hidden;
}
.hero::before {
  content: "";
  position: absolute;
  top: -340px; left: 50%;
  width: 1100px; height: 760px;
  transform: translateX(-58%);
  background: radial-gradient(ellipse at center, rgba(46,155,255,0.16), transparent 62%);
  pointer-events: none;
}
[data-product="hoops"] .hero::before { background: radial-gradient(ellipse at center, rgba(255,138,60,0.14), transparent 62%); }

.hero-grid {
  position: relative;
  display: grid;
  gap: clamp(40px, 6vw, 76px);
  align-items: center;
}
/* The shot column is sized to the phone, not to half the hero. The phone is a
   fixed 390px, so an even split handed it 200px of slack it could not use while
   the headline was squeezed into the other half and wrapped to three lines. A
   named 390px track gives every spare pixel to the copy — the column goes from
   596px to 814px at 1920 — and the shot is anchored to the end of it, so the
   hero spans the width it claims: copy at one edge, product at the other. */
@media (min-width: 1001px) {
  .hero-grid { grid-template-columns: minmax(0, 1fr) 390px; }
  .hero-grid > :last-child { display: flex; justify-content: flex-end; }
}

/* Greedy wrapping, against the balance set on every other heading. Balance
   minimises the longest line, which here means it prefers "Keep your" over
   "league moving." to "Keep your league" over "moving." — it splits the phrase
   and buries the one word the sentence is built around. Greedy fills the first
   line and drops the accent word onto its own, which is the whole point of
   colouring it. Worth knowing: the keyword is `wrap`. `normal` is not a valid
   text-wrap value, so setting it silently leaves balance in force. */
.hero h1 { font-size: var(--fs-display); margin-bottom: var(--s5); text-wrap: wrap; }
.hero h1 em { font-style: normal; color: var(--accent); }
.hero .lede { max-width: 46ch; }

.hero-facts {
  display: flex; flex-wrap: wrap; gap: var(--s3) var(--s5);
  margin-top: var(--s6);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}
.hero-facts li {
  font-size: 0.875rem; color: var(--ink-3);
  position: relative; padding-left: 14px;
}
.hero-facts li::before {
  content: ""; position: absolute; left: 0; top: 0.62em;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent);
}

/* The price, directly under the buttons. A sentence rather than a pricing
   card on purpose: one number, stated once, with no table built around it. */
.hero-price {
  margin-top: var(--s5);
  font-size: 0.9375rem;
  color: var(--ink-2);
  max-width: 44ch;
}
.hero-price strong { color: var(--accent); font-weight: 600; }

/* Stacks at 1000, not 900. Between those two the two-column hero is a 390px
   phone beside a copy column of about the same width, which is too narrow to
   hold the headline: it was the one band left where it still wrapped to three
   lines. Stacked, the copy gets the full width and the headline fits on one.
   This is the same treatment the layout already gave anything under 900. */
@media (max-width: 1000px) {
  /* minmax(0,1fr), not 1fr. A 1fr track has an automatic minimum of min-content,
     so the 390px phone inside sets the track's floor and pushes the page wider
     than the screen — which is exactly the horizontal scroll this used to have. */
  .hero-grid { grid-template-columns: minmax(0, 1fr); gap: var(--s7); }
  .hero-grid .pf { margin-inline: auto; }
}

/* On a phone the hero is a single column, so everything in the copy block is
   something to scroll past before the product shot. The three facts are the
   first thing to go: each one is restated by the lede, the feature list or the
   install page, so nothing is only said here. Dropping them also drops the
   rule above them, which was drawing a divider across a stacked layout.

   The gap under the shot is the second. Stacked, the hero's bottom padding and
   the next section's top padding stack into two full section breaks between the
   product shot and the copy explaining it. Halved, they read as one scroll. */
@media (max-width: 560px) {
  .hero-facts { display: none; }
  .hero { padding-block-end: var(--s6); }
  .hero + .section { padding-block-start: var(--s7); }
}

/* --- 9. Product visuals -------------------------------------------------- */
/* The bot's output, in a frame. The cards themselves are generated from the
   real embed builders by tools/gen_embeds.py, so what follows is only the
   presentation around them: this section is the flat desktop frame, 9b is the
   phone. Every frame is one exchange — channel bar, one coach message, one
   Tempo reply. */

.dsc {
  background: var(--bg-inset);
  border: 1px solid var(--line-mid);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 28px 60px -30px rgba(0,0,0,0.85);
}
.dsc-bar {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px;
  background: var(--bg-panel);
  border-bottom: 1px solid var(--line);
}
.dsc-bar .hash { color: var(--ink-3); font-weight: 600; }
.dsc-bar .ch {
  font-size: 0.875rem; font-weight: 600; color: var(--ink-2);
}
.dsc-bar .dsc-meta {
  margin-left: auto;
  font-family: var(--font-mono); font-size: 0.6875rem;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-3);
}
.dsc-body { padding: var(--s5) var(--s4) var(--s5); display: grid; gap: var(--s5); }

.dsc-msg { display: grid; grid-template-columns: 38px minmax(0,1fr); gap: 13px; }
.dsc-av {
  width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--font-display); font-weight: 700; font-size: 0.8rem;
  color: #fff; background: #3a4a60; letter-spacing: 0;
}
.dsc-av-bot { background: linear-gradient(150deg, var(--accent), var(--accent-deep)); color: var(--on-accent); }
.dsc-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin-bottom: 4px; }
.dsc-name { font-weight: 600; font-size: 0.9375rem; color: var(--ink); }
.dsc-name-bot { color: var(--accent-hi); }
.dsc-bot {
  font-family: var(--font-mono); font-size: 0.5625rem; letter-spacing: 0.1em;
  background: var(--accent); color: var(--on-accent);
  padding: 2px 5px; border-radius: 4px; font-weight: 600; text-transform: uppercase;
  position: relative; top: -1px;
}
.dsc-time { font-size: 0.6875rem; color: var(--ink-3); }
.dsc-text { font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.5; }
.dsc-text + .dsc-text { margin-top: 0.45em; }

/* Discord's markdown headings, which the bot uses more than it uses embeds.
   The draft reveal, the on-the-clock ping and the weekly schedule are all sent
   as plain message content, because a mention only pings — and so only resolves
   to a name for every viewer — when it is in the message body rather than in an
   embed. So these are not decoration: they are the reason those posts work. */
.dsc-h1 {
  font-family: var(--font-display); font-weight: 700;
  font-size: 1.4em; line-height: 1.25; color: var(--ink);
  margin-top: 0.5em;
}
.dsc-h3 {
  font-weight: 700; font-size: var(--fs-sm); line-height: 1.3; color: var(--ink);
  margin-top: 0.6em;
}
.dsc-text:first-child, .dsc-h1:first-child, .dsc-h3:first-child { margin-top: 0; }

/* Two things Discord renders rather than stores, and the bot leans on both.
   Mentions resolve to the coach's nickname, which is why the draft ping works;
   timestamps resolve to each viewer's own timezone, which is the whole point of
   the schedule post. Drawing them as plain text loses the feature. */
/* A mention takes the member's role colour, and in a Tempo league the role is
   the team, so this is the same green a coach's name is. --rc is written onto
   the span by tools/gen_embeds.py from the team's seeded primary; the wash
   behind it is derived from that one value here rather than set alongside it,
   so the two cannot drift apart. The fallback covers a mention of somebody
   holding no team role. */
.dsc-mention {
  --rc: var(--accent-hi);
  background: color-mix(in srgb, var(--rc) 16%, transparent);
  color: var(--rc);
  padding: 1px 5px; border-radius: 4px; font-weight: 500;
}
.dsc-ts { background: rgba(255,255,255,0.07); border-radius: 4px; padding: 1px 5px; }
/* Inline code. Monospaced and space-preserving, because the bot pads its pick
   numbers to two columns inside backticks and the column only lines up if both
   of those hold. */
.dsc-code {
  background: rgba(255,255,255,0.07); border-radius: 4px; padding: 1px 5px;
  font-family: var(--font-mono); font-size: 0.9em; white-space: pre;
}

.dsc-embed {
  border-left: 3px solid var(--accent);
  background: var(--bg-panel);
  border-radius: 5px;
  padding: 14px 16px;
  max-width: 440px;
}
.dsc-embed-ok   { border-left-color: var(--ok); }
.dsc-embed-warn { border-left-color: var(--warn); }
.dsc-embed-bad  { border-left-color: var(--bad); }
.dsc-embed-title {
  font-family: var(--font-display); font-weight: 800;
  font-size: 0.8125rem; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink); margin-bottom: 6px;
}
.dsc-embed-desc { font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.5; }
/* set_author() — the league name above the title, real on the open-teams and
   rivalry boards. It is what makes a board feel like it belongs to your league
   rather than to the bot. */
.dsc-author {
  font-size: 0.75rem; font-weight: 600; color: var(--ink-2); margin-bottom: 7px;
}
.dsc-fields {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 12px 18px; margin-top: 13px;
}
.dsc-field-name {
  font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 3px;
}
.dsc-field-val { font-size: 0.875rem; color: var(--ink); font-weight: 500; line-height: 1.35; }
/* Discord's inline=False fields span the row. The real /upgrade card uses one
   for the upgrade itself, which is what makes it read as the card's headline. */
.dsc-field-wide { grid-column: 1 / -1; }
.dsc-foot {
  margin-top: 13px; padding-top: 10px; border-top: 1px solid var(--line);
  font-size: 0.6875rem; color: var(--ink-3);
}
/* Discord's own footer on an ephemeral reply. Worth drawing: the activation
   panel is private, and that is the reassurance a commissioner wants before
   typing a billing command into a channel 30 people can read. */
.dsc-only-you { margin-top: 9px; font-size: 0.6875rem; color: var(--ink-3); }

.dsc-btns { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; max-width: 440px; }
.dsc-btn {
  font-size: 0.8125rem; font-weight: 500;
  padding: 8px 14px; border-radius: 6px;
  background: #3d4657; color: #fff;
}
.dsc-btn-primary { background: var(--info); }
.dsc-btn-ok      { background: var(--ok); color: #06210f; font-weight: 600; }
.dsc-btn-bad     { background: var(--bad); }
.dsc-btn-off     { background: transparent; border: 1px solid var(--line-mid); color: var(--ink-3); }
/* Discord marks a link button with a small external-link glyph. Worth keeping:
   it is the visual cue that pressing this leaves the app, which on the checkout
   button is the single most useful thing the card says. */
.dsc-btn-link::after { content: " \2197"; opacity: 0.7; }

/* The frame is drawn at the size a desktop Discord client renders it, and on a
   phone that is a 600px hero sitting under an already tall text column: taller
   than the seed board and the lottery board it is supposed to sit alongside as
   a peer. The padding, the avatar and the button chrome are all client
   furniture rather than content, so they shrink first. What is left is the
   exchange itself, which is the only part carrying the argument.

   Keyed off the container rather than the viewport, because "is this card
   narrow" and "is this browser narrow" stop being the same question the moment
   a card is nested. A viewport query is why the old phone poster rendered its
   mock at desktop scale inside a 279px frame: the browser was wide, so the
   phone rules never fired. Containment is on .dsc and on .pf-screen, so a card
   shrinks whenever the thing holding it is small, wherever that is. */
.dsc { container-type: inline-size; }

@container (max-width: 560px) {
  .dsc-bar { padding: 9px 13px; }
  .dsc-body { padding: 16px 13px; gap: 16px; }
  .dsc-msg { grid-template-columns: 30px minmax(0,1fr); gap: 10px; }
  .dsc-av { width: 30px; height: 30px; font-size: 0.7rem; }
  .dsc-embed { padding: 11px 13px; }
  .dsc-fields { gap: 9px 14px; }
  .dsc-field-val { font-size: 0.8125rem; }
  .dsc-foot { margin-top: 10px; padding-top: 8px; }
  .dsc-btns { margin-top: 9px; }
  .dsc-btn { padding: 7px 11px; font-size: 0.75rem; }
  /* An embed field the message header already answers. Discord's own cards
     repeat the coach because the card is read out of context in a log channel;
     directly under the author line, on a narrow column, it is the row that
     costs the most height and says the least. Marked in the markup rather than
     matched by position, so a card that genuinely needs every field keeps them. */
  .dsc-field-tuck { display: none; }
}

/* --- 9b. Phone frame ------------------------------------------------------
   The bot's output as a coach actually meets it: on a phone, in the Discord
   mobile client, at 2am. The desktop .dsc frame is the same cards in the same
   CSS; this is the presentation layer around them.

   Discord's own mobile palette rather than ours. The point of the frame is that
   it reads as a screenshot, and a Discord that is Tempo-navy reads as a mockup.
   The cards inside keep their real embed colours, which is the whole argument. */

.pf {
  width: 390px; max-width: 100%; padding: 11px; border-radius: 54px;
  background: linear-gradient(160deg, #3a3f4a, #15181e 40%, #0b0d12);
  box-shadow: 0 40px 90px -30px rgba(0,0,0,0.9), 0 0 0 1px rgba(255,255,255,0.06);
}
.pf-screen {
  position: relative; border-radius: 44px; overflow: hidden;
  display: flex; flex-direction: column;
  background: var(--dc-body);
  /* So the cards inside answer to the frame's width, not the browser's. */
  container-type: inline-size;
  /* No height, only a floor. The frame grows to whatever the generated cards
     need, because the alternative — a number per scene, set by hand — is a
     number guessed against one column width. The same card is 390px wide here
     and 335px on a phone, where it wraps taller: the hand-set heights clipped
     the top of the trade card on mobile and left the draft frame a third empty
     on desktop. This floor is the only constant left, and all it does is stop a
     one-message scene from rendering as a stub. */
  min-height: 480px;
}
.pf-island {
  position: absolute; top: 9px; left: 50%; transform: translateX(-50%);
  width: 104px; height: 28px; border-radius: 16px; background: #000; z-index: 5;
}

/* iOS status bar. Cheap, and its absence is the first thing that makes a phone
   mock look like a div with rounded corners. */
.pf-ios {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 26px 6px;
  font: 600 14px/1 var(--font-body); color: #fff; background: var(--dc-chrome);
}
.pf-sig { display: flex; gap: 4px; align-items: flex-end; }
.pf-sig i { display: block; width: 3px; background: #fff; border-radius: 1px; }
.pf-batt {
  width: 23px; height: 12px; margin-left: 4px; padding: 1.5px;
  border: 1px solid rgba(255,255,255,0.5); border-radius: 3.5px;
}
.pf-batt i { display: block; height: 100%; width: 72%; background: #fff; border-radius: 1.5px; }

/* Channel header. Padded off the left edge rather than hard against it, which
   is what the real client does and what stops the back chevron and the channel
   name reading as one run-on string. */
.pf-head {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 16px 10px 14px; background: var(--dc-chrome);
}
.pf-back { color: #b5bac1; font-size: 22px; line-height: 1; }
.pf-ch {
  display: flex; align-items: center; gap: 3px;
  font: 600 16px/1.2 var(--font-body); color: #f2f3f5;
}
.pf-ch i { color: #80848e; font-style: normal; font-weight: 400; font-size: 19px; }
.pf-tools { margin-left: auto; display: flex; align-items: center; color: #b5bac1; }

/* Message list. Bottom-aligned, because a channel you have just opened is
   scrolled to the newest message, not the oldest. The top padding is a fixed
   strip of empty channel above the oldest message shown: now that the frame is
   sized by its contents, there is no leftover height to supply that gap, and
   without it the first message sits flush against the channel header. */
.pf-msgs {
  flex: 1; overflow: hidden; padding: 30px 12px 0;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 15px;
  background: var(--dc-body);
}
.pf-msgs .dsc-name { color: #f2f3f5; font-size: 15px; }
.pf-msgs .dsc-time { color: var(--dc-dim); }
.pf-msgs .dsc-text { color: #dbdee1; }
.pf-msgs .dsc-h1, .pf-msgs .dsc-h3 { color: #f2f3f5; }
.pf-msgs .dsc-embed { background: var(--dc-card); max-width: 100%; }
.pf-msgs .dsc-embed-title { color: #f2f3f5; }
.pf-msgs .dsc-embed-desc,
.pf-msgs .dsc-field-val { color: #dbdee1; }
.pf-msgs .dsc-author { color: #f2f3f5; }
.pf-msgs .dsc-field-name { color: var(--dc-dim); }
.pf-msgs .dsc-foot { border-top-color: rgba(255,255,255,0.06); color: var(--dc-dim); }
.pf-msgs .dsc-ts,
.pf-msgs .dsc-code { background: rgba(0,0,0,0.35); }

.pf-sep {
  display: flex; align-items: center; gap: 10px;
  color: var(--dc-dim); font: 600 11px/1 var(--font-body);
}
.pf-sep::before, .pf-sep::after {
  content: ""; flex: 1; height: 1px; background: rgba(255,255,255,0.08);
}

.pf-input {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px 6px; background: var(--dc-body);
}
.pf-input span {
  flex: 1; padding: 9px 14px; border-radius: 20px;
  background: #383a40; color: #6d6f78; font: 400 14px/1 var(--font-body);
}
.pf-input i { color: #b5bac1; font-style: normal; font-size: 19px; }
.pf-home {
  height: 5px; width: 134px; margin: 8px auto 9px;
  border-radius: 3px; background: rgba(255,255,255,0.5);
}

/* The two-column split that carries a phone frame beside its copy.

   The phone track is a fixed 390px, not auto. An auto track sizes to content,
   and the content is width:390px;max-width:100% — the 100% then resolves
   against a track that is still being sized, which settles ~22% narrow
   (measured: 234px against an intended 300px, back when the track was 300).
   Naming the width breaks the circularity. */
.scene-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 390px;
  gap: clamp(32px, 5vw, 76px);
  align-items: center;
}
/* Reversed: phone on the left, copy on the right. Alternating down a page
   stops a run of these reading as one long list. */
.scene-split.is-flipped { grid-template-columns: 390px minmax(0, 1fr); }
.scene-split.is-flipped > .pf { order: -1; }

/* Centred with margin, not justify-items. justify-items:center sizes the grid
   item to fit-content, so the item's width depends on the phone while the
   phone's max-width:100% depends on the item — the same circularity as the
   auto track above, collapsing it to 234px again. Leaving the item stretched
   gives 100% a real value to resolve against. */
@media (max-width: 860px) {
  .scene-split,
  .scene-split.is-flipped { grid-template-columns: minmax(0, 1fr); }
  .scene-split .pf { margin-inline: auto; }
  /* On one column the copy leads and the frame follows, flipped or not. */
  .scene-split.is-flipped > .pf { order: 0; }
}

/* --- 10. Product split (homepage centrepiece) ---------------------------- */

.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
/* Football is pinned to blue even if the page around it is a Hoops page, so the
   two panels always read as two products rather than as one colour scheme.

   The :not() matters and is not decoration. This block used to apply to every
   .split-panel, including the Hoops one — and since it sits ~700 lines below
   the .is-hoops token override at equal specificity, it won it outright. The
   Hoops panel was quietly repainting itself Football blue: the chip, the word
   "Hoops", the list bullets, the install button and the tint behind all of it.
   Excluding the panel that asked for amber is what lets the override survive. */
.split-panel:not(.is-hoops) {
  --accent:      var(--blue);
  --accent-hi:   var(--blue-hi);
  --accent-tint: rgba(46,155,255,0.12);
  --on-accent:   #04101F;
}
.split-panel {
  position: relative;
  padding: clamp(40px, 5.5vw, 76px) clamp(24px, 4vw, 60px);
  display: flex; flex-direction: column;
  isolation: isolate;
  overflow: hidden;
  transition: background 0.25s;
}
.split-panel + .split-panel { border-left: 1px solid var(--line); }
/* Full-bleed, but the copy still lands on the same grid as every .wrap above. */
.split-panel:first-child { padding-left: calc(max(0px, (100vw - var(--max)) / 2) + var(--gutter)); }
.split-panel:last-child { padding-right: calc(max(0px, (100vw - var(--max)) / 2) + var(--gutter)); }
.split-panel::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: radial-gradient(120% 90% at 50% 0%, var(--accent-tint), transparent 70%);
  opacity: 0.85;
}
.split-panel:hover { background: rgba(255,255,255,0.015); }
/* The availability chip never breaks mid-phrase — if the row runs out of room
   it takes its own line instead. */
.split-panel .split-top { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s3) var(--s4); margin-bottom: var(--s6); }
.split-panel .split-top .chip { white-space: nowrap; }
.split-panel h3 {
  font-size: clamp(2rem, 3.6vw, 2.9rem);
  margin-bottom: var(--s4);
}
.split-panel h3 .sport { display: block; color: var(--accent); font-style: italic; }
.split-panel .split-desc { color: var(--ink-2); max-width: 34ch; margin-bottom: var(--s5); }
.split-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 9px var(--s4);
  margin-bottom: var(--s6);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}
.split-list li {
  font-size: 0.875rem; color: var(--ink-2);
  position: relative; padding-left: 21px;
}
.split-list li::before {
  content: ""; position: absolute; left: 0; top: 0.68em;
  width: 12px; height: 2px;
  background: var(--accent); opacity: 0.8;
}
.split-panel .btn-row { margin-top: auto; }

/* Price sits above the buttons and takes over the job of pushing them down,
   so both panels line their buttons up whatever the list length. */
.split-price {
  margin-top: auto;
  padding-top: var(--s5);
  font-size: var(--fs-sm);
  color: var(--ink-2);
}
.split-price + .btn-row { margin-top: var(--s4); }
.split-panel.is-soon { background: rgba(255,255,255,0.012); }

@media (max-width: 860px) {
  .split { grid-template-columns: 1fr; }
  .split-panel + .split-panel { border-left: 0; border-top: 1px solid var(--line); }
}
@media (max-width: 560px) {
  /* Availability leads, then the games it covers — both wrap badly side by side. */
  .split-panel .split-top {
    flex-direction: column-reverse;
    align-items: flex-start;
    gap: var(--s3);
  }
}

/* --- 11. Benefit row ----------------------------------------------------- */

.bens { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; }
.bens > li { padding: var(--s5) var(--s5) var(--s5) 0; }
.bens > li + li { border-left: 1px solid var(--line); padding-left: var(--s5); }
.bens h3 { font-size: 1.0625rem; letter-spacing: -0.01em; margin-bottom: 9px; }
.bens p { font-size: var(--fs-sm); color: var(--ink-2); }
.bens .num {
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.16em;
  color: var(--accent); display: block; margin-bottom: var(--s4);
}
@media (max-width: 760px) {
  .bens { grid-template-columns: 1fr; }
  .bens > li { padding: var(--s5) 0; border-top: 1px solid var(--line); }
  .bens > li + li { border-left: 0; padding-left: 0; }
}

/* --- 12. Feature rows (Football / Hoops pages) --------------------------- */

.frow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 390px;
  gap: clamp(32px, 5vw, 76px);
  align-items: center;
  padding-block: clamp(48px, 6.5vw, 92px);
}
.frow + .frow { border-top: 1px solid var(--line); }
/* Named width for the same reason as .scene-split: the phone inside is
   width:390px;max-width:100%, and an auto track sized to that content makes
   the 100% resolve against a track that is still being sized. */
.frow-rev { grid-template-columns: 390px minmax(0, 1fr); }
.frow-rev .frow-copy { order: 2; }
.frow-rev .frow-vis  { order: 1; }
.frow-vis { display: flex; justify-content: center; }
.frow-num {
  font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.18em;
  color: var(--accent); margin-bottom: var(--s4); display: block;
}
.frow h2 { font-size: var(--fs-h2); margin-bottom: var(--s4); }
.frow-copy p { color: var(--ink-2); max-width: 46ch; }
.frow-points { margin-top: var(--s5); display: grid; gap: 10px; }
.frow-points li {
  font-size: var(--fs-sm); color: var(--ink-2);
  position: relative; padding-left: 19px;
}
.frow-points li::before {
  content: ""; position: absolute; left: 0; top: 0.6em;
  width: 6px; height: 6px;
  background: var(--accent); border-radius: 1px; transform: rotate(45deg);
}
.frow-points strong { color: var(--ink); font-weight: 600; }

@media (max-width: 900px) {
  .frow, .frow-rev { grid-template-columns: minmax(0, 1fr); gap: var(--s6); }
  /* Copy always leads on mobile, regardless of desktop alternation. */
  .frow-rev .frow-copy { order: 1; }
  .frow-rev .frow-vis  { order: 2; }
}

/* --- 13. Feature catalog (Features page) --------------------------------- */

.toggle {
  display: inline-flex;
  padding: 5px;
  gap: 4px;
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: 12px;
}
.toggle button {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 10px 20px;
  border-radius: 8px;
  font-size: var(--fs-sm); font-weight: 600;
  color: var(--ink-3);
  transition: background 0.16s, color 0.16s;
}
.toggle button:hover { color: var(--ink-2); }
.toggle button[aria-selected="true"] { background: var(--accent); color: var(--on-accent); }
.toggle button .t-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }

.cat { padding-block: clamp(36px, 4.5vw, 60px); }
.cat + .cat { border-top: 1px solid var(--line); }
.cat-grid {
  display: grid;
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 60px);
}
.cat h2 { font-size: var(--fs-h3); margin-bottom: 10px; }
.cat-desc { font-size: var(--fs-sm); color: var(--ink-3); }
.flist { display: grid; grid-template-columns: repeat(auto-fill, minmax(258px, 1fr)); gap: 2px; }
.fitem {
  padding: var(--s4) var(--s4) var(--s4) 0;
  border-top: 1px solid var(--line);
}
.fitem h3 {
  font-size: 0.9375rem; font-family: var(--font-body); font-weight: 600;
  letter-spacing: 0; margin-bottom: 5px;
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
}
.fitem p { font-size: 0.875rem; color: var(--ink-3); line-height: 1.5; }
.fitem .cmd { font-size: 0.75rem; }
.cmd-row { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 9px; }
.fitem-wide { grid-column: 1 / -1; }
@media (max-width: 820px) {
  .cat-grid { grid-template-columns: 1fr; gap: var(--s5); }
}

/* --- 14. Steps (Install page) -------------------------------------------- */

/* The same two-column row the product pages use: what you do on the left, what
   you will see on the right. A third gutter column just for the index left the
   copy capped well short of its own column and squeezed the specimens into
   300px, so the index sits above the heading instead — still the one mono
   numbering voice the product rows use. */
.steps { display: grid; }
.step {
  display: grid;
  /* Named 390px for the same circularity reason as .frow and .scene-split. */
  grid-template-columns: minmax(0, 1fr) 390px;
  gap: clamp(32px, 4.5vw, 64px);
  padding-block: clamp(40px, 5vw, 72px);
  border-top: 1px solid var(--line);
  align-items: center;
}
.step:last-child { border-bottom: 1px solid var(--line); }
.step-n {
  font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.18em;
  color: var(--accent); margin-bottom: var(--s4); display: block;
}
.step h2 { font-size: var(--fs-h3); margin-bottom: var(--s4); }
.step h2 .cmd { font-size: 0.78em; }
.step-copy p { font-size: var(--fs-sm); color: var(--ink-2); max-width: 54ch; }
.step-copy p + p { margin-top: var(--s4); }
/* Items stay stretched — a fit-content track is what collapses a phone whose
   width is 390px;max-width:100% — and only the frame is centred, by margin.
   The track is named minmax(0,1fr) rather than left implicit, because an auto
   track sizes to max-content and the phone's max-content is a hard 390px. */
.step-side {
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: var(--s5); align-content: center;
}
.step-side .pf { margin-inline: auto; }
.step-note { font-size: 0.8125rem; color: var(--ink-3); margin-top: var(--s3); }

@media (max-width: 900px) {
  .step { grid-template-columns: minmax(0, 1fr); gap: var(--s6); }
}

.callout {
  border: 1px solid var(--line-mid);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
  padding: var(--s4) var(--s5);
  font-size: var(--fs-sm);
  color: var(--ink-2);
}
.callout strong { color: var(--ink); font-weight: 600; }
.callout-todo { border-left-color: var(--ink-3); border-style: dashed; border-left-style: solid; }

/* --- 15. FAQ ------------------------------------------------------------- */

.faq-group + .faq-group { margin-top: clamp(40px, 5vw, 64px); }
.faq-group > h2 {
  font-size: var(--fs-h3);
  padding-bottom: var(--s4);
  margin-bottom: 0;
}
.faq-item { border-top: 1px solid var(--line); }
.faq-item:last-child { border-bottom: 1px solid var(--line); }
.faq-q {
  width: 100%;
  display: flex; align-items: center; gap: var(--s4);
  text-align: left;
  padding: var(--s5) 0;
  font-size: 1.0625rem; font-weight: 600;
  color: var(--ink);
  line-height: 1.4;
}
.faq-q:hover { color: var(--accent-hi); }
.faq-icon {
  margin-left: auto; flex: none;
  width: 26px; height: 26px; border-radius: 50%;
  border: 1px solid var(--line-hi);
  display: grid; place-items: center;
  color: var(--ink-2);
  transition: transform 0.22s ease, border-color 0.18s, color 0.18s;
}
.faq-q[aria-expanded="true"] .faq-icon { transform: rotate(45deg); border-color: var(--accent); color: var(--accent); }
.faq-a {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows 0.26s ease;
}
.faq-a > div { overflow: hidden; }
.faq-item.is-open .faq-a { grid-template-rows: 1fr; }
.faq-a p { color: var(--ink-2); font-size: var(--fs-sm); padding-bottom: var(--s5); max-width: 68ch; }
.faq-a p + p { padding-top: var(--s3); }

/* --- 16. CTA band + footer ----------------------------------------------- */

.cta-band {
  text-align: center;
  padding-block: clamp(64px, 8vw, 112px);
  background:
    radial-gradient(70% 130% at 50% 0%, var(--accent-tint), transparent 68%),
    var(--bg-raise);
  border-top: 1px solid var(--line);
}
.cta-band h2 { font-size: var(--fs-h1); margin-bottom: var(--s4); }
.cta-band .lede { margin-inline: auto; }
.cta-band .btn-row { justify-content: center; margin-top: var(--s6); }

.site-footer { border-top: 1px solid var(--line); padding-block: var(--s8) var(--s6); background: var(--bg); }
.foot-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(0, 1fr));
  gap: var(--s6);
  padding-bottom: var(--s7);
}
.foot-grid h2 {
  font-family: var(--font-mono); font-weight: 500;
  font-size: var(--fs-label); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: var(--s4);
}
.foot-grid li + li { margin-top: 10px; }
.foot-grid a { font-size: var(--fs-sm); color: var(--ink-2); }
.foot-grid a:hover { color: var(--ink); }
.foot-blurb { font-size: var(--fs-sm); color: var(--ink-3); max-width: 34ch; margin-top: var(--s4); }
.foot-bar {
  border-top: 1px solid var(--line);
  padding-top: var(--s5);
  display: flex; flex-wrap: wrap; gap: var(--s4);
  align-items: center; justify-content: space-between;
  font-size: 0.8125rem; color: var(--ink-3);
}
@media (max-width: 760px) {
  .foot-grid { grid-template-columns: 1fr 1fr; gap: var(--s6) var(--s5); }
  .foot-grid > *:first-child { grid-column: 1 / -1; }
}

/* --- 17. Page intro band ------------------------------------------------- */

.page-hero {
  padding-block: clamp(48px, 6vw, 88px) clamp(40px, 5vw, 64px);
  position: relative;
  overflow: hidden;
}
.page-hero::before {
  content: "";
  position: absolute; top: -260px; left: -120px;
  width: 780px; height: 560px;
  background: radial-gradient(ellipse at center, var(--accent-tint), transparent 64%);
  pointer-events: none;
}
.page-hero-inner { position: relative; }
.page-hero h1 { font-size: var(--fs-h1); margin-bottom: var(--s5); max-width: 18ch; }

/* Product pages open the way the home page does: the claim on the left, the
   thing itself on the right. Left as one column, the headline wraps to four
   lines and the empty half of the screen reads as an image that failed to
   load. The columns are even rather than the home page's 1:1.02, because the
   product h1 is --fs-h1 and does not need the extra room. */
.page-hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(36px, 5vw, 68px);
  align-items: center;
}
.page-hero-split .callout { max-width: none; }

@media (max-width: 900px) {
  .page-hero-split { grid-template-columns: 1fr; gap: var(--s7); }
}

/* --- 17b. Long-form prose (privacy, terms) -------------------------------- */

/* Legal pages are the one place on the site with a genuine wall of text, so
   they get a measure and a rhythm rather than the display treatment. */
.prose { color: var(--ink-2); }
.prose > * + * { margin-top: var(--s4); }
.prose h2 {
  font-size: var(--fs-h3);
  color: var(--ink);
  margin-top: var(--s8);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}
.prose h3 {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--fs-body);
  letter-spacing: 0;
  line-height: 1.5;
  color: var(--ink);
  margin-top: var(--s6);
}
.prose ul { margin-left: 0; }
.prose li {
  position: relative;
  padding-left: var(--s5);
  font-size: var(--fs-sm);
}
.prose li + li { margin-top: var(--s3); }
.prose li::before {
  content: "";
  position: absolute; left: 2px; top: 0.62em;
  width: 5px; height: 5px;
  background: var(--accent);
  transform: rotate(45deg);
}
.prose strong { color: var(--ink); font-weight: 600; }
.prose a { color: var(--accent-hi); text-decoration: underline; text-underline-offset: 3px; }
.prose code {
  font-family: var(--font-mono); font-size: 0.875em;
  background: var(--bg-panel); border: 1px solid var(--line);
  border-radius: 5px; padding: 1px 6px; color: var(--ink);
}
.prose-updated {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* --- Support form ------------------------------------------------------- */

.support-hero { padding-bottom: clamp(32px, 4vw, 48px); }
.support-hero h1 { max-width: none; }
.support-hero .lede { max-width: 58ch; }
.support-section { padding-top: 0; }

.support-form-card {
  padding: clamp(24px, 4vw, 40px);
  border: 1px solid var(--line-mid);
  border-radius: var(--radius);
  background: linear-gradient(145deg, var(--bg-panel), var(--bg-raise));
  box-shadow: 0 24px 70px -42px rgba(0, 0, 0, 0.9);
}
.support-form-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  margin-bottom: var(--s6);
}
.support-form-head h2 { font-size: var(--fs-h3); }
.support-form-head p { color: var(--ink-3); font-size: var(--fs-sm); }

.support-form { position: relative; }
.support-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s5);
}
.support-field {
  display: grid;
  gap: var(--s2);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 600;
}
.support-field-wide { grid-column: 1 / -1; }
.support-field small {
  color: var(--ink-3);
  font-size: 0.75rem;
  font-weight: 500;
  margin-left: 4px;
}
.support-field input,
.support-field select,
.support-field textarea {
  width: 100%;
  border: 1px solid var(--line-mid);
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
  color: var(--ink);
  font: 400 var(--fs-sm)/1.4 var(--font-body);
  outline: none;
  transition: border-color 0.16s, box-shadow 0.16s, background 0.16s;
}
.support-field input,
.support-field select { height: 46px; padding: 0 13px; }
.support-field textarea { min-height: 138px; padding: 12px 13px; resize: vertical; }
.support-field select { color-scheme: dark; }
.support-field input:hover,
.support-field select:hover,
.support-field textarea:hover { border-color: var(--line-hi); }
.support-field input:focus,
.support-field select:focus,
.support-field textarea:focus {
  border-color: var(--accent);
  background: var(--bg);
  box-shadow: 0 0 0 3px var(--accent-tint);
}

.support-form-actions {
  display: flex;
  align-items: center;
  gap: var(--s4);
  margin-top: var(--s5);
}
.support-form-actions .btn { flex: none; }
.support-form-actions .btn:disabled { cursor: wait; opacity: 0.68; }
.support-form-status {
  color: var(--ink-3);
  font-size: var(--fs-sm);
  line-height: 1.45;
}
.support-form-status[data-state="success"] { color: var(--ok); }
.support-form-status[data-state="error"] { color: #FF9188; }
.support-form-noscript {
  margin-top: var(--s4);
  color: var(--ink-2);
  font-size: var(--fs-sm);
}
.support-form-noscript a { color: var(--accent-hi); text-decoration: underline; }

.support-trap {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.support-email {
  margin-top: var(--s5);
  color: var(--ink-2);
  text-align: center;
}
.support-email strong { color: var(--ink); }
.support-email a,
.support-brief a {
  color: var(--accent-hi);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.support-briefs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s6);
  margin-top: clamp(48px, 7vw, 72px);
}
.support-brief {
  border-top: 1px solid var(--line-mid);
  padding-top: var(--s5);
}
.support-brief h2 { font-size: var(--fs-h3); margin-bottom: var(--s3); }
.support-brief p { color: var(--ink-2); font-size: var(--fs-sm); }

@media (max-width: 640px) {
  .support-form-head { display: block; }
  .support-form-head p { margin-top: var(--s2); }
  .support-form-grid,
  .support-briefs { grid-template-columns: 1fr; }
  .support-field-wide { grid-column: auto; }
  .support-form-actions { align-items: stretch; flex-direction: column; }
  .support-form-actions .btn { width: 100%; }
}

/* --- 17c. Lottery board (Hoops hero — PROPOSAL) --------------------------
   Deliberately not a .dsc frame. The Discord chrome appears in every feature
   row further down the Hoops page, so reusing it in the hero leaves the page
   with no opening image of its own — you scroll and meet the same component
   eight more times. This is the same information Tempo posts on lottery
   night, drawn as a results board instead: the one artifact a 2K league
   actually gathers in the server to watch.

   Numbers here match the lottery embed in section 01 of hoops.html on
   purpose — Pistons up from 6th on 9.0% odds. Two specimens of the same
   event disagreeing on the result is the kind of detail people notice. */

.lot {
  background: var(--bg-inset);
  border: 1px solid var(--line-mid);
  border-radius: var(--radius);
  overflow: hidden;
  /* A soft accent bloom rather than a drop shadow: the board is the brightest
     object in the hero and should sit off the page, not on a grey pillow. */
  box-shadow: 0 24px 60px -30px rgba(255, 138, 60, 0.45);
}

.lot-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4);
  padding: 13px var(--s5);
  background: var(--bg-panel);
  border-bottom: 1px solid var(--line);
}
.lot-title {
  font-family: var(--font-display);
  font-size: 0.9375rem; font-weight: 700; letter-spacing: 0.02em;
}
.lot-state {
  font-family: var(--font-mono);
  font-size: 0.625rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent);
  padding: 4px 9px;
  border: 1px solid currentColor;
  border-radius: 999px;
  white-space: nowrap;
}

/* A draft order is genuinely an <ol>, so it stays one. The global reset at
   the top of this file only zeroes <ul>, which left this list with a 40px
   indent and decimal markers hidden by the board's overflow:hidden — the
   indent then squeezed the team-name track until it wrapped on mobile. */
.lot-rows { list-style: none; margin: 0; padding: var(--s2) 0; }
/* Row padding balances the board against the hero's text column, the same job
   --seed-chip-pad does on Football, and it moves the same way: the text column
   grows as the viewport narrows, so the board has to grow with it. All 14 rows
   at 10px sit +15 against the text at 1024 but -105 at 1280, where the copy
   stops wrapping. 6px above 1080 closes that. Board minus text, measured:
       width    6px    10px
       1024    +127     +15
       1280      +7    -105
   Re-measure both columns after changing either number. */
.lot { --lot-row-pad: 6px; }
.lot-row {
  position: relative;
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--s4);
  padding: var(--lot-row-pad) var(--s5);
}
@media (min-width: 901px) and (max-width: 1080px) {
  .lot { --lot-row-pad: 10px; }
}
/* Hairline between rows, inset to the board's padding so it reads as a
   table rule and not as a full-bleed divider. */
.lot-row + .lot-row::before {
  content: ""; position: absolute; top: 0; left: var(--s5); right: var(--s5);
  height: 1px; background: var(--line);
}

.lot-pick {
  font-family: var(--font-mono); font-size: 0.8125rem;
  color: var(--ink-3); font-variant-numeric: tabular-nums;
}
.lot-name  { font-size: var(--fs-sm); font-weight: 600; }
.lot-coach { font-size: 0.75rem; color: var(--ink-3); margin-left: 7px; }
/* Right-aligned, not just tabular: an auto grid track sizes to "14.0%", so
   left-aligned "9.0%" would hang a character short and the decimal column
   would read ragged down the board. */
.lot-odds {
  font-family: var(--font-mono); font-size: 0.75rem;
  color: var(--ink-3); font-variant-numeric: tabular-nums;
  min-width: 44px; text-align: right;
}
/* Movement is amber for a jump and grey for a fall — no third colour, and
   never colour alone: the sign carries the meaning on its own. */
.lot-move {
  font-family: var(--font-mono); font-size: 0.6875rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3); min-width: 28px; text-align: right;
}
.lot-move-up { color: var(--accent-hi); }

/* Hoops draws an equal-odds order, so its board has no odds or movement to
   show — /lottery run posts a numbered list of entrants and nothing else.
   Two tracks, or the two unused auto tracks leave a gap of dead space on the
   right. The odds/movement columns above still serve the design lab. */
.lot-order .lot-row { grid-template-columns: 30px minmax(0, 1fr); }

/* The one row the whole league is watching. */
.lot-row-win { background: linear-gradient(90deg, var(--accent-tint), transparent 78%); }
.lot-row-win::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 2px; background: var(--accent);
}
.lot-row-win .lot-pick { color: var(--accent); }
.lot-row-win .lot-name { font-size: var(--fs-body); }

.lot-foot {
  padding: 11px var(--s5) 13px;
  border-top: 1px solid var(--line);
  background: var(--bg-panel);
  font-size: 0.6875rem; color: var(--ink-3);
}

/* All 14 rows fit on desktop, so there is nothing to announce there. */
.lot-rest { display: none; }

@media (max-width: 900px) {
  /* The full 14 rows is a 900px board, and on a phone the hero's columns stack
     rather than sit side by side, so that lands on top of an already tall
     text column. Six rows is enough to read it as a drawn order. The rule
     below hides the tail, so the tail has to say so: a list that stops dead
     at 06 under a footer reading "14 entrants" looks broken.
     Compound selector so it outranks the nth-of-type rule above. */
  .lot-row:nth-of-type(n+7) { display: none; }
  .lot-row.lot-rest {
    display: block;
    text-align: center;
    font-family: var(--font-mono); font-size: 0.6875rem;
    letter-spacing: 0.06em; color: var(--ink-3);
  }
}

/* Centring the visual against a tall text column is what drops it below the
   fold-line of the headline. Top-aligned, the board's edge and the eyebrow
   start on the same line and the hero reads as one object. */
.page-hero-split-top { align-items: start; }

/* --- 17d. Seed board (Football hero — PROPOSAL) --------------------------
   Football's counterpart to the lottery board, and deliberately a different
   shape: Hoops gets a ranked table, Football gets a mosaic. Table vs grid is
   a real distinction at a glance, which is the point — the two products
   should not open with the same picture.

   Every --team value is the primary_color straight out of
   bot/db/seeds/teams_data.py. The markup is generated from that file rather
   than hand-picked, so a colour here cannot drift from what the seed
   actually writes into a league. 134 CFB teams, 10 conferences, all with
   both colours populated. */

.seed {
  background: var(--bg-inset);
  border: 1px solid var(--line-mid);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 24px 60px -30px rgba(46, 155, 255, 0.45);
}
.seed-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4);
  padding: 13px var(--s5);
  background: var(--bg-panel);
  border-bottom: 1px solid var(--line);
}
.seed-title {
  font-family: var(--font-display);
  font-size: 0.9375rem; font-weight: 700; letter-spacing: 0.02em;
}
.seed-count {
  font-family: var(--font-mono);
  font-size: 0.625rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-3); white-space: nowrap;
}
.seed-foot {
  padding: 11px var(--s5) 13px;
  border-top: 1px solid var(--line);
  background: var(--bg-panel);
  font-size: 0.6875rem; color: var(--ink-3);
}

/* A1 — labelled grid. Unclaimed teams sit at a low tint so the eye lands on
   the coaches who have actually picked; colour is information, not wallpaper. */
/* Chip density is what balances the hero against the text column, so the three
   knobs live in variables rather than buried across rules. All 134 chips at the
   old 5px vertical padding finished 74px short; each extra pixel buys ~32px of
   board across the 16 rows, and 7px lands within 10px at 1280 and 20px at 1440.
   Re-measure both columns after touching these — the chip count cannot do this
   job any more, the board already shows every team the seed writes.
   Measure with the mono webfont loaded: before it arrives the fallback metrics
   make each chip ~4px shorter, which reads as a 60px error in the wrong
   direction and will talk you into the wrong number. */
.seed { --seed-chip-pad: 7px; --seed-chip-pad-x: 8px; --seed-chip-gap: 6px; }
.seed-grid {
  display: flex; flex-wrap: wrap;
  gap: var(--seed-chip-gap); padding: var(--s5);
}
.seed-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: var(--seed-chip-pad) var(--seed-chip-pad-x);
  border-radius: 6px;
  border: 1px solid var(--team-line);
  background: var(--team-dim);
  font-family: var(--font-mono);
  font-size: 0.6875rem; letter-spacing: 0.06em;
  color: var(--ink-3);
}
.seed-chip-taken {
  color: var(--ink);
  border-color: var(--team);
  background: var(--team-lit);
}
.seed-coach { font-size: 0.625rem; color: var(--ink-2); }
.seed-more {
  display: inline-flex; align-items: center;
  padding: var(--seed-chip-pad) var(--seed-chip-pad-x);
  font-family: var(--font-mono); font-size: 0.6875rem;
  color: var(--ink-3);
  border: 1px dashed var(--line-hi); border-radius: 6px;
}
/* The remainder count has to match however many chips are actually rendered,
   which differs by breakpoint. Both spans ship; one is always hidden. */
.seed-more-sm { display: none; }
/* The hero shows every team, so on desktop there is no remainder to announce.
   The chip still has a job on a phone, where the rule below hides most of the
   grid — hence mobile-only rather than deleted. */
.seed-more-rest { display: none; }

/* Just above the stacking breakpoint the column is narrow enough that the same
   134 chips wrap to 19 rows instead of 16 and the board runs 153px past the
   text. Packing them tighter fixes it without hiding any, which is the point:
   a third visible count would need a third "+N more" span to stay honest.
   The two packings cross over at ~1080 — below it tight wins by a mile, above
   it tight starts undershooting worse than loose overshoots. Board minus text,
   measured with the mono webfont loaded:
       width   loose   tight
       1024    -153      -7
       1060     -89     +54
       1100     -45     +93
       1200      +2    +136
   so tight below 1080, loose above. Worst case is ~70px right at the seam. */
@media (min-width: 901px) and (max-width: 1080px) {
  .seed { --seed-chip-pad: 6px; --seed-chip-pad-x: 6px; --seed-chip-gap: 4px; }
}

@media (max-width: 900px) {
  /* 134 chips is ~28 rows on a phone — a hero you have to scroll past. */
  .seed-chip:nth-of-type(n+33) { display: none; }
  .seed-more-lg { display: none; }
  .seed-more-sm { display: inline; }
  .seed-more-rest { display: inline-flex; }
}

/* A2 — full mosaic. All 134 at once; scale is the whole message, so the
   tiles carry no text and the named coaches move to the footer. */
.seed-mosaic { display: flex; flex-wrap: wrap; gap: 4px; padding: var(--s5); }
/* Tile size is how this board is tuned to the hero, and it works backwards:
   bigger tiles fit fewer per row, so 134 of them need more rows and the panel
   grows. 20px was 17 across and 8 down — a squat strip that read as a progress
   bar rather than a league. 28px stood it up to the text column, until the
   "Works with" strip was added under the hero facts and put ~100px back on
   that side. 34px is the re-measured fit. */
.seed-tile {
  width: 34px; height: 34px; border-radius: 6px;
  background: var(--team); opacity: 0.2;
}
.seed-tile-taken { opacity: 1; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.16); }

@media (max-width: 900px) {
  .seed-tile { width: 22px; height: 22px; }
}

/* --- 17e. "Works with" strip ---------------------------------------------
   Naming the games Tempo is built for, in the hero where the question is
   actually asked. This is nominative use — identifying the titles a league
   runs on — so the copy stays functional ("Works with") and never borrows
   an endorsement it does not have.

   The logos are third-party artwork. Geometry is never touched: --works-h
   sets the optical height and the aspect ratio is whatever the file is.
   What is touched is the background and the ink, because the supplied files
   are all dark artwork on a white canvas and would otherwise arrive as
   white bricks. tools/prep_game_logos.py removes the canvas and emits the
   dark-surface variant each brand publishes anyway — see that file for how
   it decides which whites are page and which are artwork. */

.works {
  margin-top: var(--s5);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}
.works-label {
  font-family: var(--font-mono);
  font-size: 0.625rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: var(--s4);
}
.works-list {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s5) var(--s6);
}
.works-list img {
  --works-h: 30px;
  height: var(--works-h); width: auto;
  /* Full-colour logos can shout louder than the page's own accent. A slight
     hold-back keeps the hierarchy without altering the artwork itself. */
  opacity: 0.9;
}
/* "COLLEGE FOOTBALL" stacks on two lines inside its lockup, so matching its
   box height to single-line MADDEN would set its lettering about a third
   smaller and make it read as the junior partner. The taller box is what
   makes the two marks optically equal. */
.works-list img.works-stacked { --works-h: 44px; }

/* Placeholder shown only until the real files land in assets/games/. It is
   deliberately ugly so it cannot be mistaken for a finished state. */
.works-slot {
  display: inline-flex; align-items: center; justify-content: center;
  height: 30px; padding: 0 var(--s4);
  border: 1px dashed var(--line-hi); border-radius: 6px;
  font-family: var(--font-mono); font-size: 0.625rem;
  color: var(--ink-3); letter-spacing: 0.06em;
}

@media (max-width: 900px) {
  .works-list { gap: var(--s4) var(--s5); }
  .works-list img { --works-h: 26px; }
  .works-list img.works-stacked { --works-h: 38px; }
}

/* --- 17f. Feature index --------------------------------------------------
   The "everything it does" list at the foot of a product page. Hoops used to
   do this with .split-list, which is the index page's comparison-panel
   component wearing three inline overrides — 18 items in one undifferentiated
   run, no way to find the thing you came for. Football had no such list at
   all and went from its last feature section straight to the CTA.

   Grouping is the whole fix. The categories are the same ones features.html
   organises by, so a reader who wants detail already knows where to look, and
   the mono label matches the eyebrow/seed-count/lot-title vocabulary rather
   than inventing a fourth kind of small heading.

   auto-fit rather than auto-fill: with six groups a fixed track count leaves
   an empty column at some widths, and a ragged trailing gap under a list this
   long reads as a missing group.

   The 260px floor is chosen so six groups land as 3x2, not 4+2. Measured at
   the real .wrap content width of 1120px (gap 32px): a 220px floor fits four
   tracks, which strands the last two groups beside two empty cells. The track
   count from auto-fit is floor((w + gap) / (floor + gap)), so 260px holds
   three columns from w=844px up — i.e. across every desktop width the wrap
   produces — and drops to two below that, which is still 3x2. Raising the
   floor further (280-340px) yields the same three columns, so 260px is the
   lowest value that buys the clean matrix and keeps the widest 2-col range. */

.kit {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--s7) var(--s6);
}
.kit-title {
  font-family: var(--font-mono);
  font-size: 0.625rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent);
  padding-bottom: var(--s3);
  margin-bottom: var(--s4);
  border-bottom: 1px solid var(--line);
}
.kit-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.kit-list li { font-size: 0.875rem; color: var(--ink-2); line-height: 1.45; }
/* The catalog is one click away and says considerably more than a noun does,
   so the list ends by pointing at it instead of pretending to be complete. */
.kit-foot { margin-top: var(--s7); font-size: 0.9375rem; color: var(--ink-3); }

@media (max-width: 700px) {
  /* Two columns still fit a phone at this type size and halve the scroll. */
  .kit { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--s6) var(--s5); }
}

/* --- 18. Design lab ------------------------------------------------------ */

.lab-sec { padding-block: var(--s8); border-top: 1px solid var(--line); }
.lab-sec > h2 {
  font-family: var(--font-mono); font-weight: 500;
  font-size: 0.8125rem; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--accent); margin-bottom: var(--s6);
}
.lab-note { font-size: 0.875rem; color: var(--ink-3); margin-bottom: var(--s5); max-width: 62ch; }
.lab-row { display: flex; flex-wrap: wrap; gap: var(--s4); align-items: center; }
.lab-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: var(--s4); }

.spec { border-top: 1px solid var(--line); padding-block: var(--s4); display: grid; grid-template-columns: 132px 1fr; gap: var(--s5); align-items: baseline; }
.spec-meta { font-family: var(--font-mono); font-size: 0.6875rem; color: var(--ink-3); letter-spacing: 0.1em; }

.sw { border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
.sw-chip { height: 76px; }
.sw-meta { padding: 11px 13px; background: var(--bg-panel); }
.sw-name { font-size: 0.8125rem; font-weight: 600; }
.sw-hex { font-family: var(--font-mono); font-size: 0.6875rem; color: var(--ink-3); text-transform: uppercase; }

.space-bar { display: flex; align-items: flex-end; gap: var(--s3); flex-wrap: wrap; }
.space-bar div { text-align: center; }
.space-bar i { display: block; background: var(--accent); height: 26px; border-radius: 2px; }
.space-bar span { font-family: var(--font-mono); font-size: 0.625rem; color: var(--ink-3); display: block; margin-top: 6px; }

.surface-demo { padding: var(--s5); border-radius: var(--radius); border: 1px solid var(--line); }

.vp-frame {
  border: 1px solid var(--line-mid); border-radius: var(--radius);
  overflow: hidden; background: var(--bg-inset);
}
.vp-frame > iframe { display: block; width: 100%; height: 560px; border: 0; background: var(--bg); }
.vp-bar {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  padding: 10px 14px; background: var(--bg-panel); border-bottom: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-3);
}
.vp-bar button { padding: 5px 10px; border: 1px solid var(--line-mid); border-radius: 6px; color: var(--ink-3); }
.vp-bar button[aria-pressed="true"] { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }
.vp-scroll { overflow-x: auto; }

/* --- 19. Utilities ------------------------------------------------------- */

.muted { color: var(--ink-2); }
.dim   { color: var(--ink-3); }
.accent-text { color: var(--accent); }
.center { text-align: center; }
.mt-3 { margin-top: var(--s3); }
.mt-5 { margin-top: var(--s5); }
.mt-6 { margin-top: var(--s6); }
.mt-7 { margin-top: var(--s7); }
.hr { height: 1px; background: var(--line); border: 0; margin: 0; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: var(--gutter); top: -60px; z-index: 100;
  background: var(--accent); color: var(--on-accent);
  padding: 10px 16px; border-radius: 0 0 8px 8px; font-weight: 600; font-size: var(--fs-sm);
  transition: top 0.15s;
}
.skip-link:focus { top: 0; }
