/* ==========================================================================
   BYD x Harmony Auto — "Beat Timmo" campaign stylesheet

   Built from the supplied client design byd-beat-timmo.zip (styles.css +
   six screens, Figma frame 391px), applied to the EXISTING campaign pages.
   The design HTML was visual reference only: every id, class and attribute
   the working JavaScript relies on is kept and styled where it already sits.

   Responsive model — the design's own rules:
     - The screen is a column capped at --screen-max (480px) and centred on
       black; below that it is fluid, with type clamped between its 320px
       minimum and the design value at 391px+.
     - The screen is at least one viewport tall (100dvh) and grows with its
       content, so the PAGE scrolls when a screen is longer than the viewport
       (the full registration form, a long leaderboard). Nothing scrolls
       inside a fixed box, and nothing is clipped on short screens.
     - Stage artwork is background-size: cover (home anchored top centre).
     - Form controls never drop below 16px, or iOS zooms on focus.

   Load-bearing couplings — read the comments before editing:
     1. every [hidden] guard          — see "Hidden-state guards"
     2. -webkit-text-fill-color       — iOS ignores `color` on :disabled
     3. #campaign-loader 0.4s fade    — paired with a 400ms setTimeout in
                                        js/game-loader.js hideLoader()
     4. #rotate-overlay display pair  — game-loader.js toggles .is-visible
     5. #unity-canvas geometry        — set inline by game-loader.js only
     6. #loader-fill width            — set inline (real Unity progress) by
                                        game-loader.js; no animation here
   ========================================================================== */

/* --------------------------------------------------------------------------
   Typefaces — self-hosted, basic-latin subsets (no Google Fonts request).
   The design uses Inter 400–900 and Roboto 900. Inter ships as ONE variable
   file covering 100–900. Inter: SIL OFL 1.1   Roboto: Apache 2.0.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;          /* variable axis — do not split into statics */
  font-display: swap;
  src: url('../assets/fonts/inter-latin.woff2') format('woff2');
}

@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url('../assets/fonts/roboto-900-latin.woff2') format('woff2');
}

/* ==========================================================================
   1. TOKENS  (design styles.css :root, plus campaign-only feedback colours)
   ========================================================================== */
:root {
  --byd-black:      #181818;
  --byd-red:        #e2342b;
  --byd-red-deep:   #d70c19;
  --white:          #ffffff;
  --gray-6:         #f2f2f7;
  --row-text:       #000000;   /* leaderboard rows: black text */
  --row-text-alt:   #090909;
  --field-border:   #9b9b9b;
  --field-placeholder: #5f5f5f;

  /* Leaderboard ramp, 1st place -> 10th (design .lb-row--1 … --10) */
  --rank-1:  #e2342b;
  --rank-2:  #ea3d36;
  --rank-3:  #f45752;
  --rank-4:  #ff8182;
  --rank-5:  #ffa5a5;
  --rank-6:  #fec8c8;
  --rank-7:  #feeae9;
  --rank-8:  #feeae9;
  --rank-9:  #ffffff;
  --rank-10: #f5f5f5;

  /* Feedback — not in the design; light tints so errors read on the dark
     stage, and the design's red for the form-level panel. */
  --error-ink:      #ffd9d6;
  --error-field:    #ffe3e0;

  --screen-max: 480px;
  --pad: clamp(14px, 4.1vw, 18px);

  /* fluid type — min at 320px, design value at 391px+ */
  --nav-h:    36px;                       /* = css/home.css --home-nav-h */
  --fs-small: clamp(11px, 3.1vw, 12px);
  --fs-body:  clamp(15px, 4.1vw, 17px);
  --fs-row:   clamp(14px, 4.1vw, 16px);
  --fs-h2:    clamp(17px, 5.1vw, 20px);
  --fs-h1:    clamp(20px, 6.1vw, 24px);
  --fs-btn:   clamp(13px, 3.6vw, 14px);

  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    Helvetica, Arial, sans-serif;
  --font-display: 'Roboto', 'Inter', sans-serif;

  --t-loader: 0.4s;              /* MUST stay >= the 400ms timeout in
                                    js/game-loader.js hideLoader()           */
}

