/* CSite — shared brand base: self-hosted variable fonts, the warm dark-ink
   editorial palette ("Neighbourly at night"), shape/spacing tokens, and
   cross-page primitives every page imports before its own stylesheet. */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/Inter-Variable.woff2") format("woff2");
}
@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 500 600;
  font-display: swap;
  src: url("/fonts/Newsreader-Variable.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("/fonts/JetBrainsMono-Variable.woff2") format("woff2");
}

:root {
  /* palette (14, lowercase) — warm dark ink, "Neighbourly at night" */
  --bg: #141312;
  --panel: #1c1b19;
  --panel-2: #232220;
  --line: #3a3832;
  --text: #f5f4ef;
  --body: #b8b3a7;
  --muted: #969184;
  --brand: #cc785c;
  --brand-2: #e08b6d;
  --brand-hover: #d98a6e;
  --good: #8aa876;
  --warn: #d29a56;
  --danger: #e06c65;
  --tier-orange: #e0894f;

  /* Every colour in every stylesheet under public/ resolves through the
     palette block above or the derived alphas below — no page stylesheet
     declares a raw hex or channel triplet of its own. */

  /* derived alphas — named once so no page stylesheet hardcodes a channel
     triplet again, re-derived for the dark palette above */
  --line-strong: rgba(245, 244, 239, .28);
  --edge-soft: rgba(245, 244, 239, .15);
  --nav-bg: rgba(20, 19, 18, .85);
  --select-bg: rgba(204, 120, 92, .32);
  --border-danger: rgba(224, 108, 101, .4);
  --tint-brand: rgba(204, 120, 92, .15);
  --tint-good: rgba(138, 168, 118, .15);
  --tint-warn: rgba(210, 154, 86, .15);
  --tint-danger: rgba(224, 108, 101, .15);
  --lift-veil: rgba(245, 244, 239, .18);

  /* type */
  --font-sans: "Inter", system-ui, sans-serif;
  --font-serif: "Newsreader", Georgia, serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --fs-body: 15px;
  --fs-h1: 26px;
  --fs-h2: 18px;
  --fs-brand: 21px;
  --fs-table: 13.5px;
  --fs-caption: 12.5px;
  --fs-btn: 14px;
  --fs-stat: 28px;

  /* shape */
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  /* spacing (four-multiple scale) */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px;
  --space-3xl: 64px;

  /* elevation — hairline at rest, soft and wide only on hover; deepened for
     the dark page, where a faint shadow disappears entirely */
  --shadow-card: 0 1px 2px rgba(8, 7, 6, .5);
  --shadow-panel: 0 1px 2px rgba(8, 7, 6, .5);
  --shadow-lift: 0 8px 40px -16px rgba(8, 7, 6, .75);

  /* motion — one decelerating curve shared by every new transition */
  --ease-out: cubic-bezier(.16, 1, .3, 1);

  /* drag-and-drop (quick 260927-nvl): a 3px drop-line indicator lives here,
     not in app.css, because the four-multiple pixel rule that governs every
     page-specific stylesheet makes a bare 3px declaration unwritable there —
     the same precedent --fs-table's 13.5px type-scale value already set. */
  --drop-line: 3px;

  color-scheme: dark;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  background: var(--bg);
  color: var(--body);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  font-feature-settings: "ss01";
  min-height: 100vh;
  overflow-x: clip;
}

h1 {
  font-family: var(--font-serif);
  font-size: var(--fs-h1);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -.02em;
  color: var(--text);
}

h2, h3 {
  font-family: var(--font-serif);
  font-size: var(--fs-h2);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -.02em;
  color: var(--text);
}

.hidden { display: none !important; }

::selection { background: var(--select-bg); }

/* -------- cross-page primitives -------- */

/* Masthead base — every page renders the same header bar. Pages that need a
   variant (home's logo-image alignment, the lobby's baseline marks) override
   in their own stylesheet; the bar itself must never depend on a page file,
   or pages that don't load app.css (home, fleet) render it unstyled. */
