/* ============================================================
   DOUBLE SKUNK — Brand tokens
   ------------------------------------------------------------
   This file holds the BRAND-LEVEL system: skunk colors, type,
   spacing, radii, shadows, and SEMANTIC SURFACE TOKENS bound to
   brand defaults.

   The brand surfaces are intentionally NEUTRAL — a near-black
   "ink" with a chalk-cream foreground and a brass accent. The
   wooden parlor look is NOT the brand; it is one theme.

   Themes live in `themes/` and override the semantic surface
   tokens for in-game boards. Apply with a class on a wrapper:
       <div class="theme-parlor">...the match...</div>
   ============================================================ */

/* The webfonts (Bowlby One SC, Big Shoulders Display, Space Grotesk,
   JetBrains Mono, and Caudex on the learn pages) are self-hosted out of
   priv/static/doubleskunk/fonts and declared by the ds_fonts component,
   which inlines the @font-face rules into each page's head. Pulling them
   in from here instead would hide every font URL behind this file. */

:root {
  /* ===========================================================
     BRAND CORE — skunk-ink, chalk, brass.
     These are the three colors that define the brand at every
     touchpoint regardless of which board you're on.
     =========================================================== */

  --skunk-ink:        #0d0c10;   /* near-black, hint of plum — never navy, never brown */
  --skunk-ink-2:      #14131a;
  --skunk-ink-3:      #1d1c24;
  --skunk-ink-4:      #2b2932;
  --skunk-ink-5:      #3a3742;

  --chalk:            #f1ead0;   /* the skunk stripe — primary text on dark */
  --chalk-2:          #e6dcba;
  --chalk-3:          #ada48a;
  --chalk-4:          #7a7263;

  /* --brass stays the brand hex because it is a SURFACE as well as an accent:
     it is the mid stop of the .cta.pop gradient and the fill behind the learn
     toggles, all of which carry --chalk text. Lifting it for legibility as
     text would darken that text's contrast in exchange. Accent TEXT reads
     --fg-accent instead; see the note there. */
  --brass:            #d24b3a;   /* BRAND accent (ruby). Parlor overrides this to actual brass-gold. */
  --brass-bright:     #e9624f;
  --brass-deep:       #8e2418;
  --brass-glow:       #f4a190;

  /* secondary brand accents — used for variety, progress, mystery.
     --pop-ruby is only ever text (guide badges, section kickers), so unlike
     --brass it can take the lifted value outright. It was #d24b3a, which was
     4.23:1 on --skunk-ink-2 and 3.87:1 on the --surface-raised badges. */
  --pop-ruby:         #e25947;
  --pop-mint:         #58c08e;
  --pop-violet:       #7a52c4;

  /* ===========================================================
     GAME COLORS — rarity, currency, HP, suits. These belong to
     the GAME, not to any single theme. They render on top of
     whatever surface the active theme provides.
     =========================================================== */

  /* Rarity (the roguelite layer) — desaturated to feel hand-painted */
  --rarity-common:    #9a9082;
  --rarity-uncommon:  #4c9a72;
  --rarity-rare:      #4a82c4;
  --rarity-legendary: #b562d4;
  --rarity-mythic:    #e25a36;

  /* Currency & state */
  --gold:             #e6b94e;
  --gold-deep:        #a07e1f;
  --seal-blue:        #6a9bd8;
  --seal-purple:      #9170c4;
  --seal-jade:        #5cb693;
  --hp-red:           #c0392b;
  --xp-jade:          #5cb693;
  --ok:               #5cb693;
  --warn:             #e6b94e;
  --danger:           #c0392b;

  /* Playing card stock — these stay constant across themes
     (cards are world objects, not chrome) */
  --card-face:        #f1ead0;
  --card-back:        #2b2932;   /* default — themes override */
  --card-red:         #c8412c;   /* the game's red ink (parlor CardSkin); the card SVGs hard-code the same value for parity */
  --card-black:       #16110f;   /* the game's black ink */
  --card-cut-frame:   #a56a22;   /* the game's ember starter frame, worn by the cut card */
  --card-select:      #36c2b4;   /* halo on a card the reader has picked */

  /* ===========================================================
     SEMANTIC SURFACE TOKENS — bind these in your components.
     Defaults below are the BRAND surfaces (neutral skunk-ink).
     Themes override these. Never write to --walnut-* etc.
     directly from a component — go through these.
     =========================================================== */

  /* layers from back to front */
  --surface-page:        var(--skunk-ink);     /* whole-viewport bg */
  --surface-base:        var(--skunk-ink-2);   /* default panel bg */
  --surface-raised:      var(--skunk-ink-3);   /* button, row, list cell */
  --surface-row-hover:   var(--skunk-ink-4);
  --surface-panel:       var(--skunk-ink-2);   /* major dialog / menu panel */

  /* the table — where the match happens. Themes change this most. */
  --surface-table:       var(--skunk-ink);
  --surface-board:       var(--skunk-ink-3);   /* the pegboard plank itself */

  /* foreground tiers */
  --fg-1:                var(--chalk);
  --fg-2:                oklch(85% 0.02 90);
  --fg-3:                oklch(70% 0.02 90);
  /* The quietest tier still has to be readable: it carries the kb-bar
     breadcrumb, the footer byline, the footer fine print, the learn stop-card
     kickers and the stat labels on --surface-raised. At 55% it was 4.01:1 on
     --skunk-ink and failed all of them. Still the dimmest tier, now past
     4.5:1 on every surface it is painted on. */
  --fg-4:                oklch(62% 0.02 90);
  /* Brass as TEXT. Was var(--brass) = #d24b3a, which is 4.23:1 on
     --skunk-ink-2 and accounted for five of the eight Lighthouse contrast
     failures on the landing page (.fnum, the kickers, the cast flourish).
     Lifted at the same hue and chroma until it clears 4.5:1 on every
     surface it lands on, --surface-raised included. Kept separate from
     --brass so the button fills underneath --chalk text do not move with
     it: that is the trade this split exists to avoid. */
  --fg-accent:           #e25947;

  /* borders — chalk-tinted at brand level (neutral). Themes override to warm tints. */
  --border-hair:         oklch(95% 0.02 90 / 0.12);
  --border-soft:         oklch(95% 0.02 90 / 0.06);
  --border-strong:       var(--brass);

  /* vignette + texture — themes override.
     Brand default: very subtle, no candle (that's the parlor) */
  --vignette:            none;
  --texture-overlay:     none;
  --texture-board:       none;    /* pegboard background image */

  /* ===========================================================
     TYPE SYSTEM — brand-level. Themes do not change type.
     =========================================================== */

  /* The *Fallback families are declared by the ds_fonts component, not here:
     they are local Arial (Roboto on Android) with size-adjust and vertical
     metrics overridden to match the real face. They sit ahead of the plain
     local names so headings keep their measurements while the webfont is
     still in flight, which is what stops the swap re-wrapping the page. */
  --font-display: 'Bowlby One SC', 'Bowlby One SC Fallback', 'Bowlby One', 'Anton', 'Impact', sans-serif;
  --font-poster:  'Bowlby One SC', 'Bowlby One SC Fallback', 'Bowlby One', 'Anton', 'Impact', sans-serif;
  --font-sign:    'Big Shoulders Display', 'Big Shoulders Fallback', 'Oswald', 'Impact', sans-serif;
  --font-body:    'Space Grotesk', 'Space Grotesk Fallback', 'Inter', 'Helvetica Neue', system-ui, sans-serif;
  --font-mono:    'JetBrains Mono', 'IBM Plex Mono', ui-monospace, monospace;

  --t-display-1-size: 72px;   --t-display-1-lh: 1.0;   --t-display-1-tracking: -0.02em;
  --t-display-2-size: 48px;   --t-display-2-lh: 1.05;  --t-display-2-tracking: -0.015em;
  --t-h1-size:        36px;   --t-h1-lh: 1.1;          --t-h1-tracking: -0.01em;
  --t-h2-size:        28px;   --t-h2-lh: 1.15;         --t-h2-tracking: -0.005em;
  --t-h3-size:        22px;   --t-h3-lh: 1.2;          --t-h3-tracking: 0;
  --t-body-size:      17px;   --t-body-lh: 1.5;        --t-body-tracking: 0;
  --t-small-size:     14px;   --t-small-lh: 1.45;      --t-small-tracking: 0;
  --t-sign-size:      13px;   --t-sign-lh: 1.0;        --t-sign-tracking: 0.14em;
  --t-mono-size:      13px;   --t-mono-lh: 1.0;        --t-mono-tracking: 0.05em;

  /* ===========================================================
     RADII + SPACING — brand-level, never themed.
     =========================================================== */

  --r-card: 2px;
  --r-chip: 6px;
  --r-inset: 10px;
  --r-panel: 18px;
  --r-pill: 999px;

  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px;
  --s-5: 20px; --s-6: 24px; --s-8: 32px; --s-10: 40px;
  --s-14: 56px; --s-20: 80px;

  /* ===========================================================
     SHADOWS / GLOWS — brand-level.
     =========================================================== */

  --shadow-candle:
    inset 0 1px 0 oklch(95% 0.04 80 / 0.06),
    0 24px 60px -16px oklch(8% 0 0 / 0.7),
    0 4px 12px -4px oklch(8% 0 0 / 0.4);

  --shadow-card:
    0 1px 1px oklch(8% 0 0 / 0.5),
    0 8px 16px -4px oklch(8% 0 0 / 0.4);

  --shadow-engrave:
    inset 0 1px 0 oklch(95% 0.04 80 / 0.12),
    inset 0 -1px 0 oklch(8% 0 0 / 0.6);

  --shadow-button:
    inset 0 1px 0 oklch(95% 0.04 80 / 0.18),
    inset 0 -1px 0 oklch(8% 0 0 / 0.5),
    0 2px 6px -2px oklch(8% 0 0 / 0.6);

  --glow-brass:
    0 0 0 1px var(--brass),
    0 0 24px -4px color-mix(in srgb, var(--brass) 55%, transparent);

  --glow-mythic: 0 0 22px -2px var(--rarity-mythic);

  /* ===========================================================
     BRAND TEXTURES — cribbage-hole pattern (universal, says
     "this is cribbage" without saying "this is wood"). Used as
     a subtle background overlay at low opacity.
     =========================================================== */

  --cribbage-holes: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='40'><circle cx='8' cy='8' r='1.4' fill='%23f1ead0' opacity='0.10'/><circle cx='24' cy='8' r='1.4' fill='%23f1ead0' opacity='0.10'/><circle cx='8' cy='24' r='1.4' fill='%23f1ead0' opacity='0.10'/><circle cx='24' cy='24' r='1.4' fill='%23f1ead0' opacity='0.10'/></svg>");

  --grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.6  0 0 0 0 0.45  0 0 0 0 0.25  0 0 0 0.5 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
}

