/* ==========================================================================
   Victoria 3 RP — base stylesheet
   Drop-in for a Django/Flask project. Images referenced relative to this file
   as ../img/*.png (i.e. static/css/victoria.css + static/img/*.png).
   ========================================================================== */

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

:root {
  /* Surfaces — dark chrome */
  --v-bg:            #14100d;
  --v-bg-2:          #1d1713;
  --v-panel-dark:    #241c17;
  --v-panel-dark-2:  #2e241d;
  --v-rail:          #1a1410;

  /* Surfaces — parchment */
  --v-paper:         #ece2c9;
  --v-paper-2:       #dcd0b2;
  --v-paper-3:       #cfc09c;
  --v-paper-sunken:  #d3c6a6;

  /* Ink */
  --v-ink:           #2b241c;
  --v-ink-2:         #4a4034;
  --v-ink-3:         #6d6151;
  --v-cream:         #ece2cc;
  --v-cream-2:       #b9ab90;

  /* Metals & accents */
  --v-gold:          #c2a15a;
  --v-gold-bright:   #e3ca8d;
  --v-gold-dim:      #8a7038;
  --v-maroon:        #4a1f24;
  --v-maroon-2:      #6b2b2f;
  --v-red:           #8c2b26;
  --v-red-bright:    #a8362f;
  --v-green:         #4f6b3a;
  --v-blue:          #3c5a72;

  /* Type */
  --v-font-display: "Cinzel", "Trajan Pro", Georgia, serif;
  --v-font-body:    "EB Garamond", Georgia, "Times New Roman", serif;
  --v-font-mono:    ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --v-fs-xs:   0.75rem;
  --v-fs-sm:   0.875rem;
  --v-fs-base: 1rem;
  --v-fs-md:   1.125rem;
  --v-fs-lg:   1.375rem;
  --v-fs-xl:   1.75rem;
  --v-fs-2xl:  2.25rem;
  --v-fs-3xl:  clamp(2.25rem, 5vw, 3.75rem);

  /* Space */
  --v-1: 4px;  --v-2: 8px;  --v-3: 12px; --v-4: 16px;
  --v-5: 24px; --v-6: 32px; --v-7: 48px; --v-8: 64px;

  /* Structure */
  --v-radius:     3px;
  --v-radius-lg:  6px;
  --v-border:     1px solid var(--v-gold-dim);
  --v-border-ink: 1px solid rgba(43, 36, 28, 0.22);
  --v-shadow:     0 2px 10px rgba(0, 0, 0, 0.45);
  --v-shadow-lg:  0 12px 40px rgba(0, 0, 0, 0.6);
  --v-topbar-h:   64px;
  --v-container:  1360px;

  /* Textures */
  --v-tex-paper:  url("../img/paper_texture.63599a30c205.png");
  --v-tex-damask: url("../img/fancy_pattern.446d371ac595.png");
  --v-tex-cloth:  url("../img/cloth_texture.abefa4e5d565.png");
  --v-tex-velvet: url("../img/velvet_texture.a60da638c0c4.png");
  --v-tex-tile:   url("../img/bg_tiling_pattern.df9b8f7832f2.png");
  --v-tex-slate:  url("../img/paper_bg.ca8ac1a5e5bd.png");
  --v-ornament:   url("../img/divider_decoration.1208a8dc257a.png");
  --v-frame-gold: url("../img/popup_bg_frame.f560b6edd24f.png");
  --v-frame-thin: url("../img/simple_frame.3649276b1714.png");
}

/* --- 2. Reset ------------------------------------------------------------ */

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

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

/* In-page anchor targets (`#event-42`, any future `#fragment` link) land
   below the sticky `.v-topbar` instead of flush against it, which hid the
   target's own first line under 64px of header (item 14, feedback-
   2026-08-19: "you can't see the article's title, just the line below
   it" — templates/content/partials/recent_events.html's news links to
   `timeline/_event_collapsed.html`'s `#event-<id>`, but this is generic:
   every anchor on the site sits under the same sticky bar).
   `scroll-padding-top` on the document's scrolling box is honoured by
   both the browser's own `:target` fragment navigation and script-driven
   `scrollIntoView()`, so one rule fixes every anchor site at once rather
   than hand-adding `scroll-margin-top` per target class. Derived from
   `--v-topbar-h` (never a literal 64px) plus a little breathing room so
   the target's heading clears the bar rather than touching it, and stays
   correct if that token ever changes.

   No override is needed at the 820px mobile breakpoint below: the bar's
   nav collapses behind `.v-topbar__toggle` until tapped open (`.v-nav`
   stays `display: none` at rest), so the resting bar's content — just the
   40px brand seal — never exceeds the `min-height: var(--v-topbar-h)`
   that sets this token; only the *open* mobile menu grows taller, and
   nobody is mid-navigation to a fragment while that panel is open. */
html {
  scroll-padding-top: calc(var(--v-topbar-h) + var(--v-4));
}

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--v-font-body);
  font-size: var(--v-fs-base);
  line-height: 1.55;
  color: var(--v-cream);
  background-color: var(--v-bg);
  text-wrap: pretty;
  -webkit-font-smoothing: antialiased;
}

/* Damask wallpaper, darkened and warmed */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  background-image: var(--v-tex-damask);
  background-size: 480px;
  opacity: 0.10;
  filter: sepia(1) saturate(2.2) hue-rotate(-8deg);
  pointer-events: none;
}
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: radial-gradient(120% 90% at 50% 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0.7) 100%);
  pointer-events: none;
}

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

h1, h2, h3, h4, h5 {
  font-family: var(--v-font-display);
  font-weight: 600;
  letter-spacing: 0.01em;
  margin: 0 0 var(--v-3);
  line-height: 1.2;
}
h1 { font-size: var(--v-fs-2xl); }
h2 { font-size: var(--v-fs-xl); }
h3 { font-size: var(--v-fs-lg); }
h4 { font-size: var(--v-fs-md); }
p  { margin: 0 0 var(--v-3); }

a { color: var(--v-gold-bright); text-decoration: none; }
a:hover { color: #f3e2b4; text-decoration: underline; }

hr {
  border: 0;
  height: 2px;
  background: var(--v-gold-dim);
  opacity: 0.6;
  margin: var(--v-5) 0;
}

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

.v-container {
  width: 100%;
  max-width: var(--v-container);
  margin-inline: auto;
  padding-inline: var(--v-4);
}
.v-container--narrow { max-width: 860px; }

.v-page { padding-block: var(--v-6) var(--v-8); }

.v-stack       { display: flex; flex-direction: column; gap: var(--v-4); }
.v-stack--tight{ gap: var(--v-2); }
.v-stack--loose{ gap: var(--v-6); }
.v-row {
  display: flex;
  align-items: center;
  gap: var(--v-3);
  flex-wrap: wrap;
}
.v-row--between { justify-content: space-between; }
.v-spacer { flex: 1 1 auto; }

.v-grid { display: grid; gap: var(--v-4); }
.v-grid--2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.v-grid--3 { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.v-grid--4 { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }

/* Sidebar + content shell, used by World and Dashboard */
.v-shell {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  gap: var(--v-4);
  align-items: start;
}
.v-shell--wide-aside { grid-template-columns: 260px minmax(0, 1fr) 300px; }

/* --- 4. Top bar ---------------------------------------------------------- */

.v-topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  min-height: var(--v-topbar-h);
  display: flex;
  align-items: center;
  gap: var(--v-4);
  padding-inline: var(--v-4);
  background:
    linear-gradient(180deg, rgba(255,255,255,0.06), rgba(0,0,0,0.3)),
    var(--v-tex-velvet) center / cover,
    var(--v-maroon);
  background-blend-mode: normal, multiply, normal;
  border-bottom: 2px solid var(--v-gold-dim);
  box-shadow: var(--v-shadow);
}
.v-topbar::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 2px;
  background: var(--v-gold-dim);
  opacity: 0.7;
  pointer-events: none;
}