.masthead {
  display: flex; align-items: flex-end; justify-content: space-between;
  flex-wrap: wrap; row-gap: var(--space-sm);
  padding: var(--space-lg) var(--space-xl) var(--space-md);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 40;
  background: var(--nav-bg);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.masthead-mark { display: flex; align-items: baseline; gap: var(--space-md); flex-wrap: wrap; row-gap: var(--space-xs); }
.masthead-mark h1 { position: relative; }
.masthead-mark h1::after {
  content: ""; display: inline-block; width: 28px; height: 4px;
  margin-left: var(--space-sm); vertical-align: middle;
  background: var(--brand); border-radius: var(--radius-sm);
}
.masthead-status { display: flex; align-items: center; gap: var(--space-sm); flex-wrap: wrap; row-gap: var(--space-sm); }
.mark-sub { color: var(--muted); font-size: var(--fs-caption); font-weight: 600; letter-spacing: .04em; }

/* Cross-page nav primitives (Quick 260927-kz9): the wordmark home link and
   organizer-gated nav links, shared here rather than in app.css so index.html
   and fleet.html — which never load app.css — still render them correctly.
   The container below carries its own surface (D-SHAPE, phase 11 plan 01) so
   it reads as a bar at any link count, including the one-link case on the
   public home page; gap is tightened to space-xs so the inset pills read as
   one segmented control. flex-wrap and the :empty rule are untouched — the
   wrap is the 360px safety net this project has already been bitten by
   losing once, and :empty is what hides the bar on a page whose mount never
   renders. */
.masthead-nav {
  display: inline-flex; align-items: center; flex-wrap: wrap; gap: var(--space-xs);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-pill); padding: var(--space-xs);
}
.masthead-nav:empty { display: none; }
.nav-link {
  color: var(--brand-2); text-decoration: none;
  font-size: var(--fs-caption); font-weight: 600;
  transition: opacity .15s var(--ease-out);
}
.nav-link:hover { opacity: .8; }
.nav-link[aria-current="page"] { color: var(--text); border-bottom: 1px solid var(--brand-2); padding-bottom: var(--space-xs); }

/* Nav tab surface (POLISH-01, D-SCOPE): descendant-scoped under the bar
   container above so the bare .nav-link rules immediately above — reused
   outside the nav bar at public/home.js's per-card console link and
   public/fleet-insights.html's back-link — never pick up this pill
   treatment. Hover and the current-page state each carry their own real
   surface (background, not opacity alone) so the two are visually distinct
   from one another; the current-page rule neutralises the bare rule's
   bottom-border/padding-bottom (same specificity tier, declared after) with
   its own padding and an inset accent ring instead. */
.masthead-nav .nav-link {
  display: inline-flex; align-items: center;
  min-height: 32px;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-pill);
  transition: background .15s var(--ease-out), color .15s var(--ease-out);
}
.masthead-nav .nav-link:hover {
  background: var(--panel-2); color: var(--text); opacity: 1;
}
.masthead-nav .nav-link[aria-current="page"] {
  background: var(--tint-brand); color: var(--text);
  border-bottom: none; padding-bottom: var(--space-sm);
  box-shadow: inset 0 0 0 1px var(--brand);
}
.wordmark-link { color: inherit; text-decoration: none; }
.wordmark-link:hover { opacity: .85; }

/* Identity corner (NAV-01/IDENT-01/IDENT-02): the disclosure-navigation
   avatar menu and the self-hosted Steam sign-in button, shared here (not
   app.css/fleet.css) for the identical cross-page-reach reason as the nav
   primitives above. The Steam button's own width/height are set as HTML
   attributes in site-identity.js, never a CSS rule here, so its 35px native
   height never needs to appear in a four-pixel-constrained stylesheet. */
.steam-signin { display: inline-flex; line-height: 0; }
.steam-signin img { display: block; max-width: 100%; height: auto; }

.identity-corner { position: relative; display: inline-block; }
.identity-trigger {
  display: inline-flex; align-items: center; gap: var(--space-sm);
  background: transparent; border: none; cursor: pointer;
  padding: 0; font: inherit; color: var(--text);
}
.identity-trigger img,
.identity-initial {
  width: 24px; height: 24px; border-radius: var(--radius-sm);
  object-fit: cover; display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel-2); color: var(--muted);
  font-size: var(--fs-caption); font-weight: 600;
}
.identity-name { font-size: var(--fs-caption); font-weight: 600; }