/* ============================================================
   SEMANTIC ELEMENT STYLES
   ============================================================ */

body {
  margin: 0;
  background: var(--surface-page);
  color: var(--fg-1);
  font-family: var(--font-body);
  font-size: var(--t-body-size);
  line-height: var(--t-body-lh);
  font-feature-settings: 'liga', 'kern', 'onum';
  -webkit-font-smoothing: antialiased;
}

h1, .display-1 {
  font-family: var(--font-display);
  font-size: var(--t-display-1-size);
  line-height: var(--t-display-1-lh);
  letter-spacing: var(--t-display-1-tracking);
  font-weight: 400;
  color: var(--fg-1);
  margin: 0;
  text-transform: none;
}
.display-2 {
  font-family: var(--font-display);
  font-size: var(--t-display-2-size);
  line-height: var(--t-display-2-lh);
  letter-spacing: var(--t-display-2-tracking);
  font-weight: 400; color: var(--fg-1);
}
h2 {
  font-family: var(--font-display);
  font-size: var(--t-h1-size);
  line-height: var(--t-h1-lh);
  letter-spacing: var(--t-h1-tracking);
  font-weight: 400; color: var(--fg-1); margin: 0;
}
h3 {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--t-h2-size); line-height: var(--t-h2-lh);
  letter-spacing: var(--t-h2-tracking);
  color: var(--fg-1); margin: 0;
}
h4 {
  font-family: var(--font-body);
  font-size: var(--t-h3-size); line-height: var(--t-h3-lh);
  font-weight: 600; color: var(--fg-1); margin: 0;
}
p { margin: 0; color: var(--fg-2); text-wrap: pretty; }