.v-brand {
  display: flex;
  align-items: center;
  gap: var(--v-3);
  font-family: var(--v-font-display);
  font-size: var(--v-fs-md);
  color: var(--v-gold-bright);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.v-brand:hover { text-decoration: none; }
.v-brand__seal {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border: 2px solid var(--v-gold);
  border-radius: 50%;
  background: rgba(0,0,0,0.35);
  font-family: var(--v-font-display);
  font-size: var(--v-fs-sm);
  color: var(--v-gold-bright);
}

.v-nav { display: flex; gap: var(--v-1); flex-wrap: wrap; }
.v-nav__link {
  display: block;
  padding: var(--v-2) var(--v-4);
  font-family: var(--v-font-display);
  font-size: var(--v-fs-sm);
  letter-spacing: 0.06em;
  color: var(--v-cream-2);
  border-radius: var(--v-radius);
  border: 1px solid transparent;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.v-nav__link:hover {
  color: var(--v-gold-bright);
  background: rgba(0,0,0,0.25);
  text-decoration: none;
}
.v-nav__link.is-active {
  color: var(--v-gold-bright);
  background: rgba(0,0,0,0.4);
  border-color: var(--v-gold-dim);
}

.v-topbar__user {
  display: flex; align-items: center; gap: var(--v-2);
  padding: var(--v-1) var(--v-3);
  border: var(--v-border);
  border-radius: 999px;
  background: rgba(0,0,0,0.3);
  color: var(--v-cream);
  font-size: var(--v-fs-sm);
}
.v-avatar {
  width: 28px; height: 28px; flex: none;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1px solid var(--v-gold-dim);
  background: var(--v-panel-dark-2);
  font-family: var(--v-font-display);
  font-size: var(--v-fs-xs);
  color: var(--v-gold-bright);
}

/* Mobile nav toggle — checkbox-free, JS adds .is-open */
.v-topbar__toggle {
  display: none;
  padding: var(--v-2) var(--v-3);
  border: var(--v-border);
  border-radius: var(--v-radius);
  background: rgba(0,0,0,0.3);
  color: var(--v-gold-bright);
  font: inherit;
  cursor: pointer;
}

/* --- 5. Panels ----------------------------------------------------------- */

/* Dark panel: rails, sidebars, chrome */
.v-panel {
  position: relative;
  /* Own text colour explicitly (item 20, feedback-2026-08-18: the timeline
     toolbar's "Major events only" filter label — plain text with no class
     of its own — was inheriting `.v-paper`'s dark ink from the ancestor
     `.v-paper--framed` this panel sits inside on /timeline/, over its own
     dark background: unreadable. Every other dark-surface component here
     (`.v-plaque`, `.v-rail__item`, `.v-event__card`'s siblings) sets its
     own colour rather than trusting an ambient light-surface parent not to
     be there; `.v-panel` was the one omission. */
  color: var(--v-cream);
  background:
    linear-gradient(180deg, rgba(255,255,255,0.04), rgba(0,0,0,0.22)),
    var(--v-tex-cloth) center / 380px,
    var(--v-panel-dark);
  background-blend-mode: normal, overlay, normal;
  border: var(--v-border);
  border-radius: var(--v-radius);
  box-shadow: var(--v-shadow);
}
.v-panel__head {
  display: flex; align-items: center; gap: var(--v-3);
  padding: var(--v-3) var(--v-4);
  /* Burgundy/wine of the main header (item 19/37/62, feedback-2026-08-18:
     "Filters"/"1836 World Map"/"XX nations" heads were rendering the
     blurry `paper_bg_dark.png` texture — owner: "replace it with the
     burgundy/wine color of the main header, that one looks good"). Same
     token `.v-topbar` uses, flat rather than its velvet-texture treatment
     — a panel head is a small strip, not a hero band. */
  background: var(--v-maroon);
  border-bottom: 1px solid rgba(194, 161, 90, 0.35);
  font-family: var(--v-font-display);
  font-size: var(--v-fs-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--v-gold-bright);
}
.v-panel__body { padding: var(--v-4); }

/* Parchment panel: the main content surface */
.v-paper {
  position: relative;
  color: var(--v-ink);
  background-color: var(--v-paper);
  background-image: var(--v-tex-paper);
  background-size: 520px;
  background-blend-mode: soft-light;
  border: 1px solid var(--v-gold-dim);
  border-radius: var(--v-radius);
  box-shadow: var(--v-shadow), inset 0 0 70px rgba(140, 112, 62, 0.22);
  padding: var(--v-5);
}
.v-paper--flush { padding: 0; }
.v-paper h1, .v-paper h2, .v-paper h3, .v-paper h4 { color: var(--v-ink); }
.v-paper a { color: #7a3a1e; }
.v-paper hr { opacity: 0.8; }

/* Gilded corner ornaments — opt in with .v-paper--framed */
.v-paper--framed { padding: var(--v-6); }
.v-paper--framed::before,
.v-paper--framed::after {
  content: "";
  position: absolute;
  width: 70px; height: 12px;
  background: var(--v-ornament) center / contain no-repeat;
  opacity: 0.85;
  pointer-events: none;
}
.v-paper--framed::before { top: 14px;    left: 18px; }
.v-paper--framed::after  { bottom: 14px; right: 18px; transform: rotate(180deg); }

/* Gilded 9-slice frame for hero-weight blocks */
.v-framed-gold {
  border: 40px solid transparent;
  border-image: var(--v-frame-gold) 74 fill / 40px / 0 stretch;
}

/* Thin cast-metal frame — for cards that need to read as an inset panel */
.v-framed-thin {
  border: 14px solid transparent;
  border-image: var(--v-frame-thin) 30 fill / 14px / 0 round;
}

/* Page-title wrap — formerly a gilded arch (`top_header_frame.png`) behind
   every title ("Shattered Congress", "Timeline", "Rules", "The Archive Has
   Faltered"): removed, it read as a blurry smear at real size (items 5/16/
   85, feedback-2026-08-18). The title keeps its size, centring and
   spacing; it now sits directly on the page with no background at all. */
.v-crest {
  width: min(560px, 100%);
  margin: 0 auto var(--v-3);
  text-align: center;
}
.v-crest > * { margin: 0; font-size: clamp(1.5rem, 4.6vw, 2.25rem); line-height: 1.1; }

/* Slate plaque — dark inset on parchment */
.v-plaque {
  color: var(--v-cream);
  background: var(--v-tex-slate) center / 320px, var(--v-panel-dark);
  background-blend-mode: overlay, normal;
  border: 1px solid var(--v-gold-dim);
  border-radius: var(--v-radius);
  padding: var(--v-4);
}
.v-plaque .v-stat__key, .v-plaque .v-muted { color: var(--v-cream-2); }
.v-plaque .v-figure__key { color: var(--v-cream-2); }
.v-plaque .v-figure__val { color: var(--v-gold-bright); }
.v-plaque .v-stat { border-bottom-color: rgba(194,161,90,0.2); }

/* --- 6. Headings & dividers ---------------------------------------------- */

.v-title {
  font-family: var(--v-font-display);
  text-align: center;
  margin-bottom: var(--v-2);
}
.v-title--hero { font-size: var(--v-fs-3xl); line-height: 1.08; }

.v-subtitle {
  text-align: center;
  font-size: var(--v-fs-md);
  color: var(--v-ink-2);
  font-style: italic;
}

/* Centred rule under a title — formerly the blurry `divider_fancy.png`
   bitmap (item 16/85, feedback-2026-08-18). A plain CSS-drawn line in the
   existing gold ink, same margins; `.v-rule` reads as a short flourish
   (narrow, centred) the way the bitmap's transparent edges did, `--wide`
   spans the full width the way `divider_gold.png` did at
   `background-size: 100% 100%`. */
.v-rule {
  height: 2px;
  width: min(220px, 60%);
  background: var(--v-gold-dim);
  opacity: 0.6;
  margin: var(--v-4) auto;
}
.v-rule--wide { width: 100%; }

/* Label with rules either side */
.v-legend {
  display: flex; align-items: center; gap: var(--v-3);
  font-family: var(--v-font-display);
  font-size: var(--v-fs-lg);
  color: inherit;
  margin: 0 0 var(--v-4);
}
.v-legend::before, .v-legend::after {
  content: ""; flex: 1 1 0;
  height: 2px;
  background: var(--v-gold-dim);
  opacity: 0.65;
}

.v-eyebrow {
  font-family: var(--v-font-display);
  font-size: var(--v-fs-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--v-ink-3);
}
.v-panel .v-eyebrow, .v-topbar .v-eyebrow { color: var(--v-cream-2); }

.v-quote {
  font-style: italic;
  text-align: center;
  font-size: var(--v-fs-md);
  color: var(--v-ink-2);
  max-width: 46ch;
  margin-inline: auto;
}

/* --- 7. Buttons & forms -------------------------------------------------- */

.v-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--v-2);
  min-height: 44px;
  padding: var(--v-2) var(--v-5);
  font-family: var(--v-font-display);
  font-size: var(--v-fs-sm);
  letter-spacing: 0.05em;
  color: var(--v-cream);
  background:
    linear-gradient(180deg, rgba(255,255,255,0.14), rgba(0,0,0,0.28)),
    var(--v-maroon-2);
  border: 1px solid var(--v-gold-dim);
  border-radius: var(--v-radius);
  cursor: pointer;
  transition: filter 0.15s, border-color 0.15s;
}
.v-btn:hover { filter: brightness(1.18); border-color: var(--v-gold); text-decoration: none; }
.v-btn:active { filter: brightness(0.92); }
.v-btn:focus-visible { outline: 2px solid var(--v-gold-bright); outline-offset: 2px; }
.v-btn[disabled], .v-btn.is-disabled { opacity: 0.45; pointer-events: none; }

.v-btn--primary { background: linear-gradient(180deg, rgba(255,255,255,0.16), rgba(0,0,0,0.3)), var(--v-red); }
.v-btn--ghost   { background: rgba(0,0,0,0.25); color: var(--v-gold-bright); }
.v-btn--paper   { background: linear-gradient(180deg, #f0e7d0, var(--v-paper-3)); color: var(--v-ink); }
.v-btn--sm      { min-height: 34px; padding: var(--v-1) var(--v-3); font-size: var(--v-fs-xs); }
.v-btn--block   { width: 100%; }

/* Host-mode banner (dashboard shell, PAGES.md §6): same `.v-stale-banner`
   component (`site.css`) with the maroon-2 background swapped for red, so
   the "you are editing with Host/GA authority" notice reads distinctly
   from an ordinary stale-data banner rather than forking the component with
   an inline style (R17/N1, feedback-2026-08-18). */
.v-stale-banner--host { background: var(--v-red); }

/* A `<a class="v-btn">` keeps its own text color no matter what surrounding
   link-color rule it sits inside (`.v-paper a`, `.v-panel a`, …) — a button
   is a component, not a bare link, and must never inherit ambient link
   styling. Without this, `.v-paper a`'s `color: #7a3a1e` (equal specificity,
   declared earlier in this file) wins over plain `.v-btn`'s `color:
   var(--v-cream)` for exactly the buttons that sit on parchment — the
   framed hero's "Join the campaign"/"Read the rules" CTAs rendered as
   near-invisible dark-on-dark text (owner report, feedback-2026-08-18
   WO-F3.2, confirmed by screenshot: apps.content is not the CSS's owner but
   this file is the one place the fix belongs, per that work order). Same
   compound-selector trick, one rule per variant so each keeps its intended
   color. */
a.v-btn              { color: var(--v-cream); }
a.v-btn.v-btn--ghost { color: var(--v-gold-bright); }
a.v-btn.v-btn--paper { color: var(--v-ink); }

/* Ghost button on parchment (items 12/56, feedback-2026-08-18: "See the
   full timeline" on the home page, "See full history" on the country
   sheet — both `.v-btn--ghost` sitting on `.v-paper`). `.v-btn--ghost`'s
   `rgba(0,0,0,0.25)` wash and forced gold-bright text (above) are tuned
   for the dark chrome the mock built them for; over light parchment the
   wash barely darkens the paper at all, so pale gold on near-parchment
   measured 1.24:1 — nowhere near the 4.5:1 AA floor. Reuse the
   already-AA-passing `.v-btn--paper` recipe instead of inventing a third
   button treatment: the same gradient/ink pairing used on the country
   sheet's own "Edit" buttons. `.v-paper a.v-btn.v-btn--ghost` (three
   classes) is required, not just `.v-paper .v-btn--ghost`, to out-rank
   `a.v-btn.v-btn--ghost` two classes above. */
.v-paper .v-btn--ghost {
  background: linear-gradient(180deg, #f0e7d0, var(--v-paper-3));
  border-color: rgba(43, 36, 28, 0.35);
}
.v-paper a.v-btn.v-btn--ghost,
.v-paper button.v-btn.v-btn--ghost { color: var(--v-ink); }

/* …but a `.v-panel` nested inside a `.v-paper` is still dark chrome, so the
   parchment treatment above must not leak into it — the timeline's Clear
   filters control lives there. Ordered after the rule it overrides. */
.v-panel .v-btn--ghost,
.v-paper .v-panel a.v-btn.v-btn--ghost,
.v-paper .v-panel button.v-btn.v-btn--ghost {
  background: rgba(0,0,0,0.25);
  border-color: var(--v-gold-dim);
  color: var(--v-gold-bright);
}

.v-field { display: flex; flex-direction: column; gap: var(--v-1); }
.v-label {
  font-family: var(--v-font-display);
  font-size: var(--v-fs-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-ink-3);
}
.v-panel .v-label { color: var(--v-cream-2); }

.v-input, .v-select, .v-textarea {
  width: 100%;
  min-height: 44px;
  padding: var(--v-2) var(--v-3);
  font-family: var(--v-font-body);
  font-size: var(--v-fs-base);
  color: var(--v-ink);
  background: rgba(255, 252, 240, 0.55);
  border: 1px solid rgba(43, 36, 28, 0.35);
  border-radius: var(--v-radius);
}
.v-textarea { min-height: 140px; resize: vertical; line-height: 1.6; }
.v-input:focus, .v-select:focus, .v-textarea:focus {
  outline: none;
  border-color: var(--v-gold-dim);
  box-shadow: 0 0 0 2px rgba(194, 161, 90, 0.35);
}
.v-panel .v-input, .v-panel .v-select, .v-panel .v-textarea {
  color: var(--v-cream);
  background: rgba(0, 0, 0, 0.35);
  border-color: var(--v-gold-dim);
}
.v-panel .v-input::placeholder { color: var(--v-ink-3); }

.v-select {
  appearance: none;
  padding-right: var(--v-6);
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 18px) 50%, calc(100% - 12px) 50%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
/* `<option>` popups are UA-rendered chrome that most browsers do not tint
   from the closed `<select>`'s own `background` (translucent values in
   particular are routinely flattened to an opaque system default, usually
   white) even though they do inherit `color` — the mismatch item 21
   reported as "region names in the filter box, light yellow on white": the
   timeline region multi-select's `.v-panel .v-select` colour (cream) stayed,
   but its background didn't carry into the open list. Setting `color`
   *and* an opaque `background-color` directly on `option` is the one
   reliably cross-browser way to theme the popup itself, so both contexts
   (paper-toned default, panel-toned when nested in `.v-panel`) stay legible
   regardless of what the closed control shows. */
.v-select option {
  color: var(--v-ink);
  background-color: var(--v-paper);
}
.v-panel .v-select option {
  color: var(--v-cream);
  background-color: var(--v-panel-dark);
}

.v-search { position: relative; flex: 1 1 200px; }

/* --- 8. Tabs, rails, lists ------------------------------------------------ */

.v-tabs {
  display: flex;
  gap: var(--v-1);
  flex-wrap: wrap;
  border-bottom: 1px solid rgba(43, 36, 28, 0.3);
  margin-bottom: var(--v-4);
}
.v-tab {
  padding: var(--v-2) var(--v-4);
  font-family: var(--v-font-display);
  font-size: var(--v-fs-sm);
  /* Cream by default because most tab rows (reference subnav, leaderboard
     categories) sit bare on the dark page; `.v-paper .v-tab` below restores
     ink for the one row that sits on parchment, the country sheet's. The
     old single ink-3 default measured 2.78:1 on chrome (QA 2026-08-19,
     item 86). */
  color: var(--v-cream-2);
  background: transparent;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: var(--v-radius) var(--v-radius) 0 0;
  cursor: pointer;
  white-space: nowrap;
}
.v-paper .v-tab { color: var(--v-ink-3); }

/* Hover follows the same ground rule as the resting colour above — ink
   would be invisible on the dark page rows. (`.v-tab.is-active` needs no
   pairing: it paints its own near-white background under the ink.) */
.v-tab:hover { color: var(--v-cream); text-decoration: none; }
.v-paper .v-tab:hover { color: var(--v-ink); }
.v-tab.is-active {
  color: var(--v-ink);
  background: rgba(255, 250, 236, 0.6);
  border-color: rgba(43, 36, 28, 0.3);
  box-shadow: inset 0 2px 0 var(--v-gold);
}

/* Scrollable rail of selectable rows (country list, nation menu) */
.v-rail { max-height: 620px; overflow-y: auto; }
.v-rail__item {
  display: flex;
  align-items: center;
  gap: var(--v-3);
  width: 100%;
  padding: var(--v-2) var(--v-3);
  min-height: 44px;
  color: var(--v-cream);
  background: rgba(0,0,0,0.15);
  border: 1px solid transparent;
  border-left: 3px solid transparent;
  border-radius: var(--v-radius);
  font-family: var(--v-font-body);
  font-size: var(--v-fs-base);
  text-align: left;
  cursor: pointer;
}
.v-rail__item.is-disabled { opacity: 0.45; pointer-events: none; }
.v-rail__item:hover { background: rgba(194,161,90,0.12); text-decoration: none; }
.v-rail__item.is-active {
  background: rgba(194,161,90,0.2);
  border-color: var(--v-gold-dim);
  border-left-color: var(--v-gold);
  color: var(--v-gold-bright);
}
.v-rail__item .v-spacer + span { color: var(--v-cream-2); font-size: var(--v-fs-sm); }

/* Flag chip — the `<img class="v-flag">` that `{% country_flag %}`
   (apps/common/templatetags/flags.py) renders at all five of the owner's
   flag placements. It began as an empty `<span class="v-flag">`
   placeholder box and the span-friendly declarations are kept so the
   class still degrades to a plain block if one is ever needed again;
   `object-fit` only affects a replaced element, so it is inert on a span.
   No template renders the span form today. */
.v-flag {
  display: inline-block;
  width: 30px; height: 20px; flex: none;
  border: 1px solid rgba(0,0,0,0.5);
  border-radius: 2px;
  background: var(--v-panel-dark-2);
  box-shadow: 0 1px 2px rgba(0,0,0,0.5);
  object-fit: cover;
  vertical-align: -4px;
}
/* The country sheet's header flag is a page identifier, not a row chip, so
   it takes the space the old placeholder box held (QA 2026-08-18,
   templates/world/country_sheet.html). Sizing only — border, shadow and
   `object-fit` stay the shared `.v-flag` treatment. */
.v-country-header > .v-flag { width: 66px; height: 44px; }

/* --- 9. Data display ------------------------------------------------------ */

.v-stat {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--v-3);
  padding: var(--v-2) 0;
  border-bottom: var(--v-border-ink);
}
.v-stat:last-child { border-bottom: none; }
.v-stat__key { font-size: var(--v-fs-sm); color: var(--v-ink-3); }
.v-panel .v-stat__key { color: var(--v-cream-2); }
.v-panel .v-stat { border-bottom-color: rgba(194,161,90,0.18); }
.v-stat__val { font-family: var(--v-font-display); font-size: var(--v-fs-base); }
/* Signed stat colouring (feedback-2026-08-18 WO-F5.2, owner: the country
   sheet's Cash figure is green when positive, red when negative; weekly
   income/expenses carry these same classes but fixed rather than sign-
   driven — see templates/world/_country_overview.html). */
.v-stat__val--positive { color: var(--v-green); }
.v-stat__val--negative { color: var(--v-red); }

/* Big figure block (Start Date / Current Year / …) */
.v-figure { text-align: center; padding: var(--v-3) var(--v-2); }
.v-figure__key {
  display: block;
  font-size: var(--v-fs-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-ink-3);
}
.v-figure__val {
  display: block;
  font-family: var(--v-font-display);
  font-size: var(--v-fs-xl);
  line-height: 1.1;
}

.v-table { width: 100%; border-collapse: collapse; font-size: var(--v-fs-sm); }
.v-table th, .v-table td { padding: var(--v-2) var(--v-3); text-align: left; }
.v-table th {
  font-family: var(--v-font-display);
  font-size: var(--v-fs-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--v-ink-3);
  border-bottom: 1px solid rgba(43,36,28,0.35);
}
.v-table tbody tr { border-bottom: var(--v-border-ink); }
.v-table tbody tr:hover { background: rgba(194,161,90,0.12); }
/* `overflow-x: auto` is the deliberate choice for every wide `.v-table`
   (roster, leaderboards, council actions, modifiers, …) rather than a
   stacked-card layout — these are dense numeric grids where a card
   layout would just re-lay the same header/value pairs vertically per
   row, at several times the scroll length, for no readability gain.
   Left undecorated, though, the scrollbar is invisible until a pointer
   hovers it or a finger is already on it (found on the council "Actions
   this week" and rep-leaderboard tables at 375px: extra columns were
   simply gone, no visual hint more existed — confirmed structurally
   scrollable via scrollWidth > clientWidth, just undiscoverable). A thin
   *non-overlay* scrollbar is a permanent "there's more" cue that costs
   nothing when the table already fits (no overflow, no scrollbar drawn) —
   it covers pointer browsers (Firefox everywhere via `scrollbar-width`,
   Chrome/Edge on platforms that don't force overlay scrollbars). Touch
   platforms (iOS/Android, and this project's own headless-Chromium
   screenshot rig) render overlay scrollbars that CSS cannot force
   visible outside a live touch/scroll gesture, so the tables whose
   column count is fixed and reliably wider than a phone viewport
   (modifiers, council actions, rep leaderboards — never roster or the
   3-column leaderboards, which fit) additionally carry a static
   `.v-table__hint` caption, shown only below the width where these
   specific tables actually overflow (measured, see the caption's own
   media query below — not the tablet breakpoint: at 768px these six
   columns already fit their column, so showing "swipe" there would be
   pointing at nothing). */
.v-table__wrap {
  overflow-x: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--v-gold-dim) transparent;
}
.v-table__wrap::-webkit-scrollbar { height: 6px; }
.v-table__wrap::-webkit-scrollbar-track { background: transparent; }
.v-table__wrap::-webkit-scrollbar-thumb {
  background: var(--v-gold-dim);
  border-radius: 3px;
}
.v-table__hint {
  display: none;
  margin: var(--v-1) 0 0;
  font-size: var(--v-fs-xs);
  color: var(--v-ink-3);
  text-align: right;
}
.v-panel .v-table__hint { color: var(--v-cream-2); }
/* 620px, not the shared 520/820 breakpoints: measured directly against
   the widest of these tables (modifiers, 6 columns) inside the country
   sheet's single-column layout — it still overflows at 600px content
   width and stops by ~700px, so this is the caption's own evidence-based
   cutoff rather than a page-layout one. */
@media (max-width: 620px) {
  .v-table__hint { display: block; }
}

.v-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--v-1);
  padding: 2px var(--v-2);
  font-size: var(--v-fs-xs);
  letter-spacing: 0.04em;
  border: 1px solid currentColor;
  border-radius: 2px;
  background: rgba(0,0,0,0.06);
  white-space: nowrap;
}
.v-tag--gold  { color: var(--v-gold-dim); }
.v-tag--red   { color: var(--v-red); }
.v-tag--green { color: var(--v-green); }
.v-tag--blue  { color: var(--v-blue); }