/* ==========================================================================
   2. RESET
   ========================================================================== */
*,
*::before,
*::after { box-sizing: border-box; }

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

/* A column at least one viewport tall, so the development ribbon
   js/campaign.js inserts before .shell takes its own height and the screen
   fills what is left, instead of pushing the footer below the fold. */
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
  background: #000;
  color: var(--white);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

p, h1, h2 { margin: 0; }

h1, h2 { font-weight: 700; }

ol, ul { margin: 0; padding: 0; list-style: none; }

a { color: inherit; }

button { font: inherit; cursor: pointer; }

/* Content that must reach a screen reader but has no place in the design */
.visually-hidden,
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ==========================================================================
   3. SCREEN SCAFFOLD — nav / stage / footer
   .shell is the design's .screen and .stage its stage.
   ========================================================================== */
.shell {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 1 0 auto;               /* the rest of the viewport, or its content */
  width: 100%;
  max-width: var(--screen-max);
  margin-inline: auto;
  background: var(--byd-black);
}

.stage {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  background-color: var(--byd-black);   /* shows if artwork fails to load */
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
}

/* The blurred night stadium: register, leaderboard, Hall of Fame, legal */
.stage--stadium { background-image: url('../assets/byd/bg-stadium-dark.webp'); }

/* --- Top navigation: HOME | LEADERBOARD | WINNERS --------------------------
   Client (2026-10-02): every screen uses the HOME screen's navigation. These
   rules mirror css/home.css .home-nav value for value (index.html loads
   home.css instead of this file, so the two never meet); tests/frontend
   asserts the rendered metrics match. Locked to the top across the full
   viewport width; .shell is offset by the same height. Like the home nav
   there is no hover or current-page dimming - aria-current / .is-active
   (js/campaign.js) still mark the current page for assistive tech. */
.shell {
  --nav-safe-top: env(safe-area-inset-top, 0px);
  padding-top: calc(var(--nav-h) + var(--nav-safe-top));
}

.topnav {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 12px;
  width: 100%;
  height: calc(var(--nav-h) + var(--nav-safe-top));
  padding: var(--nav-safe-top) max(16px, env(safe-area-inset-right, 0px)) 0 max(16px, env(safe-area-inset-left, 0px));
  background-color: var(--byd-black);
  font-family: 'Inter', Arial, sans-serif;
  font-size: 16px;
  line-height: 20px;
}

.topnav a {
  color: var(--white);
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
}

.topnav a:focus-visible { outline-offset: 2px; }

@media screen and (max-width: 991px) {
  .topnav {
    font-size: 12px;
    line-height: 12px;
  }
}

/* --- Footer: BYD logo left; "Terms & Conditions | Privacy Policy" right ----
   The separator is a sibling of the two links (never inside either), and
   takes their colour and size so the three read as one line. */
.footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex: none;
  padding: 8px var(--pad);
  background: var(--byd-black);
}

/* BYD x Harmony Auto footer logo (client-supplied, 84x16): shown at its
   native size - never scaled up past 1x. */
.footer__logo {
  flex: none;
  width: 84px;
  height: 16px;
  object-fit: contain;
}

.footer__legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  column-gap: 8px;
  row-gap: 2px;
  margin-left: auto;
  font-size: var(--fs-small);
  line-height: 1.3;
}

.footer__legal a {
  color: var(--white);
  text-decoration: none;
  white-space: nowrap;
}

.footer__legal a:hover { text-decoration: underline; }

.footer__sep { color: var(--white); }

/* Thin divider above the footer (client, 2026-10-02): the home footer's line,
   value for value - css/home.css draws it on .home-footer at <= 479px only. */