.sign, .sublabel, .section-label {
  font-family: var(--font-sign);
  font-size: var(--t-sign-size); line-height: var(--t-sign-lh);
  letter-spacing: var(--t-sign-tracking);
  font-weight: 600; text-transform: uppercase;
  color: var(--fg-3);
}
.sign-strong {
  font-family: var(--font-sign);
  font-size: var(--t-sign-size); letter-spacing: var(--t-sign-tracking);
  font-weight: 800; text-transform: uppercase;
  color: var(--fg-accent);
}

.mono, .readout, .keybind {
  font-family: var(--font-mono);
  font-size: var(--t-mono-size); letter-spacing: var(--t-mono-tracking);
  font-weight: 500; color: var(--fg-2);
}
.readout-large {
  font-family: var(--font-mono); font-size: 22px;
  letter-spacing: 0.04em; font-weight: 700; color: var(--fg-accent);
  font-variant-numeric: tabular-nums;
}

a, .link {
  color: var(--fg-accent); text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--brass) 40%, transparent);
  transition: border-color 180ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
a:hover, .link:hover { border-bottom-color: var(--brass); }

em, .italic { font-style: normal; font-weight: 600; }
.italic-display {
  font-family: var(--font-display); font-weight: 400;
}

/* ============================================================
   BRAND PAGE CHROME — apply to the root of a brand-level
   surface (title screen, main menu, settings, achievements).
   Themes (theme-parlor etc.) layer ON TOP of this.
   ============================================================ */