/* Smaller variant for an event's *secondary* categories (feedback-2026-08-18
   item 22): the primary category drives the card's accent colour and the
   timeline dot, so the extra ones must read as subordinate to it rather
   than competing at the same weight. */
.v-tag--sm {
  padding: 1px calc(var(--v-2) - 2px);
  font-size: calc(var(--v-fs-xs) * 0.9);
  opacity: 0.85;
}

/* `.v-tag--muted` itself lives in site.css (a flat `--v-ink-3` for every
   surface it renders on); these two-class overrides win on specificity
   over its one-class rule regardless of load order. Item 39 (feedback-
   2026-08-18): the country sheet's "Unrecognized" chip must read as the
   same colour as the "Rank" figure beside it, which is unstyled — plain
   inherited `.v-paper` ink, not ink-3 — so match that ink exactly rather
   than keep the chip a visibly lighter/different brown. The directory's
   rail rows carry the same chip class for the plain country-tag chip
   ("FRA") too; ink-3 measured 2.78:1 against their dark background
   (fails AA), so this also folds in that fix rather than leaving one
   `.v-tag--muted` chip legible beside another that isn't. (The roster's
   own tag chip needs no rule here — that table sits on `.v-paper`, where
   ink-3 already passes.) */
.v-country-header .v-tag--muted { color: var(--v-ink); }
.v-rail__item .v-tag--muted { color: var(--v-cream); }