.identity-panel {
  position: absolute; right: 0; top: 100%; margin-top: var(--space-xs);
  display: flex; flex-direction: column;
  min-width: 160px; max-width: 240px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-md); box-shadow: var(--shadow-lift);
  z-index: 50;
}
.identity-panel.hidden { display: none; }
.identity-item {
  display: block; color: var(--body); text-decoration: none;
  font-size: var(--fs-caption); font-weight: 600;
  padding: var(--space-sm) var(--space-md);
}
.identity-item:hover { color: var(--brand-2); background: var(--panel-2); }

/* Narrow-viewport masthead pass: the sticky bar caps its own height once the
   wrapped status cluster (up to seven controls on the console) would
   otherwise consume most of a short viewport. Phase 10 plan 04 extends this
   for the identity corner: justify-content: flex-end keeps every wrapped
   status line (and, on a line where the identity corner is the sole
   occupant, the corner itself) flush against the masthead's own right
   padding edge, so the corner's right-anchored dropdown is always anchored
   space-md away from the viewport's true right edge rather than wherever it
   happened to fall in source order — the panel's own max-width below is
   sized against that same edge, not a fixed pixel figure, so the two
   together keep the panel fully on-screen down to 360px. */
@media (max-width: 640px) {
  .masthead { padding: var(--space-sm) var(--space-md); max-height: 45vh; overflow-y: auto; }
  .masthead-mark h1 { font-size: var(--fs-h2); }
  .masthead-status { justify-content: flex-end; }
  .identity-panel { max-width: calc(100vw - 2 * var(--space-md)); }
  .masthead-nav .nav-link { min-height: 44px; }
}

/* Signed-in identity chip — rendered by every page's auth box. */
.who { display: inline-flex; align-items: center; gap: var(--space-sm); font-size: var(--fs-caption); }
.who img { width: 24px; height: 24px; object-fit: cover; border-radius: var(--radius-sm); }
.who a { color: var(--muted); text-decoration: none; padding: 0 var(--space-xs); }
.who a:hover { color: var(--brand); }
.who a.history-link { color: var(--brand-2); }
.who a.history-link:hover { color: var(--brand-2); opacity: .8; }

.btn {
  font-family: var(--font-sans); font-weight: 500; font-size: var(--fs-btn);
  cursor: pointer;
  color: var(--bg); background: var(--brand);
  border: 1px solid var(--brand);
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
  transition: background .15s var(--ease-out), box-shadow .15s var(--ease-out), border-color .15s var(--ease-out);
}
.btn:hover:not(:disabled) { background: var(--brand-hover); border-color: var(--brand-hover); }
.btn:disabled { opacity: .35; cursor: not-allowed; }

.btn-accent {
  background: var(--brand); border-color: var(--brand); color: var(--bg);
}
.btn-accent:hover:not(:disabled) { background: var(--brand-hover); border-color: var(--brand-hover); }

.btn-outline {
  background: transparent; border-color: var(--edge-soft); color: var(--text);
}
.btn-outline:hover:not(:disabled) { background: var(--tint-brand); }

/* Destructive button variant (POLISH-04, phase 11 plan 04): previously
   restated byte-identical in both public/app.css and public/fleet.css,
   each with its own comment explaining the restatement existed only
   because neither page stylesheet loads the other — this shared base is
   the project's own existing answer to that constraint. Declared after
   the base .btn rule above (D-ORDER) so source order still lets it
   override the base button's brand fill, the same cascade relationship
   it had in either page stylesheet. */
.btn-danger { color: var(--danger); border-color: var(--border-danger); background: transparent; }

.btn-mini { padding: var(--space-xs) var(--space-sm); font-size: var(--fs-caption); }

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 0 0 var(--space-md);
}

/* Panel-head layout (POLISH-04, phase 11 plan 04): previously declared
   identically in both public/app.css and public/fleet.css — promoted here
   beside .panel so a page loading only theme.css still renders it
   correctly. */