.brand-room {
  background: var(--surface-page);
  background-image: var(--cribbage-holes);
  background-repeat: repeat;
  min-height: 100vh;
  position: relative;
}

/* ============================================================
   MOTION — the feel every Double Skunk page shares.
   ------------------------------------------------------------
   Above the chrome boundary on purpose. The landing page inlines this
   half and links no stylesheet, so this is the one place a rule reaches
   every page from without a second hand-kept copy.

   motion.js drives the tilt and the deal (see the notes at the top of
   that file); everything else here is plain CSS. The reduced-motion
   block at the end switches all of it off, and motion.js checks the
   same query before it holds anything back or leans anything.
   ============================================================ */

:root {
  --ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
  /* Overshoots and settles: a card set down on felt. */
  --ease-pop: cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* ---- keyboard focus ----
   Zero specificity (:focus-visible sits inside the :where), so every
   control that already draws its own ring (the menu toggle, learn
   buttons, gallery thumbs) keeps it, wherever its rule is. */
:where(a:focus-visible, button:focus-visible, summary:focus-visible,
  [tabindex]:not([tabindex="-1"]):focus-visible) {
  outline: 2px solid var(--brass-glow);
  outline-offset: 3px;
}

/* ---- CTA buttons ----
   The one copy, for every page: the landing page used to carry its own
   and the chrome half another. Hover lifts; a press sinks, and the drop
   shadow and bottom bevel squash with it like a key going down. The
   :active rules come after the :hover ones at the same specificity, so a
   mouse press wins over the hover it is also still in. */
.cta { display: inline-flex; align-items: center; gap: 12px; padding: 16px 22px; border-radius: 12px;
  font-family: var(--font-sign); font-weight: 800; font-size: 13px; letter-spacing: 0.18em;
  text-transform: uppercase; text-decoration: none; border: 0; cursor: pointer;
  transition: transform 80ms, box-shadow 160ms, background 160ms; }
.cta.pop { color: var(--chalk);
  background: linear-gradient(180deg, var(--brass-bright), var(--brass) 60%, var(--brass-deep));
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.30), inset 0 -2px 0 rgba(0,0,0,0.35), 0 10px 24px -8px color-mix(in srgb, var(--brass) 65%, transparent); }
.cta.pop:hover { transform: translateY(-2px);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.35), inset 0 -2px 0 rgba(0,0,0,0.35), 0 16px 32px -8px color-mix(in srgb, var(--brass) 75%, transparent); }
.cta.ghost { color: var(--chalk); background: transparent; box-shadow: inset 0 0 0 1px var(--chalk-4); }
.cta.ghost:hover { transform: translateY(-2px); box-shadow: inset 0 0 0 1px var(--chalk); background: rgba(241,234,208,0.03); }
.cta.pop:active, .cta.ghost:active { transform: translateY(1px); }
.cta.pop:active {
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.22), inset 0 -1px 0 rgba(0,0,0,0.30),
    0 3px 8px -4px color-mix(in srgb, var(--brass) 55%, transparent); }
.cta.ghost:active { background: rgba(241,234,208,0.06); }
.cta .glyph { font-size: 16px; line-height: 1; }
/* The header CTA's own rules stay in the chrome half (and the landing
   page's copy of it, which the drift tests hold together), so its press
   has to outrank a :hover that comes later in the cascade. */