.v-meter {
  height: 8px;
  background: rgba(43,36,28,0.25);
  border: 1px solid rgba(43,36,28,0.35);
  border-radius: 999px;
  overflow: hidden;
}
.v-meter__fill { height: 100%; background: linear-gradient(90deg, var(--v-gold-dim), var(--v-gold)); }

/* --- 10. Timeline --------------------------------------------------------- */

.v-timeline { position: relative; padding-left: 92px; }
/* Item 31 (feedback-2026-08-18): "the timeline line should stop in the first
   event instead of continuously going up, because it's cutting into text
   above."

   The first attempt kept one container-length spine and merely nudged it to
   `top: 20px`, on the assumption that `.v-timeline` wraps only event cards.
   It does not: `services.build_stream_items` interleaves era headers, so
   whenever the stream opens on one the spine still began ~68px above the
   first dot and ran straight through the era band (QA 2026-08-19).

   A container-level pseudo-element cannot know where its first `.v-event`
   starts, so the spine is drawn per card instead: each event owns the
   segment beside it, extended by its own bottom margin to meet the next
   one. The first card starts its segment at the dot, so the line can never
   precede the first event; era headers simply have no segment, which also
   stops it cutting through them. */
.v-event::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: calc(-1 * var(--v-4));
  left: -18px;
  width: 2px;
  background: var(--v-gold-dim);
}
.v-timeline > .v-event:first-of-type::before { top: 20px; }
.v-timeline > .v-event:last-of-type::before {
  bottom: 0;
  background: linear-gradient(180deg, var(--v-gold-dim), transparent);
}
.v-event { position: relative; display: flex; gap: var(--v-4); margin-bottom: var(--v-4); }
.v-event__year {
  position: absolute;
  left: -92px;
  width: 60px;
  text-align: right;
  font-family: var(--v-font-display);
  font-size: var(--v-fs-md);
  color: var(--v-ink-2);
  padding-top: var(--v-3);
}
.v-event__dot {
  position: absolute;
  left: -22px;
  top: 20px;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--v-red);
  border: 2px solid var(--v-gold);
  box-shadow: 0 0 0 3px rgba(230, 220, 194, 0.9);
}
.v-event__card {
  flex: 1 1 auto;
  display: flex;
  gap: var(--v-4);
  align-items: stretch;
  padding: var(--v-3) var(--v-4);
  background: rgba(255, 250, 236, 0.5);
  border: var(--v-border-ink);
  border-radius: var(--v-radius);
}
.v-event__body { flex: 1 1 auto; min-width: 0; }
.v-event__title { font-family: var(--v-font-display); font-size: var(--v-fs-md); margin: 0 0 var(--v-1); }
.v-event__text { font-size: var(--v-fs-sm); color: var(--v-ink-2); margin: 0; }
.v-event__thumb { width: 150px; flex: none; align-self: stretch; min-height: 90px; }
.v-event__publication { font-family: var(--v-font-display); font-style: italic; font-size: var(--v-fs-sm); margin: 0 0 var(--v-1); }
.v-event__byline { font-size: var(--v-fs-sm); color: var(--v-ink-3); margin: 0 0 var(--v-1); }