@media screen and (max-width: 479px) {
  .footer { border-top: 1px solid #424242; }
}

/* ==========================================================================
   4. SHARED PIECES
   ========================================================================== */

/* Button — the design's red pill */
.btn {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  align-self: center;
  flex: none;
  min-height: 30px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: var(--byd-red);
  color: var(--white);
  font: inherit;
  font-size: var(--fs-btn);
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.02em;
  text-align: center;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.btn:hover,
.btn:focus-visible { background: var(--byd-red-deep); }

/* Outline variant — "Try Again" on the leaderboard, secondary overlay action */
.btn--secondary {
  border: 1px solid var(--white);
  background: transparent;
}

.btn--secondary:hover,
.btn--secondary:focus-visible { background: rgba(255, 255, 255, 0.12); }

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: default;
}

/* BYD x Harmony Auto lockup and the "BEAT Timmo" wordmark */
.lockup {
  width: min(230px, 59%);
  height: auto;
  margin-inline: auto;
}

.wordmark {
  width: min(309px, 79%);
  height: auto;
  margin-inline: auto;
}

/* Screen headings */
.h-display {
  max-width: 280px;
  font-family: var(--font-display);
  font-size: var(--fs-h1);
  font-weight: 900;
  text-align: center;
}

/* ==========================================================================
   5. HOME  (design 01 index.html)
   Layer order: artwork, Timmo (1), brand + copy panel (2) — the panel's
   gradient always covers Timmo's feet and the ball.
   ========================================================================== */
.stage--home {
  justify-content: flex-start;
  padding-top: 24px;
  background-image: url('../assets/byd/bg-home.webp');
  background-position: top center;
}

.home-brand {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: 100%;
  padding-inline: var(--pad);
}

.home-timmo {
  position: absolute;
  left: -11%;
  bottom: 2.3%;
  z-index: 1;
  width: 86%;
  height: auto;
  pointer-events: none;
}

.home-copy {
  position: relative;
  z-index: 2;
  margin-top: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  padding: 16px var(--pad);
  background: linear-gradient(180deg, rgba(8, 23, 37, 0) 0%, var(--byd-black) 38.942%);
  text-align: center;
}

.home-copy h1 {
  font-size: var(--fs-h2);
  font-weight: 700;
}

.home-copy p {
  font-size: var(--fs-row);
  line-height: 1.25;
}

.home-copy__text {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 341px;
}

/* ==========================================================================
   6. REGISTER  (design 02 register.html)
   The design shows two fields; the campaign keeps all eight controls in the
   approved order. The screen grows and the page scrolls to reach them.
   ========================================================================== */
.register {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  padding: 32px var(--pad) 24px;
}

.register__brand { width: 100%; }

/* Client request 2026-10-01: lockup-to-wordmark gap cut by at least half.
   Only .register's 24px gap separates them now (was 24px plus
   clamp(24px, 8vh, 62px) here), and everything below moves up with it. */
.register__body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  width: 100%;
}

.register__intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
}

.register__intro p {
  max-width: 333px;
  font-size: var(--fs-body);
  line-height: 1.25;
}

.register__form {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  width: 100%;
}

/* The design sizes controls at 301px and centres them */
.register__fields,
.register__form .form__error {
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: min(301px, 100%);
}

/* --- Field: wrapper for label + control + message ------------------------
   (The design calls the INPUT ".field"; here .field stays the wrapper that
   js/campaign.js locks and hides, and the control is styled inside it.)
   Labels are visually hidden, never deleted — the design shows placeholders. */
.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

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

.field input,
.field select {
  width: 100%;
  min-height: 32px;
  padding: 6px 16px;
  border: 1px solid var(--field-border);
  border-radius: 999px;
  background: var(--white);
  color: #1a1a1a;
  font-family: inherit;
  /* never below 16px, or iOS zooms the page on focus */
  font-size: max(16px, var(--fs-row));
  line-height: 1.25;
}

.field select {
  appearance: none;
  padding-right: 40px;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--field-placeholder) 50%),
    linear-gradient(135deg, var(--field-placeholder) 50%, transparent 50%);
  background-position:
    calc(100% - 22px) 55%,
    calc(100% - 16px) 55%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}

/* The "Nearest dealership" prompt option reads as a placeholder */
.field select:has(option[value=""]:checked) { color: var(--field-placeholder); }

.field input::placeholder {
  color: var(--field-placeholder);
  opacity: 1;                  /* Firefox dims placeholders by default */
}

.field input:focus-visible,
.field select:focus-visible {
  outline: 2px solid var(--byd-red);
  outline-offset: 1px;
}

