/* ==========================================================================
   Bad Dog Sports — Global stylesheet & design system
   All design tokens live in :root as CSS custom properties. See DESIGN.md
   for the reasoning behind the palette and type choices.
   ========================================================================== */

/* Self-hosted fonts — ZERO external requests. These were `@import`s of the
   `@fontsource` npm packages under Astro; there is no npm here, so the two
   .woff2 files were lifted out of those packages into /static/fonts/ and are
   declared directly. The family names must stay exactly as they were, because
   --font-display and --font-body below name them.

   `font-display: swap` so a competitor on venue wifi reads the running order in
   a fallback face rather than staring at nothing. */
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/anton-latin-400-normal.woff2') format('woff2');
}

@font-face {
  font-family: 'Inter Variable';
  font-style: normal;
  font-weight: 100 900; /* variable axis — one file covers 400 through 700 */
  font-display: swap;
  src: url('/static/fonts/inter-latin-wght-normal.woff2') format('woff2');
}

:root {
  /* --------------------------------------------------------------------
     1. RAW PALETTE
     Brand-anchored scales. Prefer the semantic tokens below in components.
     -------------------------------------------------------------------- */

  /* Navy — the brand anchor. 900 and 800 are the locked brand values that
     match the Mission/Vision/Values page; the rest are derived steps. */
  --navy-950: #050e17;
  --navy-900: #0b1c2c; /* LOCKED — primary brand navy */
  --navy-800: #132f4c; /* LOCKED — navy light */
  --navy-700: #1c4269;
  --navy-600: #265687;
  --navy-500: #316ba5;

  /* Gold — the single brand accent (awards, CTAs, energy hits).
     500 and 400 are locked; 700 exists because gold is too light to
     read as text on the cream background (see --color-accent-on-light). */
  --gold-700: #7d6212;
  --gold-600: #a5851f;
  --gold-500: #c9a227; /* LOCKED — gold */
  --gold-400: #e8c547; /* LOCKED — gold light */

  /* Neutrals — cream page ground, slate text, cool-gray support scale. */
  --white: #ffffff;
  --cream: #f8f5f0; /* LOCKED — page background */
  --mist: #ece7de; /* cream, one step down, for alt surfaces */
  --gray-200: #e2e8f0;
  --gray-300: #cbd5e1;
  --gray-400: #94a3b8;
  --gray-500: #64748b; /* LOCKED — muted text */
  --gray-600: #475569;
  --slate-900: #1e293b; /* LOCKED — body text */

  /* Functional status colors — outside the brand palette on purpose;
     these only ever signal form/state feedback, never brand. */
  --success-600: #1e6b4e;
  --error-600: #b3261e;

  /* THE BROADCAST BOARD'S FOUR STATE COLOURS. Functional in exactly the sense
     the two above are: they signal which dog is where in the order and never the
     brand. They live here rather than in `board.css` because that file declares
     no colour of its own — surface-token table, rule 4.

     Taken from Sport Dog Hub's broadcast page, which colour-codes the same four
     states, and kept close to its values because they were chosen for a glossy
     television in direct sun and BAD's boards stand in the same fields.
     `--board-up` is BAD's own gold rather than SDH's amber: the dog that is up is
     the one the room is watching, and that is what the accent colour is for.

     Only ON NAVY. None of these is legible on cream and none is used there. */
  --board-up: var(--gold-400);
  --board-deck: #5fccff;
  --board-hole: #c3adff;
  --board-done: #46e285;

  /* --------------------------------------------------------------------
     2. SEMANTIC TOKENS
     What components should actually reference.
     -------------------------------------------------------------------- */
  --color-bg: var(--cream);
  --color-bg-alt: var(--white);
  --color-bg-invert: var(--navy-900);
  --color-surface: var(--white);
  --color-surface-alt: var(--mist);

  --color-text: var(--slate-900);
  --color-text-muted: var(--gray-500);
  --color-text-invert: var(--cream);
  --color-text-invert-muted: var(--gray-300);

  --color-brand: var(--navy-900);
  --color-brand-light: var(--navy-800);

  /* Gold is a FILL accent: backgrounds, borders, and text on navy. It does
     not pass contrast as text on cream — use --color-accent-on-light there. */
  --color-accent: var(--gold-500);
  --color-accent-hover: var(--gold-600);
  --color-accent-on-light: var(--gold-700);
  --color-on-accent: var(--navy-900); /* text sitting on a gold fill */

  /* Links in body copy read as navy, not gold, for legibility. */
  --color-link: var(--navy-800);
  --color-link-hover: var(--gold-700);

  --color-border: var(--gray-200);
  --color-border-strong: var(--gray-300);

  --color-success: var(--success-600);
  --color-error: var(--error-600);

  --color-focus: var(--navy-800);

  /* Per-sport accents. The locked brand palette is navy + gold only, so all
     three sports share the gold accent and differentiate through imagery.
     Set `accent` in a sport's frontmatter to override one of them. */
  --sport-dock-jumping: var(--color-accent);
  --sport-shed-hunting: var(--color-accent);
  --sport-disc: var(--color-accent);

  /* --------------------------------------------------------------------
     3. TYPOGRAPHY
     Display: Anton (condensed, athletic, uppercase headlines).
     Body/UI: Inter (highly legible, neutral, wide weight range).
     -------------------------------------------------------------------- */
  --font-display: 'Anton', 'Arial Narrow', system-ui, sans-serif;
  --font-body: 'Inter Variable', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono: ui-monospace, 'SFMono-Regular', 'Menlo', monospace;

  /* Fluid type scale (min at 320px viewport → max at ~1200px). */
  --text-xs: clamp(0.75rem, 0.72rem + 0.15vw, 0.8125rem);
  --text-sm: clamp(0.875rem, 0.84rem + 0.18vw, 0.9375rem);
  --text-base: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  --text-lg: clamp(1.125rem, 1.06rem + 0.35vw, 1.25rem);
  --text-xl: clamp(1.35rem, 1.2rem + 0.7vw, 1.75rem);
  --text-2xl: clamp(1.75rem, 1.45rem + 1.5vw, 2.5rem);
  --text-3xl: clamp(2.25rem, 1.7rem + 2.7vw, 3.75rem);
  --text-4xl: clamp(2.75rem, 1.9rem + 4.3vw, 5.5rem);
  --text-display: clamp(3.25rem, 2rem + 6.2vw, 7rem);

  --leading-tight: 1.05;
  --leading-snug: 1.2;
  --leading-normal: 1.55;

  --tracking-tight: -0.01em;
  --tracking-wide: 0.02em;
  --tracking-caps: 0.06em;

  /* --------------------------------------------------------------------
     4. SPACING, RADII, SHADOWS, MOTION, LAYOUT
     -------------------------------------------------------------------- */
  --space-2xs: 0.25rem;
  --space-xs: 0.5rem;
  --space-sm: 0.75rem;
  --space-md: 1rem;
  --space-lg: 1.5rem;
  --space-xl: 2.5rem;
  --space-2xl: 4rem;
  --space-3xl: 6rem;

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  --shadow-sm: 0 1px 2px rgba(11, 28, 44, 0.08);
  --shadow-md: 0 6px 18px rgba(11, 28, 44, 0.12);
  --shadow-lg: 0 18px 40px rgba(11, 28, 44, 0.18);

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --transition-fast: 140ms var(--ease);
  --transition: 220ms var(--ease);

  --container: 1200px;
  --container-narrow: 760px;
  --header-height: 4.5rem;

  --z-header: 100;
  --z-menu: 200;
  --z-overlay: 300;
}