.nav-cta.pop:hover:active, .nav-cta.pop:active { transform: translateY(1px);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.20), inset 0 -1px 0 rgba(0,0,0,0.30),
    0 2px 6px -4px color-mix(in srgb, var(--brass) 55%, transparent); }

/* ---- the phone menu drops in ---- */
.nav-menu[open] .nav-menu-panel { animation: menu-drop 170ms var(--ease-out); transform-origin: top right; }
@keyframes menu-drop {
  from { opacity: 0; transform: translateY(-6px) scale(0.97); }
  to { opacity: 1; transform: none; }
}

/* ---- cards that lean (motion.js) ----
   [data-tilt] takes the pointer and carries --rx/--ry/--mx/--my; .tilt is
   the face that turns, either that element or one inside it. The long
   springy transition is the settle when the pointer leaves; while it
   moves, the short linear one keeps the face under it.
   The 3D transform is only there while a pointer is on the card. At rest
   it is `none`, which the settle still eases back to, so a page of eighty
   cards is not eighty compositor layers.
   The timing is two variables rather than a transition, so a face that
   needs its own transition list writes `transform var(--tilt-t)
   var(--tilt-e)` into it and follows the pointer with no extra rule. */
.tilt { --tilt-t: 520ms; --tilt-e: var(--ease-pop); position: relative;
  transition: transform var(--tilt-t) var(--tilt-e); }
:is(.is-tilting .tilt, .tilt.is-tilting) { --tilt-t: 120ms; --tilt-e: linear;
  transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)); }
/* The light: a warm glint that sits under the pointer (motion.js
   measures the pointer against this face, not the larger card around it). */
.tilt::after { content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  border-radius: var(--tilt-radius, 4%); opacity: 0; transition: opacity 240ms;
  background: radial-gradient(circle at var(--mx, 50%) var(--my, 30%), rgba(255, 244, 214, 0.42), transparent 60%);
  mix-blend-mode: soft-light; }
:is(.is-tilting .tilt, .tilt.is-tilting)::after { opacity: 1; }
/* Foil, on the rare ones only: a band of colour that slides with the
   pointer across the face. */
.tilt:is([data-rarity="rare"], [data-rarity="legendary"], [data-rarity="mythic"])::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  border-radius: var(--tilt-radius, 4%); opacity: 0; transition: opacity 240ms;
  background: linear-gradient(115deg, transparent 22%, hsl(185 90% 65% / 0.55) 36%,
    hsl(275 85% 72% / 0.5) 47%, hsl(45 95% 66% / 0.55) 58%, transparent 74%);
  background-size: 280% 280%; background-position: var(--mx, 50%) var(--my, 50%);
  mix-blend-mode: color-dodge; }
:is(.is-tilting .tilt, .tilt.is-tilting):is([data-rarity="rare"], [data-rarity="legendary"], [data-rarity="mythic"])::before { opacity: 0.4; }
/* A finger has nothing to lean toward, so a tap presses the card instead. */
@media (hover: none) {
  [data-tilt]:active .tilt, .tilt[data-tilt]:active { --tilt-t: 90ms; transform: scale(0.97); }
}

/* ---- grids deal in (motion.js) ----
   Only cards that start below the fold are ever held, and only with JS
   and motion allowed. Individual transform properties, so a card's own
   hover transform and tilt still compose with the deal. Scoped to
   [data-deal] so the rule outranks the component stylesheets, which load
   after this file and set their own transitions. */
[data-deal] > .deal-hold { opacity: 0; translate: 0 22px; rotate: -2deg; scale: 0.96; }
[data-deal] > .deal-in {
  transition: opacity 280ms var(--ease-out), translate 440ms var(--ease-pop),
    rotate 440ms var(--ease-pop), scale 440ms var(--ease-pop);
  transition-delay: var(--deal-delay, 0ms);
}
@media print { [data-deal] > .deal-hold { opacity: 1; translate: none; rotate: none; scale: none; } }

/* ---- a stamp: the playtest confirmation, a finished learn chapter ---- */
@keyframes stamp {
  0% { opacity: 0; transform: scale(2.2) rotate(-16deg); }
  55% { opacity: 1; transform: scale(0.92) rotate(3deg); }
  100% { opacity: 1; transform: none; }
}