.field input[aria-invalid="true"],
.field select[aria-invalid="true"] {
  border-color: var(--byd-red);
  background-color: var(--error-field);
}

/* Returning player: the stored values are shown greyed out and locked.
   iOS and Safari IGNORE `color` on :disabled, so -webkit-text-fill-color is
   required, not decorative. */
.field input:disabled,
.field select:disabled {
  background-color: #e8e8ec;
  border-style: dashed;
  color: var(--field-placeholder);
  opacity: 1;
  cursor: not-allowed;
  -webkit-text-fill-color: var(--field-placeholder);
}

/* campaign.js toggles .is-locked on the .field wrapper */
.field.is-locked input,
.field.is-locked select { cursor: not-allowed; }

/* Per-field validation message (also where the server's inappropriate-language message lands) */
.field__error {
  padding: 0 16px;
  color: var(--error-ink);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.35;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}

.field__error:empty { display: none; }

/* Returning player: the server-derived public name, shown read-only */
.register__returning {
  font-size: max(15px, var(--fs-row));
  line-height: 1.35;
  text-align: center;
}

/* --- Consents --------------------------------------------------------------- */
.consents {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 0;
  padding: 0;
  border: 0;
}

.check {
  display: grid;
  grid-template-columns: 20px 1fr;
  column-gap: 10px;
  align-items: start;
  font-size: 13px;
  line-height: 1.4;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.check input[type="checkbox"] {
  width: 18px;
  height: 18px;
  margin: 1px 0 0;
  accent-color: var(--byd-red);
}

.check input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--white);
  outline-offset: 2px;
}

.check input[aria-invalid="true"] {
  outline: 2px solid var(--byd-red);
  outline-offset: 1px;
}

.check label a { color: inherit; text-decoration: underline; }
.check .field__error { grid-column: 2; padding: 0; }

/* Form-level message: server, network and unexpected-response errors */
.form__error {
  padding: 10px 16px;
  border-radius: 12px;
  background: rgba(191, 2, 14, 0.92);
  color: var(--white);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.45;
  text-align: center;
}

/* ==========================================================================
   7. LEADERBOARD  (design 05 leaderboard.html)
   Rows are built by js/campaign.js (createElement + textContent):
   .board__row > .board__rank / __name / __dealership / __score.
   The design shows position, name and score only. The dealership still
   arrives from the API and is still written into its cell by campaign.js,
   but is not displayed (presentation only - see .board__dealership).
   ========================================================================== */
.board {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 32px var(--pad) 12px;
}

.board__list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
}

/* An empty board draws nothing — the status line speaks instead */
.board__list:empty { display: none; }

.board__row {
  display: grid;
  /* position | name | score (the dealership cell is not displayed) */
  grid-template-columns: 27px minmax(0, 1fr) auto;
  column-gap: 16px;
  align-items: center;
  min-height: 32px;
  padding: 6px 16px;
  border-radius: 999px;
  background: var(--rank-10);
  color: var(--row-text);
  font-size: var(--fs-row);
  font-weight: 700;
  line-height: 1.2;
}

/* Rows arrive in rank order, so position and rank agree */
.board__list .board__row:nth-child(1)  { background: var(--rank-1); }
.board__list .board__row:nth-child(2)  { background: var(--rank-2); }
.board__list .board__row:nth-child(3)  { background: var(--rank-3); }
.board__list .board__row:nth-child(4)  { background: var(--rank-4); }
.board__list .board__row:nth-child(5)  { background: var(--rank-5); }
.board__list .board__row:nth-child(6)  { background: var(--rank-6); }
.board__list .board__row:nth-child(7)  { background: var(--rank-7); }
.board__list .board__row:nth-child(8)  { background: var(--rank-8); }
.board__list .board__row:nth-child(9)  { background: var(--rank-9); }
.board__list .board__row:nth-child(n + 10) { background: var(--rank-10); }