/* Category-driven card accent (item 21, feedback-2026-08-19, colour half:
   "the flavor for 'primary category' says it should set the card color
   but all events... have the same color" — `TimelineEvent.category.color`
   was wired only to the `.v-tag` chip below, never to the card itself).
   State-class contract, mirroring `.v-tag--<color>`'s shape and reusing
   its exact palette tokens rather than inventing a second colour
   vocabulary: `.v-event__card--cat-gold|red|green|blue` — one class per
   `TimelineCategory.Color` value, so every colour a category can carry has
   a rule here. Applied by `timeline/_event_collapsed.html` and
   `_event_expanded.html` on the card element, off `event.category.color`,
   alongside the existing `--major` toggle below (keep those two in step:
   a state class no template applies is how phase-4.5's item 40 shipped
   inert, and how this comment itself was briefly false).

   Painted as an *inset* left-edge stripe (`box-shadow`), not a
   `border-left` on the `border` shorthand: `.v-event__card--major` below
   already claims the full `border`/`border-image` for its cast-metal
   frame, and `border-image` paints over `border-color` on every side it
   covers — a border-based stripe would simply vanish the moment both
   classes land on one card. `box-shadow` is a separate paint layer inside
   the padding box, so it composes with `--major`'s frame instead of
   fighting it: both render together on a major + categorized card.

   Contrast checked as a decorative UI accent (WCAG non-text, >=3:1) against
   the card's own actual rendered background — not the page's `--v-paper`
   underneath — since `.v-event__card` sits on its own semi-transparent
   wash (~#f6efdc effective; ~#e4d5b3 on `--major`'s gold-tinted variant,
   see below): red 7.35:1 / 5.81:1, blue 6.32:1 / 4.99:1, green 5.23:1 /
   4.14:1, gold 4.11:1 / 3.25:1 (plain card / major card) — every
   combination clears 3:1; gold reads lowest because it's the same dim
   tone `--major`'s own background tints toward, same as `.v-tag--gold`
   already is on this surface. */
.v-event__card--cat-gold  { box-shadow: inset 5px 0 0 var(--v-gold-dim); }
.v-event__card--cat-red   { box-shadow: inset 5px 0 0 var(--v-red); }
.v-event__card--cat-green { box-shadow: inset 5px 0 0 var(--v-green); }
.v-event__card--cat-blue  { box-shadow: inset 5px 0 0 var(--v-blue); }

/* Major events (WO-F6.7, owner decision 2026-08-18): a framed card (the
   same --v-frame-thin cast-metal border as .v-framed-thin, at a size that
   fits a timeline card) and a larger dot. Separate modifier classes rather
   than reusing .v-framed-thin directly — combining classes with
   conflicting `border` shorthands is a cascade-order footgun, and this way
   the frame width is tuned for .v-event__card's own padding.

   Item 24 (feedback-2026-08-18): the frame + "Major" tag alone didn't read
   at a glance while scanning — both are thin/small details easy to miss
   mid-scroll. Added here: a distinct gold-tinted card background (the same
   family `.v-choice.is-selected`/`.v-rail__item.is-active` already use for
   "this one is different", not a new colour) so the card itself pops
   before a reader even reaches the frame or tag, plus a heavier dot — gold
   fill instead of the ordinary red, a thicker ring — for the same
   at-a-glance signal on the spine itself. */
.v-event__card--major {
  background: rgba(194, 161, 90, 0.24);
  border: 8px solid transparent;
  border-image: var(--v-frame-thin) 30 fill / 8px / 0 round;
}
.v-event__dot--major {
  width: 20px;
  height: 20px;
  left: -25px;
  top: 16px;
  background: var(--v-gold);
  border-width: 3px;
  box-shadow: 0 0 0 4px rgba(230, 220, 194, 0.9);
}

/* Era thumbnail rail + cards (WO-F6.2): a horizontal strip on /timeline/
   (_era_rail.html) and the grid on /timeline/eras/ (_era_list.html) share
   one card component (_era_card.html). */
.v-era-rail__track {
  display: flex;
  gap: var(--v-3);
  overflow-x: auto;
  padding: 2px 2px var(--v-2);
  scroll-snap-type: x proximity;
}
.v-era-rail__arrow {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  color: var(--v-gold-dim);
  text-decoration: none;
  font-size: var(--v-fs-xl);
}
.v-era-rail__arrow:hover { color: var(--v-gold); text-decoration: none; }

.v-era-card {
  flex: none;
  scroll-snap-align: start;
  width: 160px;
  display: block;
  padding: var(--v-2);
  color: inherit;
  text-decoration: none;
  background: rgba(255, 250, 236, 0.5);
  border: var(--v-border-ink);
  border-radius: var(--v-radius);
}
a.v-era-card:hover { border-color: var(--v-gold); text-decoration: none; }
.v-era-card.is-active { border-color: var(--v-gold); box-shadow: 0 0 0 2px rgba(194,161,90,0.35) inset; }
.v-era-card__thumb,
.v-era-card__thumb-fallback {
  width: 100%;
  height: 90px;
  border-radius: 2px;
  margin-bottom: var(--v-2);
}
.v-era-card__thumb { object-fit: cover; display: block; }
/* Tasteful typographic fallback for an era with no thumbnail (WO-F6.2) —
   never a broken image: the era's initial, in the display face, on the
   same dark plaque treatment .v-portrait uses for characters. */