.panel-head {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--space-sm);
  padding: var(--space-md) var(--space-lg) var(--space-sm);
  border-bottom: 1px solid var(--line);
}

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  transition: border-color .3s var(--ease-out), box-shadow .3s var(--ease-out);
}
.card:hover {
  border-color: var(--line-strong);
  box-shadow: var(--shadow-lift);
}

.pill {
  font-size: var(--fs-caption); font-weight: 600;
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--line); color: var(--muted);
  border-radius: var(--radius-pill); background: var(--panel-2);
  transition: border-color .15s var(--ease-out), background .15s var(--ease-out), color .15s var(--ease-out);
}

.badge {
  font-size: var(--fs-caption); font-weight: 600;
  border-radius: var(--radius-sm);
  transition: border-color .15s var(--ease-out), background .15s var(--ease-out);
}

/* Form hint (POLISH-04, phase 11 plan 04): the base treatment plus its
   error variant, previously declared identically in both public/app.css
   and public/fleet.css — promoted here so a page loading only theme.css
   still renders it correctly. The fleet stylesheet's own self-collapsing
   .form-hint:empty rule is deliberately NOT promoted: it exists only
   there, and promoting it would start collapsing reserved hint space on
   pages that deliberately reserve it (app.css's own form-hint consumers),
   shifting layout the moment a message appears. Every higher-specificity
   descendant override of this base (.profile-panel .form-hint,
   #playerActions .form-hint, .mp-forfeit-picker .form-hint, all in
   app.css) stays exactly where it is — those are real page-specific
   deltas, not duplication. */
.form-hint { color: var(--muted); font-size: var(--fs-caption); font-weight: 600; padding: var(--space-sm) var(--space-lg) 0; min-height: 20px; }
.form-hint.err { color: var(--danger); opacity: 1; }

/* Stat strip (D-STRIP, phase 11 plan 02): a row of labelled count chips,
   the one shared primitive public/home.js, public/results.js and
   public/me-matches.js all consume. Lives here rather than app.css because
   index.html loads only theme.css + home.css and this is the only
   stylesheet all three pages reach. The row wraps (flex-wrap) so it never
   reintroduces the non-wrapping-row 360px defect this project has already
   been bitten by once; :empty mirrors .masthead-nav:empty above, so a page
   whose builder returns the empty string renders no bordered row at all. */
.stat-strip {
  display: flex; flex-wrap: wrap; gap: var(--space-sm);
}
.stat-strip:empty { display: none; }
.stat-chip {
  display: inline-flex; flex-direction: column; align-items: flex-start;
  gap: var(--space-xs);
  padding: var(--space-sm) var(--space-md);
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.stat-chip-value {
  font-family: var(--font-serif); font-size: var(--fs-stat); color: var(--text);
}
.stat-chip-label {
  font-size: var(--fs-caption); color: var(--muted);
  font-weight: 600; letter-spacing: .04em;
}

/* Empty state (D-EMPTY, phase 11 plan 04): absorbs two byte-identical
   container/title/body families previously declared separately in
   fleet.css and app.css (each retired outright, not left in place) under
   one shared name. The art slot generalises home.css's decorative-
   illustration rule so the home page's own richer empty-state family — a
   different layout, not a different name for the same thing — can
   compose onto this base as a delta (folded in by phase 11 plan 05)
   instead of restating it. */
.empty-state { text-align: center; padding: var(--space-xl) var(--space-lg); }
.empty-state-title { font-family: var(--font-serif); font-size: var(--fs-h2); font-weight: 500; margin-bottom: var(--space-xs); }
.empty-state-body { color: var(--muted); font-size: var(--fs-body); }
.empty-state-art { max-width: 200px; opacity: .5; color: var(--brand-2); margin-bottom: var(--space-sm); }

/* -------- accessibility: keyboard focus -------- */
:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
.btn:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* -------- scrollbars -------- */
html {
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) var(--bg);
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: var(--radius-sm);
}

/* -------- live-state keyframes (odd values live here, not app.css) -------- */
@keyframes live-glow {
  0%, 100% { box-shadow: 0 0 0 1px var(--danger); }
  50% { box-shadow: 0 0 0 1px rgba(224, 108, 101, .65); }
}
@keyframes live-dot {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}