.board__rank {
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.board__name { margin-left: 8px; }      /* the design's 24px pos -> name gap */

.board__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Not drawn in the client design: kept in the DOM (the API data and
   campaign.js are unchanged), taken out of the layout here. */
.board__dealership { display: none; }

.board__score {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The player's own row (design .lb-row--you) */
/* Screen-reader column headings: out of the layout entirely */
.board__row--head.visually-hidden {
  min-height: 0;
  padding: 0;
}

.board__row--you {
  width: 100%;
  min-height: 24px;
  background: var(--rank-10);
  color: var(--row-text);
}

/* Loading / empty / error line */
.board__status {
  font-size: var(--fs-row);
  font-weight: 500;
  text-align: center;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}

.board__note {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  text-align: center;
}

.board__note h2 {
  font-size: var(--fs-h2);
  font-weight: 700;
  text-transform: uppercase;
}

.board__note p {
  font-size: var(--fs-row);
  font-weight: 500;
  line-height: 1.25;
}

.board__cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: auto;
  padding-block: 12px;
}

/* FRIENDS OF TIMMO under PLAY AGAIN: the shared .friends block (Winners,
   section 8). Only the spacing differs - the leaderboard's old test-drive
   spacing, a little further from the button and the footer. */
.board__cta .friends { margin: 24px 0 16px; }

/* ==========================================================================
   8. WINNERS / HALL OF FAME  (design 06 winners.html -> site/prizes.html)
   No approved winner data exists, so the list holds a neutral "coming soon"
   card in the design's winner style. Real winners go in #prize-weeks.
   ========================================================================== */
.hall {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 32px var(--pad) 16px;
}

.hall h1 {
  max-width: 280px;
  font-size: var(--fs-h1);
  font-weight: 700;
  text-align: center;
}

.hall__intro {
  max-width: 347px;
  font-size: var(--fs-row);
  font-weight: 500;
  line-height: 1.25;
  text-align: center;
}

.winners {
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: 100%;
  margin: 20px 0 0;
}

.winners__cards { display: contents; }

.winner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 84px;
  padding: 10px 24px;
  border-radius: 12px;
  background: linear-gradient(90deg, #bf020e 0%, var(--byd-black) 100%);
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
}

.winner__meta {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

.winner__city { font-size: var(--fs-row); }

.winner__name {
  font-size: var(--fs-row);
  font-weight: 700;
  line-height: 1;
}

.winner__stats {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--fs-row);
}

.winner__divider {
  flex: none;
  width: 1px;
  height: 13px;
}

.winner__medal {
  flex: none;
  width: auto;
  height: 64px;
  object-fit: contain;
}

/* The no-winners-yet card */
.winner--empty .winner__name {
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.winner--empty .winner__note {
  font-size: var(--fs-small);
  line-height: 1.35;
}

.hall__cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: auto;
  padding-top: 24px;
}

/* FRIENDS OF TIMMO under PLAY (client, 2026-10-07; replaced BOOK A TEST
   DRIVE on Winners and Leaderboard). The markup is a .board__note, so the
   intro line takes the note's paragraph style and the dealership links take
   its h2 style - "Timmo's top score is 172": Inter 700, uppercase, white.
   Spacing is the old test-drive CTA's. */
.friends { margin: 20px 0 14px; }

/* "WOLLONGONG | NOWRA | ALBION PARK" is ~19.8em wide, so at the h2 size
   (20px) it needs ~396px - more than a phone's column (359px at the 391px
   design width). Rather than wrap and strand a separator, the size comes
   down only as far as one line needs: 4.5vw keeps it inside the column
   down to ~260px screens, and the h2 size is reached from ~445px up. */
.friends .friends__links { font-size: min(var(--fs-h2), 4.5vw); }

/* Same weight and colour as the heading, so no underline at rest; hover
   underlines as the footer links do. Focus ring: section 14. */
.friends__links a { text-decoration: none; }
.friends__links a:hover { text-decoration: underline; }

/* The pipes between the names are plain, not bold (client, 2026-10-07). */
.friends__sep { font-weight: 400; }

/* ==========================================================================
   9. LEGAL PAGES (terms.html / privacy.html) — shared shell only
   ========================================================================== */
.page-legal .stage {
  gap: 24px;
  padding: 32px var(--pad);
}

.section {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.section__title {
  font-family: var(--font-display);
  font-size: var(--fs-h1);
  font-weight: 900;
  text-align: center;
}

.placeholder-notice {
  padding: 12px 16px;
  border: 2px dashed #ffcc00;
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.6);
  color: #fff6cc;
  font-size: 14px;
  line-height: 1.45;
}

.placeholder-notice p { margin: 0 0 6px; }
.placeholder-notice p:last-child { margin-bottom: 0; }

/* ==========================================================================
   10. GAME PAGE — Unity canvas, loading screen, overlays
   The design's gameplay.html is an empty shell; the real Unity canvas is
   sized to the viewport by js/game-loader.js, so it is not wrapped in the
   nav/footer frame.
   ========================================================================== */
body.game-page {
  height: 100%;
  overflow: hidden;
  background: #000;
}

/* width / height / top / left / transform are ALL set inline by
   js/game-loader.js resizeCanvas(). Nothing here may override them. */
#unity-canvas {
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: #000;
}