/* ==========================================================================
   Base / reset
   ========================================================================== */
*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
}

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

/* SMOOTH SCROLLING, BUT NOT ON THE FIRST PAINT — and the selector IS the fix.

   `html { scroll-behavior: smooth }` breaks EVERY on-load fragment in Chrome.
   Loading `/rules/#refund-policy` with it on lands at the top of the page, in
   silence: no console error, no visible failure, just the wrong section. Found in
   Phase 8 because every desk action redirects to `#dog-<id>` so the person lands
   back on the dog they were working on, and none of them did — then confirmed on
   the rules table of contents, which had been shipping broken since Phase 4. A
   competitor following a deep link to a rule was reading whatever was at the top.

   `:focus-within` is what separates the two cases. On a fresh document nothing is
   focused, so the browser's initial fragment scroll runs with `auto` and lands
   where it should. Clicking a table-of-contents link focuses that link, `html`
   matches, and the same-document scroll animates. Both behaviours, no JavaScript,
   and nothing to keep in sync with a template.

   Verified both ways in Chrome, not reasoned about: on-load lands on the section,
   an in-page click still animates. */
html:focus-within {
  scroll-behavior: smooth;
}

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

body {
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

a {
  color: var(--color-link);
  text-decoration: none;
  transition: color var(--transition-fast);
}

a:hover {
  color: var(--color-link-hover);
}

/* Inverted (navy) surfaces flip the accent-dependent tokens: on navy, gold
   is the readable one and navy-800 is not. Keep this list in sync with the
   components that use --color-bg-invert. */
.site-header,
.site-footer,
.hero,
.page-header,
.sport-hero,
.event-hero,
.post-header,
.notfound,
.invert {
  --color-link: var(--color-text-invert);
  --color-link-hover: var(--gold-400);
  --color-accent-on-light: var(--gold-400);
  --color-focus: var(--gold-400);

  --btn-outline-fg: var(--color-text-invert);
  --btn-outline-border: var(--color-accent);
  --btn-outline-hover-bg: var(--color-accent);
  --btn-outline-hover-fg: var(--color-on-accent);
}

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}