.v-era-card__thumb-fallback {
  display: grid;
  place-items: center;
  font-family: var(--v-font-display);
  font-size: var(--v-fs-xl);
  color: var(--v-gold-bright);
  background: linear-gradient(180deg, var(--v-panel-dark-2), var(--v-panel-dark));
}
.v-era-card__name { font-family: var(--v-font-display); font-size: var(--v-fs-sm); margin: 0; }
.v-era-card__meta { font-size: var(--v-fs-xs); color: var(--v-ink-3); margin: 2px 0 0; }

/* --- 11. Entity cards ----------------------------------------------------- */

.v-card {
  padding: var(--v-4);
  background: rgba(255, 250, 236, 0.5);
  border: var(--v-border-ink);
  border-radius: var(--v-radius);
}
.v-panel .v-card {
  background: rgba(0,0,0,0.25);
  border-color: rgba(194,161,90,0.25);
}
.v-card--sunken {
  background-color: rgba(43,36,28,0.07);
  background-image: var(--v-tex-tile);
  background-size: 41px 44px;
  background-blend-mode: soft-light;
}

/* Character */
.v-char { display: flex; gap: var(--v-3); align-items: flex-start; }
.v-portrait {
  width: 64px; height: 76px; flex: none;
  display: grid; place-items: center;
  font-family: var(--v-font-display);
  font-size: var(--v-fs-lg);
  color: var(--v-gold-bright);
  background: linear-gradient(180deg, var(--v-panel-dark-2), var(--v-panel-dark));
  border: 2px solid var(--v-gold-dim);
  border-radius: 40% 40% 6px 6px / 26% 26% 6px 6px;
  box-shadow: inset 0 0 18px rgba(0,0,0,0.6);
}
.v-portrait--sm { width: 44px; height: 52px; font-size: var(--v-fs-sm); }
.v-portrait--lg { width: 96px; height: 114px; font-size: var(--v-fs-xl); }
.v-char__name { font-family: var(--v-font-display); font-size: var(--v-fs-md); margin: 0; }
.v-char__role { font-size: var(--v-fs-sm); color: var(--v-ink-3); margin: 0 0 var(--v-2); }
.v-panel .v-char__role { color: var(--v-cream-2); }
.v-char__traits { display: flex; flex-wrap: wrap; gap: var(--v-1); }

/* Council slot */
.v-slot {
  display: flex;
  align-items: center;
  gap: var(--v-3);
  padding: var(--v-3);
  background: rgba(255, 250, 236, 0.45);
  border: 1px dashed rgba(43,36,28,0.35);
  border-radius: var(--v-radius);
  min-height: 84px;
}
.v-slot.is-filled  { border-style: solid; background: rgba(255, 250, 236, 0.62); }
.v-slot.is-locked  { border-color: var(--v-gold-dim); box-shadow: inset 0 0 0 1px rgba(194,161,90,0.35); }
.v-slot.is-empty   { color: var(--v-ink-3); font-style: italic; justify-content: center; }
.v-slot__label {
  font-family: var(--v-font-display);
  font-size: var(--v-fs-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--v-ink-3);
}

/* Selectable goal / option card */
.v-choice {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--v-3) var(--v-4);
  background: rgba(255, 250, 236, 0.45);
  border: 1px solid rgba(43,36,28,0.28);
  border-radius: var(--v-radius);
  cursor: pointer;
  color: inherit;
  font: inherit;
}
.v-choice:hover { border-color: var(--v-gold-dim); text-decoration: none; }
.v-choice.is-selected {
  border-color: var(--v-gold-dim);
  background: rgba(194,161,90,0.18);
  box-shadow: inset 0 0 0 1px var(--v-gold-dim);
}
.v-choice.is-locked { opacity: 0.45; pointer-events: none; }

/* Law row */
.v-law { display: flex; align-items: center; gap: var(--v-3); padding: var(--v-2) 0; border-bottom: var(--v-border-ink); }
.v-law:last-child { border-bottom: none; }
.v-law__group { flex: 1 1 auto; font-size: var(--v-fs-sm); color: var(--v-ink-3); }
.v-law__value { font-family: var(--v-font-display); }

/* --- 12. Map -------------------------------------------------------------- */

.v-map { position: relative; overflow: hidden; border-radius: var(--v-radius); }
.v-map__img { width: 100%; height: auto; display: block; }
.v-map__hotspot {
  position: absolute;
  transform: translate(-50%, -50%);
  width: 18px; height: 18px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid var(--v-gold-bright);
  background: rgba(140, 43, 38, 0.9);
  cursor: pointer;
  box-shadow: 0 0 0 3px rgba(0,0,0,0.35);
}
.v-map__hotspot:hover, .v-map__hotspot.is-active {
  background: var(--v-gold-bright);
  transform: translate(-50%, -50%) scale(1.25);
}
.v-map__hotspot span {
  position: absolute;
  left: 50%; top: -6px;
  transform: translate(-50%, -100%);
  padding: 2px var(--v-2);
  font-size: var(--v-fs-xs);
  white-space: nowrap;
  background: rgba(20,16,13,0.92);
  border: 1px solid var(--v-gold-dim);
  border-radius: 2px;
  color: var(--v-cream);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s;
}
.v-map__hotspot:hover span { opacity: 1; }

/* --- 13. Admin editing ---------------------------------------------------- */

.v-admin-bar {
  display: flex;
  align-items: center;
  gap: var(--v-3);
  flex-wrap: wrap;
  padding: var(--v-2) var(--v-4);
  background: repeating-linear-gradient(135deg, rgba(194,161,90,0.14) 0 12px, rgba(194,161,90,0.06) 12px 24px);
  border: 1px solid var(--v-gold-dim);
  border-radius: var(--v-radius);
  font-size: var(--v-fs-sm);
  color: var(--v-cream);
}

/* Wrap any editable region; the pencil appears on hover */
.v-editable { position: relative; }
.v-editable:hover { outline: 1px dashed var(--v-gold-dim); outline-offset: 6px; }
.v-editable__edit {
  position: absolute;
  top: -10px; right: -10px;
  z-index: 5;
  opacity: 0;
  transition: opacity 0.15s;
}
.v-editable:hover .v-editable__edit,
.v-editable__edit:focus-visible { opacity: 1; }

/* --- 14. Utilities -------------------------------------------------------- */

.v-muted   { color: var(--v-ink-3); }
.v-panel .v-muted { color: var(--v-cream-2); }
.v-center  { text-align: center; }
.v-right   { text-align: right; }
.v-small   { font-size: var(--v-fs-sm); }
.v-tiny    { font-size: var(--v-fs-xs); }
.v-display { font-family: var(--v-font-display); }
.v-nowrap  { white-space: nowrap; }
.v-hide    { display: none !important; }
.v-sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Striped placeholder for art that isn't in yet */
.v-placeholder {
  display: grid;
  place-items: center;
  min-height: 80px;
  padding: var(--v-3);
  text-align: center;
  font-family: var(--v-font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: rgba(43,36,28,0.6);
  background: repeating-linear-gradient(135deg,
      rgba(43,36,28,0.06) 0 8px, rgba(43,36,28,0.12) 8px 16px);
  border: 1px solid rgba(43,36,28,0.25);
  border-radius: 2px;
}
.v-panel .v-placeholder,
.v-placeholder--dark {
  color: rgba(236,226,204,0.6);
  background: repeating-linear-gradient(135deg,
      rgba(236,226,204,0.05) 0 8px, rgba(236,226,204,0.1) 8px 16px);
  border-color: rgba(194,161,90,0.3);
}

/* --- 15. Reference pages (ideology graph) --------------------------------- */

/* The Vic3-HRP visualiser's vocabulary, re-expressed in this design system:
   its palette is a dark SaaS theme, so only the *structure* is ported (level
   ramps, relationship cards, the ideology x ideology matrix). Everything here
   assumes a parchment surface; the `.v-panel` block below re-tints the ramps
   for the dark sidebar, where only the chip dots carry them. */

:root {
  /* Bond levels 1-4 and rivalry levels 1-2 (apps/reference/services.py owns
     their names). Ascending intensity = ascending level; bonds run to green,
     rivalries from amber to the house red. */
  --v-bond-1:  #6d8a72;
  --v-bond-2:  #577d58;
  --v-bond-3:  #416e3f;
  --v-bond-4:  #2f5f2c;
  --v-rival-1: #a8762a;
  --v-rival-2: #8c2b26;
}

.v-panel {
  --v-bond-1:  #8fae94;
  --v-bond-2:  #7fae80;
  --v-bond-3:  #6fae6c;
  --v-bond-4:  #63c05e;
  --v-rival-1: #d0a054;
  --v-rival-2: #c96a63;
}

/* Two-column shell: ideology rail beside the selected ideology's page. */
.v-ref {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  gap: var(--v-5);
  align-items: start;
}

/* Group chips — a level-coloured dot plus the group name, with any aliases
   trailing. `--v-lvl` is set inline from the group's bond level. */
.v-chips { display: flex; flex-wrap: wrap; gap: var(--v-2); }
.v-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--v-2);
  padding: var(--v-1) var(--v-3);
  min-height: 32px;
  font-size: var(--v-fs-sm);
  color: inherit;
  background: rgba(255, 250, 236, 0.5);
  border: var(--v-border-ink);
  border-radius: 999px;
}
a.v-chip:hover { background: rgba(194,161,90,0.18); text-decoration: none; }
.v-chip__dot {
  width: 9px; height: 9px; flex: none;
  border-radius: 50%;
  background: var(--v-lvl, var(--v-ink-3));
}
.v-chip__alt { color: var(--v-ink-3); font-size: var(--v-fs-xs); font-style: italic; }