#unity-canvas:focus { outline: none; }

/* --- Loading screen (design 03 loading.html, on the REAL loader) --------- */
#campaign-loader {
  position: fixed;
  inset: 0;
  z-index: 10;
  background: #000;
  /* Paired with a 400ms setTimeout in game-loader.js hideLoader(). Change
     both or neither. */
  transition: opacity var(--t-loader) ease;
}

#campaign-loader.is-hidden {
  opacity: 0;
  pointer-events: none;
}

/* The design's 480px screen, centred on black like every other page */
.loader__screen {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100%;
  max-width: var(--screen-max);
  margin-inline: auto;
  padding: var(--pad);
  background: var(--byd-black);
}

.loader__brand {
  position: absolute;
  top: clamp(40px, 9vh, 68px);
  left: 0;
  right: 0;
  padding-inline: var(--pad);
}

.loader__stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  width: 100%;
}

/* role="progressbar"; aria-valuenow follows the real Unity progress
   (mirrored from #loader-fill by js/campaign.js) */
.loader__bar {
  width: min(301px, 79%);
  height: 22px;
  padding: 3px;
  border-radius: 999px;
  background: var(--white);
  overflow: hidden;
}

/* Width is written inline, as a %, by game-loader.js on every real progress
   tick. The short transition only eases between those real values — there
   is no keyframe animation and no timer. */
.loader__fill {
  width: 0%;
  height: 100%;
  border-radius: 999px;
  background: var(--byd-red-deep);
  transition: width 0.3s ease;
}

/* game-loader.js writes this on every progress tick, so it must exist; it
   is visually hidden while loading (the bar speaks for itself)... */
.loader__status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ...but showLoadError() writes its headline here, so a failed load brings
   it back. Keyed off the error paragraph losing [hidden]. */
#campaign-loader:has(#loader-error:not([hidden])) .loader__status {
  position: static;
  width: auto;
  height: auto;
  margin: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: normal;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 900;
  line-height: 1.2;
  text-align: center;
}

.loader__error {
  max-width: 340px;
  margin: 24px 0 0;
  color: var(--error-ink);
  font-size: 14px;
  line-height: 1.5;
  text-align: center;
}

/* --- Score submission overlay --------------------------------------------- */
#score-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(24, 24, 24, 0.9);
}

.score-overlay__panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: 327px;
  padding: 24px;
  border-radius: 12px;
  background: var(--white);
  color: var(--row-text-alt);
  text-align: center;
}

/* The submitting state ships as artwork with its own letterforms, so it
   needs no plate. Scoped via :has() to the one state the artwork shows;
   padding stays, as it holds the artwork at its width. */
.score-overlay__panel:has(#score-title:not([hidden])) {
  background: transparent;
}

.score-overlay__title {
  display: block;
  width: 100%;
  height: auto;
}

.score-overlay__status {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 900;
  line-height: 1.2;
  text-wrap: balance;
}

.score-overlay__detail {
  margin: 10px 0 0;
  color: var(--field-placeholder);
  font-size: 15px;
  line-height: 1.45;
}

.score-overlay__panel .btn { margin-top: 18px; }