/* ---- reduced motion ----
   Everything still reaches its end state, at once. `:root` rather than
   `html` so it outranks the smooth scroll the chrome sets further down. */
@media (prefers-reduced-motion: reduce) {
  :root { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
  }
}

/* PAGE CHROME BOUNDARY */

/* ============================================================
   SHARED DOUBLE SKUNK PAGE CHROME
   ------------------------------------------------------------
   Shared Double Skunk page chrome (kb-bar, nav, footer). The CTA
   buttons used to live here too; they are in the brand half now, so the
   landing page shares one copy of them.
   Dresses the ds_kb_bar / ds_nav components, so it only matters to the
   pages that render them: the guides, the learn chapters, the dev log
   and the updates feed.

   This used to be chrome.css, a second render-blocking request on every
   one of those pages. The tokens above and these rules always shipped
   together, so they are one file and one request now.

   The landing page is the exception. It renders its own <html> with its
   own hand-written, localized chrome, so it inlines everything ABOVE the
   boundary marker and none of this. `ds_tokens/1` does that split, on the
   marker line above, and raises at compile time if the line is gone.
   Moving a rule across that line changes what the landing page gets.
   ============================================================ */

* { box-sizing: border-box; }
/* Beats any component rule that sets display, so `hidden` always hides. */
[hidden] { display: none !important; }
html, body { margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
  background: var(--skunk-ink);
  background-image: var(--cribbage-holes);
  background-size: 40px 40px;
  color: var(--fg-1);
  font-family: var(--font-body);
  min-height: 100vh;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}
.wrap { max-width: 1040px; margin: 0 auto; padding: 0 32px; }

/* ---- Kerrberry domain bar ---- */
.kb-bar { background: #08070a; border-bottom: 1px solid var(--border-hair);
  font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.02em; color: var(--fg-4); }
.kb-bar-inner { max-width: 1040px; margin: 0 auto; padding: 8px 32px;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.kb-sep { color: var(--fg-4); opacity: 0.5; }
.kb-here { color: var(--brass-glow); }
.kb-spacer { flex: 1; }
.kb-bar a { color: var(--fg-3); text-decoration: none; border-bottom: 1px dotted var(--fg-4); transition: color 160ms, border-color 160ms; }
.kb-bar a:hover { color: var(--chalk); border-bottom-color: var(--chalk-4); }

/* ---- Nav ---- */
.nav { position: sticky; top: 0; z-index: 50;
  background: color-mix(in oklch, var(--skunk-ink) 92%, transparent);
  backdrop-filter: blur(10px); border-bottom: 1px solid var(--border-hair); }
.nav-inner { max-width: 1040px; margin: 0 auto; padding: 12px 32px;
  display: flex; align-items: center; gap: 18px; position: relative; }
.nav-brand { display: inline-flex; align-items: center; gap: 12px; text-decoration: none; }
.nav-brand img { width: 38px; height: 38px; display: block; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.6)); }
.nav-brand .nb-name { font-family: var(--font-display); font-size: 17px; color: var(--chalk); line-height: 1; }
.nav-spacer { flex: 1; }
.nav-link { font-family: var(--font-sign); font-weight: 800; font-size: 12px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--fg-3);
  text-decoration: none; border-bottom: 1px dotted var(--fg-4); transition: color 160ms, border-color 160ms; }
.nav-link:hover { color: var(--chalk); border-bottom-color: var(--chalk-4); }
.nav-link[aria-current="page"] { color: var(--chalk); border-bottom: 2px solid var(--brass); }
.nav-cta { display: inline-flex; align-items: center; gap: 8px; padding: 10px 16px;
  border-radius: 10px; font-family: var(--font-sign); font-weight: 800; font-size: 12px;
  letter-spacing: 0.18em; text-transform: uppercase; text-decoration: none; border: 0; cursor: pointer;
  transition: transform 80ms, box-shadow 160ms, background 160ms, color 160ms; }
.nav-cta.pop { color: var(--chalk);
  background: linear-gradient(180deg, var(--brass-bright), var(--brass) 65%, var(--brass-deep));
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.25), inset 0 -2px 0 rgba(0,0,0,0.30), 0 6px 14px -6px color-mix(in srgb, var(--brass) 55%, transparent); }
.nav-cta.pop:hover { transform: translateY(-1px);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.30), inset 0 -2px 0 rgba(0,0,0,0.30), 0 10px 22px -6px color-mix(in srgb, var(--brass) 70%, transparent); }
.nav-cta:active { transform: translateY(1px); }
.nav-cta .star { color: var(--chalk); }