/* Relationship cards (bonds / rivalries / frenemies). */
.v-relgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
  gap: var(--v-3);
}
.v-relcard {
  display: block;
  padding: var(--v-3);
  color: inherit;
  background: rgba(255, 250, 236, 0.5);
  border: var(--v-border-ink);
  border-left: 3px solid var(--v-lvl, var(--v-ink-3));
  border-radius: var(--v-radius);
}
a.v-relcard:hover { background: rgba(194,161,90,0.14); text-decoration: none; }
.v-relcard__top { display: flex; align-items: center; gap: var(--v-2); }
.v-relcard__name { flex: 1; min-width: 0; font-family: var(--v-font-display); font-size: var(--v-fs-base); }
.v-relcard__label { margin: var(--v-1) 0 0; font-size: var(--v-fs-sm); color: var(--v-lvl, var(--v-ink-2)); }
/* `via` provenance: which group or rivalry line produced this edge. */
.v-relcard__via { margin: var(--v-2) 0 0; font-size: var(--v-fs-xs); color: var(--v-ink-3); line-height: 1.5; }
.v-relcard__via span { display: block; }
/* Frenemy: one stripe fading from the bond colour into the rivalry colour. */
.v-relcard--frenemy {
  border-left-color: transparent;
  background-image: linear-gradient(to bottom, var(--v-lvl-bond), var(--v-lvl-rival));
  background-size: 3px 100%;
  background-repeat: no-repeat;
  background-position: left center;
  background-origin: border-box;
}
.v-relcard__labels {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--v-2);
  margin-top: var(--v-1); font-size: var(--v-fs-sm);
}
.v-relcard__sep { color: var(--v-ink-3); font-style: italic; }

/* Level badge — pill tinted from the level colour it is given. */
.v-lvl {
  flex: none;
  padding: 2px var(--v-2);
  border: 1px solid var(--v-lvl, var(--v-ink-3));
  border-radius: 999px;
  color: var(--v-lvl, var(--v-ink-2));
  font-size: var(--v-fs-xs);
  white-space: nowrap;
}

/* Law stances (the static asset's grouped law cards). */
.v-lawgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--v-3);
}
.v-lawrow {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--v-3);
  padding: var(--v-1) 0;
  font-size: var(--v-fs-sm);
}
.v-stance {
  flex: none;
  display: inline-grid; place-items: center;
  min-width: 28px; height: 20px; padding: 0 var(--v-2);
  border: 1px solid currentColor;
  border-radius: 999px;
  font-size: var(--v-fs-xs);
  font-variant-numeric: tabular-nums;
}
.v-stance--strongly_approve    { color: var(--v-bond-4); }
.v-stance--approve             { color: var(--v-bond-2); }
.v-stance--neutral             { color: var(--v-ink-3); }
.v-stance--disapprove          { color: var(--v-rival-1); }
.v-stance--strongly_disapprove { color: var(--v-rival-2); }

/* Ideology x ideology (and IG x IG) matrix: both headers stay put while the
   grid scrolls in its own container, so the page never scrolls sideways. */
.v-matrix__wrap {
  overflow: auto;
  max-height: 76dvh;
  border: var(--v-border-ink);
  border-radius: var(--v-radius);
  background: rgba(255, 250, 236, 0.5);
}
.v-matrix { border-collapse: separate; border-spacing: 0; font-size: var(--v-fs-xs); }
.v-matrix th, .v-matrix td {
  border-right: 1px solid rgba(43,36,28,0.15);
  border-bottom: 1px solid rgba(43,36,28,0.15);
}
.v-matrix thead th {
  position: sticky; top: 0; z-index: 2;
  height: 132px; padding: var(--v-1);
  background: var(--v-paper-2);
  vertical-align: bottom;
}
.v-matrix thead th span {
  display: block;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
  color: var(--v-ink-2);
}
.v-matrix tbody th {
  position: sticky; left: 0; z-index: 1;
  min-width: 150px; padding: var(--v-1) var(--v-2);
  background: var(--v-paper-2);
  text-align: left;
  font-weight: normal;
}
.v-matrix thead th.v-matrix__corner { position: sticky; left: 0; z-index: 3; min-width: 150px; }
.v-matrix td {
  width: 26px; height: 26px;
  text-align: center;
  color: var(--v-paper);
  font-size: 10px;
}
.v-matrix__cell--self { background: rgba(43,36,28,0.25); }
/* Frenemy cell: bond in the top-left triangle, rivalry in the bottom-right. */
.v-matrix__cell--split {
  background-image: linear-gradient(135deg,
      var(--v-lvl-bond) 0 50%, var(--v-lvl-rival) 50% 100%);
}

/* Ideology icons (apps/reference/icons.py) AND interest-group icons
   (apps/common/icons.py, `{% ig_icon %}`) share this class — same generic
   icon-chip sizing, two independent resolvers/key spaces feeding it.
   Absent icons render nothing, so these never style an empty box. */
.v-ig-icon { width: 26px; height: 26px; flex: none; border-radius: 2px; vertical-align: -6px; }
.v-ig-icon--lg { width: 44px; height: 44px; }
.v-ig-icon--sm { width: 16px; height: 16px; vertical-align: -3px; }

/* Power-rank icons (apps/common/icons.py, `{% rank_icon %}`), sized to sit
   inline beside a `v-tag` rank chip. Absent icons render nothing. */
.v-rank-icon { width: 18px; height: 18px; flex: none; border-radius: 2px; vertical-align: -3px; }

/* Great Power emphasis (item 40, feedback-2026-08-18: "Great Power" must
   read as visibly distinct from and more prominent than the other three
   rank labels, which today all render as identical plain text/`.v-tag`
   colour). A state class in this codebase's existing `.is-*` vocabulary
   (`.is-active`, `.is-selected`, …), not a new `.v-tag--` colour variant,
   because the rank label isn't always a `.v-tag` — the country sheet
   header renders it as bare text (`country_sheet.html`'s `.v-display`),
   the roster table as a plain `<td>` (`_roster_table.html`) — so the
   lever has to work on any element. Applied at all three rank render
   sites — `_directory.html`, `_roster_table.html`, `country_sheet.html` —
   each adding `{% if rank_token == "great_power" %} is-great-power{% endif %}`
   next to its own rank markup, off `merged_rank`'s canonical token. */
.is-great-power {
  /* Deep gold: 5.9:1 on parchment, and distinct from the plain ink every
     other rank uses, which is what "stands out more" has to mean on this
     background. The dark-chrome contexts below keep the bright gold —
     one token cannot serve both grounds (QA 2026-08-19, item 40). */
  color: #6d4f00;
  font-weight: 700;
}
.v-panel .is-great-power,
.v-rail__item .is-great-power,
.v-topbar .is-great-power { color: var(--v-gold-bright); }