.score-overlay__panel .btn--secondary {
  border-color: var(--field-border);
  background: transparent;
  color: var(--field-placeholder);
}

/* --- Rotate-to-portrait overlay -------------------------------------------
   game-loader.js toggles .is-visible. The display:none / display:flex pair is
   the mechanism — do not swap it for opacity or visibility. */
#rotate-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 20px;
  background: rgba(24, 24, 24, 0.97);
  color: var(--white);
  text-align: center;
}

#rotate-overlay.is-visible { display: flex; }

#rotate-overlay img {
  width: 50vw;
  max-width: 160px;
  height: auto;
  opacity: 0.9;
}

#rotate-overlay p {
  font-size: 16px;
  font-weight: 500;
  line-height: 1.4;
}

@media (max-height: 500px) {
  #rotate-overlay img { width: 34vw; max-width: 120px; }
}

/* ==========================================================================
   11. DEVELOPMENT RIBBON (js/campaign.js, when SHOW_DEV_BANNER is on)
   ========================================================================== */
.dev-banner {
  position: relative;
  z-index: 200;                 /* above the locked nav, as on the home page */
  padding: 6px 12px;
  background: #ffcc00;
  color: #111;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.3;
  text-align: center;
}

/* ==========================================================================
   12. HOME HERO — TIMMO AGAINST THE WORDMARK
   The wordmark sits in flow from the top of the home stage; .home-timmo is
   anchored to the stage's bottom edge. The two only line up as in the
   client's Home screen when the stage is about as tall as the 391x757 design
   frame. Shorter, and the red script runs across his face; taller, and a gap
   opens between the wordmark and his head.
   ========================================================================== */

/* Short windows: scale him down with the viewport height. */
@media (max-height: 700px) {
  .home-timmo { width: min(86%, 62vh); }
}

/* Phones: pin Timmo to the BRAND instead of the stage bottom, at every phone
   height. His top is derived from the brand block (stage padding + lockup +
   gap + 70% of the wordmark height - the relationship in the client's
   revised Home screen, 30.09.26), so the crown of his head meets the tail of
   the script and underline. The lockup and wordmark widths mirror their own
   rules above (min(230px, 59%) and min(309px, 79%) of the brand's content
   box, with the artwork's 720x138 and 960x434 aspect ratios). On phones the
   cutout's cropped lower edge still lands inside the copy panel's solid
   black; on the taller 480px desktop/tablet frame it would not, so those keep
   the design's bottom anchor.
   Guarded by @supports: without container units he keeps the bottom anchor. */
@media (max-width: 479.98px) {
  @supports (width: 1cqw) {
    .stage--home { container-type: inline-size; }

    .home-timmo {
      --brand-w: calc(100cqw - 2 * var(--pad));
      top: calc(24px
        + min(230px, 0.59 * var(--brand-w)) * 0.19167
        + 16px
        + min(309px, 0.79 * var(--brand-w)) * 0.45208 * 0.70);
      bottom: auto;
    }
  }
}

/* ==========================================================================
   13. HIDDEN-STATE GUARDS  — load-bearing, do not delete
   Rules above give these elements display:flex / grid / block, which BEATS
   the browser default `[hidden] { display: none }`. Any new display rule on a
   JS-toggled element needs its own guard here.
   ========================================================================== */
.btn[hidden],
#board-retry[hidden],
#score-retry[hidden],
#score-register[hidden],
#score-overlay[hidden],
#form-error[hidden],
.form__error[hidden],
#loader-error[hidden],
.loader__error[hidden],
#score-detail[hidden],
.score-overlay__detail[hidden],
#score-title[hidden],
.score-overlay__title[hidden],
.board__status[hidden],
.register__returning[hidden],
[data-new-player-only][hidden] { display: none !important; }

/* ==========================================================================
   14. ACCESSIBILITY
   ========================================================================== */
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--white);
  outline-offset: 3px;
}

.btn:focus-visible {
  outline: 2px solid var(--white);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* The loader fade is exempt: game-loader.js waits 400ms before setting
     display:none, so zeroing this would leave a visible gap. */
  #campaign-loader { transition-duration: var(--t-loader) !important; }
}