/* ---- Mobile menu ----
   Only ever visible under 720px, where .nav-link is hidden. The <details>
   element owns the open state, the keyboard, and the expanded/collapsed
   announcement, so the whole menu costs no script. */
.nav-menu { display: none; }
.nav-menu-toggle { display: inline-flex; align-items: center; gap: 9px; list-style: none;
  padding: 9px 13px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--chalk-4); color: var(--chalk);
  font-family: var(--font-sign); font-weight: 800; font-size: 12px;
  letter-spacing: 0.16em; text-transform: uppercase; }
.nav-menu-toggle::-webkit-details-marker { display: none; }
.nav-menu-toggle:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
.nav-menu[open] .nav-menu-toggle { border-color: var(--chalk); }
.nav-menu-glyph { width: 15px; height: 2px; background: currentColor;
  box-shadow: 0 -5px 0 currentColor, 0 5px 0 currentColor; }
.nav-menu-panel { position: absolute; right: 32px; top: calc(100% + 6px); z-index: 60;
  display: flex; flex-direction: column; min-width: 200px; padding: 8px;
  border-radius: 12px; background: var(--surface-base);
  border: 1px solid var(--border-hair); box-shadow: 0 18px 34px -12px rgba(0,0,0,0.8); }
.nav-menu-link { padding: 11px 12px; border-radius: 8px; border: 0; text-decoration: none; color: var(--fg-2);
  font-family: var(--font-sign); font-weight: 800; font-size: 13px;
  letter-spacing: 0.16em; text-transform: uppercase; }
.nav-menu-link:hover { background: var(--surface-raised); color: var(--chalk); }
.nav-menu-link[aria-current="page"] { color: var(--chalk); box-shadow: inset 2px 0 0 var(--brass); }

/* ---- Footer ---- */
.foot { background: #08070a; border-top: 1px solid var(--border-hair); padding: 36px 0 32px; margin-top: 24px; }
.foot-inner { display: flex; align-items: center; justify-content: space-between; gap: 24px; flex-wrap: wrap; }
.foot-brand { display: inline-flex; align-items: center; gap: 12px; }
.foot-brand img { width: 32px; height: 32px; }
.foot-brand .fb-name { font-family: var(--font-display); font-size: 15px; color: var(--chalk); line-height: 1; }
.foot-brand .fb-by { font-family: var(--font-sign); font-weight: 800; font-size: 10px; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--fg-4); margin-top: 4px; display: block; line-height: 1; }
.foot-brand .fb-by a { color: var(--fg-3); text-decoration: none; border-bottom: 1px dotted var(--fg-4); }
.foot-brand .fb-by a:hover { color: var(--chalk); }
.foot-fine { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em; color: var(--fg-4); }

/* ---- Responsive ---- */
@media (max-width: 720px) {
  .nav-link { display: none; }
  .nav-menu { display: block; }
  /* backdrop-filter repaints a blurred copy of the page under the bar on
     every scroll frame, which a cheap Android feels. The header is opaque
     at this width, so there is nothing left to blur. */
  .nav { background: color-mix(in oklch, var(--skunk-ink) 92%, var(--skunk-ink-3));
    backdrop-filter: none; }
  /* Brand, menu and CTA have to share a 320px row. The medallion holds
     the brand on its own here; the wordmark would cost the menu its
     place, and on the landing page it is repeated three times the size
     in the hero directly below. Gutters stay at 32px so the header
     still lines up with the body content under it. */
  .nav-inner { padding: 10px 32px; gap: 12px; }
  .nav-brand img { width: 32px; height: 32px; }
  .nav-brand .nb-name { display: none; }
  .nav-cta { padding: 9px 12px; letter-spacing: 0.12em; }
}

/* The brand, the menu and the CTA still have to fit a 320px phone with a
   32px gutter, and `body { overflow-x: hidden }` would clip the CTA
   rather than let you scroll to it. This buys the last few pixels
   without touching 375px and up. */
@media (max-width: 360px) {
  .nav-inner { gap: 10px; }
  .nav-menu-toggle { padding: 9px 10px; gap: 7px; letter-spacing: 0.08em; }
  .nav-cta { padding: 9px 10px; gap: 6px; letter-spacing: 0.06em; }
}