/* Major Power emphasis (item 38, feedback-2026-08-19: "change the color of
   'Major Power' to be something more highlighted, maybe a silvery
   highlight since golden is Great Power… do this on the country page as
   well"). Same `.is-*` state-class shape as `.is-great-power` right above
   — for the same reason (the rank label isn't always a `.v-tag`) — kept
   as its own sibling class rather than a second value the same class
   accepts, so a row can never carry both at once by a stray template
   condition. A cool steel-grey rather than a literal light "silver" tone:
   flat silver/light-grey measures under 3:1 on parchment at any usable
   weight (`#708090` slategray only clears 3.14:1) — this deep, desaturated
   blue-grey is parchment's equivalent of `.is-great-power`'s *deep* gold
   (`#6d4f00`, itself not the bright `--v-gold-bright` for the same
   contrast reason), reading as "steel/silver" while clearing AA, and the
   dark-chrome override below is its bright counterpart, mirroring
   `--v-gold-bright`'s role for great power. Deliberately cooler than both
   `.is-great-power`'s warm gold and the plain warm ink every other rank
   uses, so "major" reads as a different *kind* of emphasis, not just a
   paler version of "great" — clearly second to gold, clearly above the
   unranked plain-ink rows.

   Applied at all three of `.is-great-power`'s sites, off the same
   `merged_rank` canonical token: `_directory.html`'s `.v-tag`,
   `_roster_table.html`'s `<td>`, and `country_sheet.html`'s `.v-display`.
   Keep those three in step — a rank state class that no template applies
   is how phase-4.5's item 40 shipped inert. */
.is-major-power {
  /* 6.4:1 on parchment (`--v-paper`), 6.98:1 on `.v-event__card`'s own
     lighter wash — clears AA at both weights this class might render at. */
  color: #43505b;
  font-weight: 700;
}
.v-panel .is-major-power,
.v-rail__item .is-major-power,
.v-topbar .is-major-power {
  /* 11.4:1 on `--v-panel-dark`, 12.4:1 on `--v-rail` — the light, cool
     counterpart to `--v-gold-bright` above, same dark-chrome contexts. */
  color: #cfd6dc;
}

/* Icon + label pair outside an existing flex container (`.v-tag`, `.v-chip`,
   `.v-row`) — e.g. an IG icon beside its plain-text name in a table cell or
   a muted header line (WO-F7.2). The global `img, svg { display: block }`
   reset (top of this file) would otherwise stack the icon above the text
   instead of beside it. */
.v-icon-text { display: inline-flex; align-items: center; gap: var(--v-2); }

/* Classification blocks (groups + rivalries listings). */
.v-classblock { margin-bottom: var(--v-5); }
.v-classtitle {
  margin: 0 0 var(--v-3);
  padding-bottom: var(--v-1);
  border-bottom: 1px solid rgba(43,36,28,0.3);
  font-family: var(--v-font-display);
  font-size: var(--v-fs-xs);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--v-gold-dim);
}

/* Empty/explanatory note — distinct from `.v-placeholder`, which marks a
   stand-in for missing content rather than a genuine "nothing here". */
.v-callout {
  padding: var(--v-3) var(--v-4);
  border: 1px dashed rgba(43,36,28,0.35);
  border-radius: var(--v-radius);
  color: var(--v-ink-3);
  font-size: var(--v-fs-sm);
}
.v-panel .v-callout { border-color: rgba(194,161,90,0.3); color: var(--v-cream-2); }
/* Item 86 (feedback-2026-08-18): /reference/ideologies/'s "Pick an ideology
   from the list…" / "No such ideology." prompts (`ideologies.html`) are the
   one `.v-callout` on this page that sits bare on the dark page background
   rather than inside `.v-paper` or `.v-panel` — every other reference page
   wraps its copy in `.v-paper` first. Base `.v-callout`'s ink-3 measured
   3.14:1 there (fails AA). The selector must go through `#reference-detail`,
   the HTMX swap target that wraps both prompts — an earlier `.v-ref > div >
   .v-callout` was one level short of the real DOM and matched nothing at
   all (QA 2026-08-19). It still stops short of the callout inside
   `_ideology_content.html`'s own nested `.v-paper`, which renders correctly
   on parchment and must keep its ink. */
.v-ref #reference-detail > .v-callout { color: var(--v-cream-2); border-color: rgba(194,161,90,0.3); }

@media (max-width: 900px) {
  .v-ref { grid-template-columns: minmax(0, 1fr); }
}

/* --- 16. Responsive ------------------------------------------------------- */

@media (max-width: 1100px) {
  .v-shell, .v-shell--wide-aside { grid-template-columns: minmax(0, 1fr); }
  .v-rail { max-height: 320px; }
}

@media (max-width: 820px) {
  :root { --v-fs-2xl: 1.875rem; --v-fs-xl: 1.5rem; }

  .v-topbar { flex-wrap: wrap; padding-block: var(--v-2); }
  .v-topbar__toggle { display: inline-flex; }
  .v-nav {
    order: 10;
    flex-basis: 100%;
    flex-direction: column;
    gap: 0;
    display: none;
  }
  .v-nav.is-open { display: flex; }
  .v-nav__link { padding-block: var(--v-3); border-radius: 0; border-bottom: 1px solid rgba(194,161,90,0.2); }

  .v-paper, .v-paper--framed { padding: var(--v-4); }
  .v-framed-gold { border-width: 20px; border-image-width: 20px; }

  .v-timeline { padding-left: var(--v-5); }
  .v-event::before { left: -18px; }
  .v-event__year { position: static; width: auto; text-align: left; padding: 0 0 var(--v-1); }
  .v-event__dot { left: -24px; }
  .v-event__card { flex-direction: column; }
  .v-event__thumb { width: 100%; min-height: 120px; }

  .v-event { flex-direction: column; gap: var(--v-1); }

  /* Country-sheet tab strip (`.v-tab`) — 8 tabs (feedback-2026-08-18
     WO-F5) wrap onto several rows at phone width already (`.v-tabs`'s own
     `flex-wrap: wrap`); the wrapped rows sat close enough together that
     the ~34px hit area (padding `var(--v-2) var(--v-4)` at the base
     `--v-fs-sm` line height) read as cramped on a phone screenshot. Taller
     padding only below the breakpoint where tabs actually wrap into a
     multi-row block — desktop's single row is untouched. */
  .v-tab { padding-block: var(--v-3); }

  /* Country/roster directory rows (`.v-rail__item`, feedback-2026-08-18
     mobile pass): flag + name + tag chip + rank chip + an optional
     "Unrecognized" chip + a trailing status chip all sit on one flex row
     with no wrap. `.v-rail`'s own `overflow-y: auto` forces its
     `overflow-x` to resolve to `auto` too (the CSS overflow spec: a
     non-visible value on one axis promotes 'visible' on the other to
     'auto') — so at phone width the row didn't overflow the *page*, it
     silently overflowed *inside* `.v-rail` instead, clipping the last
     chip (often the assignment status) off tags-per-row with no scrollbar
     ever appearing on a touch swipe test. Letting the row wrap is the fix
     that keeps every chip visible instead of hiding it in an invisible
     scroll track. */
  .v-rail__item { flex-wrap: wrap; row-gap: var(--v-1); }

  /* Era thumbnail rail (`_era_rail.html`, WO-F6.2): the prev/next arrows
     and the scrollable card track share one `.v-row`, which wraps by
     default — below ~460px of content width the track's intrinsic
     content width (it never shrinks; `overflow-x: auto` needs a definite
     width to clip against) pushed the arrows onto their own lines above
     and below the track instead of beside it (found via the 375px
     screenshot: "<" over the track, ">" under it). `flex-wrap: nowrap`
     plus `min-width: 0` on the track (letting it actually shrink to the
     remaining row width so its own `overflow-x: auto` has something to
     clip against) keeps the arrows flanking the track at any width. The
     arrows also grow from 28px to 44px here — a comfortable thumb target
     — without touching their desktop size. */
  .v-era-rail__row { flex-wrap: nowrap; }
  .v-era-rail__track { min-width: 0; }
  .v-era-rail__arrow { width: 44px; }

}

@media (max-width: 520px) {
  .v-container { padding-inline: var(--v-3); }
  .v-figure__val { font-size: var(--v-fs-lg); }

  /* Home "Campaign Overview" (`.v-grid--4`, templates/content/partials/
     figures.html — the only user of this class) would otherwise fall to
     `auto-fit`'s single column below ~360px of content width, turning
     eight tiles into eight full-width rows (feedback-2026-08-18 mobile
     pass). Two columns keeps the pairing readable without the row-height
     hit of a single column, and without inventing a fifth `.v-grid--N`
     variant nobody else needs. */
  .v-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* --- 17. Print ------------------------------------------------------------ */

@media print {
  body::before, body::after { display: none; }
  .v-topbar, .v-admin-bar, .v-editable__edit { display: none; }
  body { background: #fff; color: #000; }
  .v-paper { box-shadow: none; }
}