/* A CONTROL THAT INHERITS ITS TEXT COLOUR MUST NOT KEEP THE BROWSER'S
   BACKGROUND. Those two are one decision and they were made half-way: `color:
   inherit` above, and the user agent's opaque white underneath it.

   On the cream site nobody notices, because inherited text is dark. In the
   staff portal's Sun Mode the inherited colour is near-white — so every bare
   input rendered WHITE TEXT ON A WHITE BOX. The value was there, it was
   focusable, it submitted correctly, and it was invisible. It surfaced on the
   check-in screen, where an amount of money is typed into exactly such a box.

   `.field` controls already set this same token, so nothing that looks right
   today changes; this only reaches the controls that were relying on a default
   that does not follow the theme. Anything with its own background still wins,
   which is why `button` is excluded — buttons carry their own fills. */
input,
textarea,
select {
  background-color: var(--color-surface);
}

:where(h1, h2, h3, h4) {
  line-height: var(--leading-tight);
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ==========================================================================
   Typography helpers
   ========================================================================== */
.font-display {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-tight);
}

.eyebrow {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--color-accent-on-light);
}

/* ==========================================================================
   Layout utilities
   ========================================================================== */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-lg);
}

.container-narrow {
  max-width: var(--container-narrow);
}

.section {
  padding-block: var(--space-3xl);
}

.stack > * + * {
  margin-top: var(--space-md);
}

.visually-hidden {
  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(--space-md);
  top: -100%;
  z-index: var(--z-overlay);
  background: var(--color-brand);
  color: var(--color-text-invert);
  padding: var(--space-xs) var(--space-md);
  border-radius: var(--radius-md);
  transition: top var(--transition-fast);
}

.skip-link:focus {
  top: var(--space-md);
  color: var(--color-text-invert);
}

/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  padding: var(--space-sm) var(--space-lg);
  font-weight: 700;
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  line-height: 1;
  border: 2px solid transparent;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: transform var(--transition-fast), background var(--transition-fast),
    color var(--transition-fast), border-color var(--transition-fast);
}

.btn:active {
  transform: translateY(1px);
}

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

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

/* A filled state for "this already happened" — checked in, paid, done. Not an
   outline: an outline reads identically to every other secondary action on the
   desk (Add an entry, Comp, Refund), so a checked-in dog looked the same as one
   that wasn't. White text on a dark fill needs no Sun Mode override — the
   contrast that matters is text-on-fill, not fill-on-page. */
.btn-success {
  background: var(--color-success);
  color: var(--white);
}

.btn-success:hover {
  background: var(--color-success);
  opacity: 0.85;
}

/* Outline buttons read from flippable tokens so they work on cream and navy
   without a per-section override. The inverted block below retunes them. */
.btn-outline {
  background: transparent;
  color: var(--btn-outline-fg, var(--color-brand));
  border-color: var(--btn-outline-border, var(--color-brand));
}

.btn-outline:hover {
  background: var(--btn-outline-hover-bg, var(--color-brand));
  color: var(--btn-outline-hover-fg, var(--color-text-invert));
  border-color: var(--btn-outline-hover-bg, var(--color-brand));
}

/* A toggle that stays pressed — the desk's roster filter — reads the same
   flippable tokens as :hover above rather than a colour of its own, so it
   still works on an inverted surface and in Sun Mode without a second
   override. */
.btn-outline[aria-pressed='true'] {
  background: var(--btn-outline-hover-bg, var(--color-brand));
  color: var(--btn-outline-hover-fg, var(--color-text-invert));
  border-color: var(--btn-outline-hover-bg, var(--color-brand));
}

.btn[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}

/* ==========================================================================
   Forms

   Global rather than scoped, because forms are no longer one page. The contact
   form defined these first; sign-up, sign-in, password reset and the profile
   page all need the identical field, and a competitor should not be able to
   tell from the input borders which part of the site they are on. Scoped
   copies in six files is six places for them to drift apart.
   ========================================================================== */
.field {
  display: grid;
  gap: var(--space-xs);
}

.field label {
  font-weight: 600;
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.field input,
.field select,
.field textarea {
  padding: var(--space-sm) var(--space-md);
  border: 2px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  font: inherit;
  color: inherit;
  transition: border-color var(--transition-fast);
}

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  border-color: var(--color-accent);
  outline: none;
}

/* What a field is for, under the field. Muted and small — it must not compete
   with the label, and it must not be the only place a requirement is stated. */
.field-hint {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* Every form's one live region. `role="status"` with `aria-live` is announced
   without stealing focus, which matters most on the forms where the answer is
   "that did not work" and the cursor should stay where it is. */
.form-status {
  font-weight: 600;
  min-height: 1.5em;
}

.form-status[data-state='ok'] {
  color: var(--color-success);
}

.form-status[data-state='error'] {
  color: var(--color-error);
}

/* Neither. Phase 5's eligibility note — "too young to compete yet, eligible from
   January 12" — is not a failure and it is emphatically not a success, and it read
   as one in success green. Ordinary body colour, so it is a statement rather than
   a verdict. */
.form-status[data-state='info'] {
  color: var(--color-text);
  font-weight: 500;
}

/* Off-screen, not display:none — a bot fills it, a screen reader skips it
   because the wrapper is aria-hidden, and a keyboard user never reaches it
   because the input is tabindex="-1". */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
