/*
 * Verified Handles — 2026 design system.
 *
 * This is the SINGLE design authority for the rebuild (the legacy wiki `vh.css` is no longer
 * linked — it was Arial-everywhere with no max-width and full-width grey handle bars; the owner
 * asked for a significant uplift). Self-hosted Poppins, a token-driven light/dark palette, a
 * constrained reading column, a proper sticky header with an off-canvas menu, and redesigned
 * handle cards. No inline styles anywhere → CSP `style-src 'self'`; fonts are self-hosted →
 * `font-src 'self'`; no third-party requests.
 */

/* ----------------------------------------------------------------- fonts -- */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/poppins/Poppins-Regular.woff2') format('woff2'),
       url('/static/fonts/poppins/Poppins-Regular.ttf') format('truetype');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/static/fonts/poppins/Poppins-SemiBold.woff2') format('woff2'),
       url('/static/fonts/poppins/Poppins-SemiBold.ttf') format('truetype');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/static/fonts/poppins/Poppins-Bold.woff2') format('woff2'),
       url('/static/fonts/poppins/Poppins-Bold.ttf') format('truetype');
}

/* ---------------------------------------------------------------- tokens -- */
:root {
  --vh-font: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  /* Spacing scale — 4/8 rhythm (GLOBAL-01). Every padding/margin/gap snaps to these;
     raw px is reserved for borders, radii, hairline icon sizing. */
  --vh-space-1: 4px;
  --vh-space-2: 8px;
  --vh-space-3: 12px;
  --vh-space-4: 16px;
  --vh-space-5: 24px;
  --vh-space-6: 32px;
  --vh-space-7: 48px;
  --vh-space-8: 64px;

  /* Type scale — a modular set (GLOBAL-02). Every font-size maps to one of these. */
  --vh-fs-50: 0.75rem;    /* 12 — caption / eyebrow / badge */
  --vh-fs-75: 0.8125rem;  /* 13 — small meta */
  --vh-fs-100: 0.875rem;  /* 14 — secondary text / controls */
  --vh-fs-200: 1rem;      /* 16 — body */
  --vh-fs-300: 1.125rem;  /* 18 — lead / subtitle */
  --vh-fs-400: 1.25rem;   /* 20 — h3 / section */
  --vh-fs-500: 1.5rem;    /* 24 — h2 / console h1 */
  --vh-fs-600: clamp(1.55rem, 3.4vw, 2.1rem); /* fluid h1 / article title */
  --vh-fs-700: clamp(1.9rem, 5vw, 2.6rem);    /* hero */

  /* Standard control padding so every button shares a rhythm (GLOBAL-01). */
  --vh-control-pad: var(--vh-space-2) var(--vh-space-4);
  /* Minimum comfortable touch target (A11Y-03). */
  --vh-tap: 44px;

  --vh-bg: #ffffff;
  --vh-surface: #f6f8fa;
  --vh-surface-2: #eef1f5;
  --vh-inset: #e9edf2;
  --vh-border: #e3e7ec;        /* decorative card/divider edge (paired with shadow) */
  --vh-border-strong: #d0d7de;
  --vh-field-border: #868e98;  /* input/control edge — 3.3:1 on #fff (WCAG 1.4.11) */

  --vh-fg: #1c2128;          /* 14.6:1 on #fff */
  --vh-fg-muted: #586374;    /*  5.7:1 on #fff, 5.2:1 on inset */
  --vh-fg-faint: #5b6675;    /*  5.8:1 on #fff, 5.0:1 on inset (measured AA) */

  --vh-brand: #1f3df0;       /* the VH blue, lightly refined */
  --vh-logo-filter: none;    /*  the fixed-fill wordmark SVGs are lifted in dark mode (below) */
  --vh-accent: #1b44d4;      /*  7.4:1 on #fff — links/buttons */
  --vh-on-accent: #ffffff;   /*  text/icon on an accent fill — 7.4:1 (GLOBAL-04) */
  --vh-accent-soft: #eaeefc; /*  selected/hover fill */
  --vh-success: #136c2c;     /*  5.2:1 as pill text on its 12% tint (measured AA) */
  --vh-attention: #8a5a00;
  --vh-danger: #cf222e;

  --vh-mark-bg: #fff3bf;
  --vh-mark-fg: #1c2128;

  --vh-shadow-sm: 0 1px 2px rgba(27, 31, 36, 0.06), 0 1px 3px rgba(27, 31, 36, 0.05);
  --vh-shadow-md: 0 4px 14px rgba(27, 31, 36, 0.10);
  --vh-shadow-lg: 0 12px 34px rgba(27, 31, 36, 0.16);

  --vh-radius: 10px;
  --vh-radius-lg: 16px;
  --vh-maxw: 1080px;
}
/* Dark palette. Applied to the OS-dark default (unless the user forced light via the toggle) AND
   to an explicit [data-theme="dark"] (FEATURE-05). The two selectors can't share a rule across the
   media boundary, so the token block is duplicated — KEEP THE TWO IN SYNC. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --vh-bg: #0d1117;
    --vh-surface: #161b22;
    --vh-surface-2: #1b212a;
    --vh-inset: #1f2630;
    --vh-border: #2a313c;
    --vh-border-strong: #3a424e;
    --vh-field-border: #647082; /* 3.8:1 on #0d1117 */

    --vh-fg: #e6edf3;          /* 14.4:1 on #0d1117 */
    --vh-fg-muted: #a3adba;    /*  8.3:1 */
    --vh-fg-faint: #8b95a3;    /*  6.2:1 on bg, 5.0:1 on inset */

    --vh-brand: #6f8bff;
    --vh-logo-filter: brightness(1.9);  /*  lifts the #1f3df0 wordmark to ~4.6:1 on dark */
    --vh-accent: #84a4ff;      /*  7.5:1 on #0d1117 */
    --vh-on-accent: #0d1117;   /*  dark ink on the pale-blue fill — 7.9:1 (fixes 2.40:1) */
    --vh-accent-soft: #1a2436;
    --vh-success: #3fb950;     /*  4.9:1 as pill text on its 12% tint */
    --vh-attention: #d29922;
    --vh-danger: #ff6a6a;

    --vh-mark-bg: #6b5300;
    --vh-mark-fg: #f4f6f9;

    --vh-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --vh-shadow-md: 0 6px 18px rgba(0, 0, 0, 0.5);
    --vh-shadow-lg: 0 14px 40px rgba(0, 0, 0, 0.6);
  }
}
:root[data-theme='dark'] {
  --vh-bg: #0d1117;
  --vh-surface: #161b22;
  --vh-surface-2: #1b212a;
  --vh-inset: #1f2630;
  --vh-border: #2a313c;
  --vh-border-strong: #3a424e;
  --vh-field-border: #647082;

  --vh-fg: #e6edf3;
  --vh-fg-muted: #a3adba;
  --vh-fg-faint: #8b95a3;

  --vh-brand: #6f8bff;
  --vh-logo-filter: brightness(1.9);
  --vh-accent: #84a4ff;
  --vh-on-accent: #0d1117;
  --vh-accent-soft: #1a2436;
  --vh-success: #3fb950;
  --vh-attention: #d29922;
  --vh-danger: #ff6a6a;

  --vh-mark-bg: #6b5300;
  --vh-mark-fg: #f4f6f9;

  --vh-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --vh-shadow-md: 0 6px 18px rgba(0, 0, 0, 0.5);
  --vh-shadow-lg: 0 14px 40px rgba(0, 0, 0, 0.6);
}
/* Force native UI (form controls, scrollbars) to the chosen theme when overridden. */
:root[data-theme='dark'] { color-scheme: dark; }
:root[data-theme='light'] { color-scheme: light; }

/* ------------------------------------------------------------------ base -- */
html { color-scheme: light dark; -webkit-text-size-adjust: 100%; }
* { box-sizing: border-box; }
body {
  font-family: var(--vh-font);
  font-size: 16px;
  line-height: 1.6;
  color: var(--vh-fg);
  background: var(--vh-bg);
  margin: 0 auto;
  max-width: var(--vh-maxw);
  padding: 0 var(--vh-space-5) var(--vh-space-8);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
a { color: var(--vh-accent); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3, h4 { font-weight: 600; line-height: 1.25; color: var(--vh-fg); }
img { max-width: 100%; }
hr { border: 0; border-top: 1px solid var(--vh-border); margin: 18px 0; }
:focus-visible { outline: 2px solid var(--vh-accent); outline-offset: 2px; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) {
  *, ::before, ::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
  /* ENTRY-LIVE-004: the `*` rule above does NOT reach View Transition pseudo-elements (they aren't
     matched by the universal selector), so document.startViewTransition()'s default cross-fade
     needs its own explicit kill switch here — the single source of truth for reduced-motion stays
     CSS-only, matching the rule above, rather than an extra JS-side prefers-reduced-motion branch. */
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
    animation: none !important;
  }
}
/* Windows High Contrast / forced-colors: keep rings + key boundaries perceivable (A11Y-04). */
@media (forced-colors: active) {
  :focus-visible { outline-color: Highlight; }
  .vh-site-header, .vh-menu-panel, .vh-handle__link, .vh-result, .vh-featured__card,
  .vh-review-item, .vh-card, .vh-field input, .vh-field textarea, .vh-field select,
  .vh-console-table th, .vh-console-table td, .vh-platpicker, .vh-platpicker-trigger { border: 1px solid CanvasText; }
  .vh-btn-primary, .vh-menu-login, .vh-sb__submit, .vh-edit-btn, .vh-menu-btn { border: 1px solid ButtonText; }
}

/* Screen-reader-only + skip link (single canonical definition — GLOBAL-05; searchbar.css defers) */
.vh-visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  border: 0; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.vh-skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--vh-bg); color: var(--vh-accent);
  padding: 10px 16px; border: 1px solid var(--vh-border-strong); border-radius: 0 0 8px 0;
}
.vh-skip-link:focus { left: 0; }

/* --------------------------------------------------------------- header --- */
.vh-site-header {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; gap: var(--vh-space-3);
  padding: var(--vh-space-3) 0; margin-bottom: var(--vh-space-5);
  background: var(--vh-bg); /* solid fallback when color-mix is unsupported (GLOBAL-03) */
  background: color-mix(in srgb, var(--vh-bg) 88%, transparent);
  border-bottom: 1px solid var(--vh-border);
}
/* Gate the blur — invalid declarations would otherwise leave a transparent bar (GLOBAL-08). */
@supports (backdrop-filter: saturate(140%) blur(8px)) {
  .vh-site-header { backdrop-filter: saturate(140%) blur(8px); }
}
@media (pointer: coarse) {
  /* Touch devices: drop the scroll-costly blur, keep the readable solid bar. */
  .vh-site-header { background: var(--vh-bg); backdrop-filter: none; }
}
/* Homepage variant: a transparent floating bar carrying only the Menu trigger (the hero owns the
   logo + search). No bar chrome — just the right-aligned button hovering over the hero. */
.vh-site-header--minimal {
  background: transparent; border-bottom: 0; backdrop-filter: none;
  justify-content: flex-end; margin-bottom: 0; padding: var(--vh-space-2) 0;
}
.vh-site-header__logo { display: inline-flex; flex: 0 0 auto; align-items: center; }
/* Two brand assets toggled by viewport: the wide wordmark (desktop) and the square mark (compact).
   Each is styled by its own class so neither needs a specificity hack (PERF-01). */
.vh-logo-wide { height: 30px; width: auto; display: block; filter: var(--vh-logo-filter, none); }
.vh-logo-mark { display: none; height: 34px; width: 34px; }
.vh-site-header .vh-sb { flex: 1 1 360px; max-width: 560px; min-width: 0; margin: 0; }
.vh-header-spacer { flex: 1 1 0; }

/* menu trigger / close — labels driving the CSS-only off-canvas panel. The checkbox is the
 * real (keyboard-focusable) control; it's clipped to 1px (not moved off-screen, so focusing it
 * doesn't scroll), and its focus ring is reflected onto the visible Menu button via a sibling
 * selector below. */
.vh-menu-toggle { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
/* The navbar menu trigger is a plain icon+label control (not an encircled pill). */
.vh-menu-btn {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--vh-space-2); min-height: var(--vh-tap);
  font: 600 var(--vh-fs-100) var(--vh-font); color: var(--vh-fg);
  background: transparent; border: 0; border-radius: 10px;
  padding: var(--vh-space-2) var(--vh-space-2); cursor: pointer; user-select: none;
}
.vh-menu-btn[hidden] { display: none; } /* respect the `hidden` attr the class would otherwise beat */
.vh-menu-btn:hover { color: var(--vh-accent); background: var(--vh-surface); }
.vh-menu-btn__bars { display: inline-block; width: 16px; height: 11px; position: relative; }
.vh-menu-btn__bars::before, .vh-menu-btn__bars::after, .vh-menu-btn__bars span {
  content: ''; position: absolute; left: 0; right: 0; height: 2px; border-radius: 2px; background: currentColor;
}
.vh-menu-btn__bars::before { top: 0; } .vh-menu-btn__bars span { top: 4.5px; } .vh-menu-btn__bars::after { top: 9px; }

/* off-canvas panel + overlay. Opened by the no-JS checkbox (`:checked`) OR by site.ts
   (`.vh-menu-open` on <html>). Closed = translated off AND `visibility:hidden`, so its links are
   never focusable off-screen (HEADER-01). */
.vh-menu-overlay {
  position: fixed; inset: 0; z-index: 90; background: rgba(8, 11, 16, 0.58);
  opacity: 0; visibility: hidden; transition: opacity 0.2s ease;
}
@supports (backdrop-filter: blur(2px)) { .vh-menu-overlay { backdrop-filter: blur(2px); } }
.vh-menu-panel {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 100; width: min(360px, 86vw);
  background: var(--vh-bg); border-left: 1px solid var(--vh-border);
  box-shadow: var(--vh-shadow-lg); transform: translateX(100%); visibility: hidden;
  transition: transform 0.24s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 0.24s;
  display: flex; flex-direction: column; padding: var(--vh-space-5);
  overflow-y: auto; overscroll-behavior: contain;
}
.vh-menu-toggle:checked ~ .vh-menu-overlay,
.vh-menu-open .vh-menu-overlay { opacity: 1; visibility: visible; }
.vh-menu-toggle:checked ~ .vh-menu-panel,
.vh-menu-open .vh-menu-panel { transform: translateX(0); visibility: visible; transition-delay: 0s; }
.vh-menu-toggle:focus-visible ~ .vh-site-header .vh-menu-btn { outline: 2px solid var(--vh-accent); outline-offset: 2px; }
.vh-menu-open { overflow: hidden; } /* JS path scroll-lock */
.vh-menu-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--vh-space-4); }
.vh-menu-head strong { font-size: var(--vh-fs-400); font-weight: 700; }
.vh-menu-close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; margin-right: calc(-1 * var(--vh-space-2)); font-size: var(--vh-fs-500); line-height: 1;
  color: var(--vh-fg-muted); cursor: pointer; border: 0; border-radius: 10px; background: transparent;
}
.vh-menu-close:hover { color: var(--vh-fg); background: var(--vh-surface); }
.vh-menu-nav { display: flex; flex-direction: column; gap: 2px; }
.vh-menu-nav a {
  display: flex; align-items: center; justify-content: space-between; gap: var(--vh-space-3); min-height: var(--vh-tap);
  padding: var(--vh-space-2) var(--vh-space-3); border-radius: 10px;
  color: var(--vh-fg); font-weight: 500; font-size: var(--vh-fs-200);
}
.vh-menu-nav a:hover { background: var(--vh-surface); text-decoration: none; }
.vh-menu-nav .vh-ext-mark { color: var(--vh-fg-faint); font-size: var(--vh-fs-100); }
/* account section */
.vh-menu-account { display: flex; flex-direction: column; gap: var(--vh-space-2); margin-top: var(--vh-space-4); padding-top: var(--vh-space-4); border-top: 1px solid var(--vh-border); }
.vh-menu-account a[hidden] { display: none; }
.vh-menu-login {
  display: flex; align-items: center; justify-content: center; min-height: var(--vh-tap);
  padding: var(--vh-space-2) var(--vh-space-4); border-radius: 10px; font-weight: 600;
  color: var(--vh-on-accent); background: var(--vh-accent);
}
.vh-menu-login:hover { background: var(--vh-brand); color: var(--vh-on-accent); text-decoration: none; }
.vh-menu-signout {
  display: flex; align-items: center; justify-content: center; min-height: var(--vh-tap);
  padding: var(--vh-space-2) var(--vh-space-4); border-radius: 10px; font-weight: 600;
  color: var(--vh-fg-muted); background: transparent;
}
.vh-menu-signout:hover { color: var(--vh-accent); background: var(--vh-surface); text-decoration: none; }
/* appearance: a 3-way segmented theme control */
.vh-menu-appearance { margin-top: var(--vh-space-4); padding-top: var(--vh-space-4); border-top: 1px solid var(--vh-border); }
.vh-menu-label { display: block; margin-bottom: var(--vh-space-2); font-size: var(--vh-fs-50); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--vh-fg-faint); }
.vh-theme-seg { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px; padding: 3px; background: var(--vh-inset); border-radius: 10px; }
.vh-theme-seg[hidden] { display: none; }
.vh-theme-seg__btn {
  min-height: 36px; padding: var(--vh-space-1) var(--vh-space-2); border: 0; border-radius: 8px;
  font: 600 var(--vh-fs-75) var(--vh-font); color: var(--vh-fg-muted); background: transparent; cursor: pointer;
}
.vh-theme-seg__btn:hover { color: var(--vh-fg); }
.vh-theme-seg__btn.is-active { color: var(--vh-fg); background: var(--vh-bg); box-shadow: var(--vh-shadow-sm); }
/* preferences: collapsed by default — deliberately quieter than the appearance control above.
   Reuses .vh-menu-label's small-caps styling for the closed-state look (BETA-FIX: without its
   own +/− cue below, that shared styling made "Preferences" indistinguishable from the static
   "Appearance"/"Entry VHID" section labels either side of it, and the bare native <summary>
   marker was too easy to miss — the section read as an empty, non-interactive heading. Mirrors
   the .vh-identifiers +/− disclosure pattern above.) */
.vh-menu-prefs { margin-top: var(--vh-space-4); padding-top: var(--vh-space-4); border-top: 1px solid var(--vh-border); }
.vh-menu-prefs[hidden] { display: none; }
.vh-menu-prefs summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: var(--vh-space-2); }
.vh-menu-prefs summary::-webkit-details-marker { display: none; }
.vh-menu-prefs summary::before { content: '+'; font-weight: 600; }
.vh-menu-prefs[open] summary::before { content: '−'; }
.vh-menu-prefs summary:hover { color: var(--vh-fg); }
.vh-menu-pref-row { display: flex; align-items: center; gap: var(--vh-space-2); margin-top: var(--vh-space-3); font-size: var(--vh-fs-75); color: var(--vh-fg-muted); cursor: pointer; }
.vh-menu-pref-row input { margin: 0; accent-color: var(--vh-accent); }
.vh-menu-foot { margin-top: auto; padding-top: var(--vh-space-5); color: var(--vh-fg-faint); font-size: var(--vh-fs-75); }

/* --------------------------------------------------------------- footer --- */
.vh-footer { text-align: center; margin: var(--vh-space-8) 0 var(--vh-space-2); }
.vh-foot-links { display: flex; flex-wrap: wrap; gap: var(--vh-space-1) var(--vh-space-3); justify-content: center; font-size: var(--vh-fs-100); }
.vh-foot-links a { display: inline-flex; align-items: center; min-height: var(--vh-tap); padding: 0 var(--vh-space-2); color: var(--vh-fg-muted); }
.vh-foot-links a:hover { color: var(--vh-accent); }
.vh-foot-cc { margin-top: var(--vh-space-3); color: var(--vh-fg-faint); font-size: var(--vh-fs-75); }
.vh-foot-cc a { color: var(--vh-fg-muted); }
.vh-foot-cc a:hover { color: var(--vh-accent); }

/* ----------------------------------------------------------------- home --- */
.vh-hero { text-align: center; margin: clamp(24px, 7vh, 72px) auto 0; max-width: 760px; }
.vh-hero__brand { margin: 0; }
.vh-hero__logo { display: block; width: min(58%, 380px); margin: 0 auto var(--vh-space-4); filter: var(--vh-logo-filter, none); }
.vh-hero__tagline { font-size: clamp(1.05rem, 2.6vw, 1.35rem); color: var(--vh-fg-muted); font-weight: 400; margin: 0 auto var(--vh-space-5); max-width: 38ch; line-height: 1.5; }
.vh-hero__tagline strong { color: var(--vh-fg); font-weight: 600; }
.vh-hero__search { text-align: left; }
.vh-hero__stat { margin: var(--vh-space-4) 0 0; color: var(--vh-fg-muted); font-size: var(--vh-fs-100); }
.vh-hero__stat strong { color: var(--vh-fg); font-weight: 700; }
.vh-hero__examples { display: flex; flex-wrap: wrap; gap: var(--vh-space-2); justify-content: center; align-items: center; margin-top: var(--vh-space-3); }
.vh-hero__examples-label { color: var(--vh-fg-faint); font-size: var(--vh-fs-75); }
.vh-chip { display: inline-flex; align-items: center; min-height: 36px; padding: var(--vh-space-1) var(--vh-space-3); border: 1px solid var(--vh-border-strong); border-radius: 999px; font-size: var(--vh-fs-100); color: var(--vh-fg-muted); }
.vh-chip:hover { border-color: var(--vh-accent); color: var(--vh-accent); text-decoration: none; }

.vh-featured { margin: 56px auto 0; max-width: var(--vh-maxw); }
.vh-featured__h {
  font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--vh-fg-faint); text-align: center; margin: 0 0 18px;
}
.vh-featured__row { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.vh-featured__card {
  display: block; padding: 16px 18px; border: 1px solid var(--vh-border); border-radius: var(--vh-radius-lg);
  background: var(--vh-bg); box-shadow: var(--vh-shadow-sm); color: inherit;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.vh-featured__card:hover { border-color: var(--vh-accent); box-shadow: var(--vh-shadow-md); transform: translateY(-2px); text-decoration: none; }
.vh-featured__title { font-weight: 600; font-size: 1.05rem; color: var(--vh-fg); }
.vh-featured__sub { display: block; margin-top: 3px; font-size: 0.88rem; color: var(--vh-fg-muted); }

/* ---------------------------------------------------------- entry page ---- */
main { display: block; }
.article-body { padding: 4px 0 0; }
/* composed entry header — figure + title row (chips via gap, not margin-left) + identity +
   action row, replacing the inline edit button + <hr> (ENTRY-01/02/03/08, RESP-02). */
/* Entry header: a two-column top — identity + actions + description (left), portrait (right) — so
   the description starts under the subtitle with the image beside it. On mobile the column stacks
   and the portrait drops below the description, centred (the ≤760 block). */
.vh-entry-top { display: flex; gap: var(--vh-space-6); align-items: flex-start; margin-bottom: var(--vh-space-5); }
.vh-entry-main { flex: 1 1 auto; min-width: 0; }
.vh-entry-head { margin-bottom: var(--vh-space-2); }
.vh-entry-figure { flex: 0 0 auto; width: min(240px, 30%); margin: 0; }
.vh-entry-titlerow { display: flex; flex-wrap: wrap; align-items: center; gap: var(--vh-space-2) var(--vh-space-3); }
.vh-entry-title, .article-title { font-size: var(--vh-fs-600); font-weight: 700; margin: 0; letter-spacing: -0.01em; line-height: 1.15; min-width: 0; }
/* page-level titles (search/message/maintenance/console) keep a little breathing room below */
.article-title { margin-bottom: var(--vh-space-4); }
.vh-entry-handle { margin: var(--vh-space-2) 0 0; font-size: var(--vh-fs-300); font-weight: 500; color: var(--vh-fg-faint); }
.vh-entry-subtitle { margin: var(--vh-space-3) 0 0; font-size: var(--vh-fs-300); color: var(--vh-fg-muted); }
/* UX-011: copyable VHID in the MENU footer (entry pages only). The button reuses .vh-handle__copy
   (visuals + site.ts copy wiring); the <code> is mono via the base `code` rule and select-all.
   Reuses .vh-menu-label for the heading. */
.vh-menu-vhid { margin-top: var(--vh-space-4); }
.vh-menu-vhid__row { display: inline-flex; align-items: center; gap: var(--vh-space-2); margin-top: var(--vh-space-1); }
.vh-menu-vhid__val { font-size: var(--vh-fs-100); color: var(--vh-fg-muted); user-select: all; }
.vh-entry-actions { display: flex; flex-wrap: wrap; gap: var(--vh-space-2); margin-top: var(--vh-space-4); clear: none; }
.vh-action__ic { flex: 0 0 auto; }

.vh-type-label {
  display: inline-flex; align-items: center;
  font-size: var(--vh-fs-50); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--vh-fg-muted); background: var(--vh-surface); border: 1px solid var(--vh-border);
  padding: var(--vh-space-1) var(--vh-space-2); border-radius: 999px;
}
/* Verified = the production blue seal (smallVerified.svg). Entry/info: a neutral pill with the
   seal + 'Verified' text; search: the bare seal icon. The seal carries the colour. */
.vh-verified {
  display: inline-flex; align-items: center; gap: var(--vh-space-1);
  font-size: var(--vh-fs-50); font-weight: 600; color: var(--vh-fg-muted);
  background: var(--vh-surface); border: 1px solid var(--vh-border);
  padding: var(--vh-space-1) var(--vh-space-2) var(--vh-space-1) var(--vh-space-1); border-radius: 999px;
}
.vh-verified img { display: block; }
.vh-verified-mark { display: inline-flex; align-items: center; vertical-align: middle; }
.vh-verified-mark img { display: block; }
/* entry-page Verified/Claimed pills are links to /verification now (not just a hover title), so
   the default `a`/`a:hover` treatment (underline on hover, pointer cursor) applies — no
   tooltip-specific cursor override needed here any more. */

/* in-product info pages (/about, /verification, /contributing) — a readable prose column */
.vh-info { max-width: 68ch; }
.vh-info__lead { margin: var(--vh-space-3) 0 var(--vh-space-5); font-size: var(--vh-fs-300); color: var(--vh-fg-muted); }
/* body-size variant of .vh-info__lead (keeps its spacing, drops the larger lead font-size) */
.vh-info__lead--body { font-size: var(--vh-fs-200); }
.vh-info section { margin: 0 0 var(--vh-space-5); }
.vh-info h2 { margin: var(--vh-space-5) 0 var(--vh-space-2); }
.vh-info p { margin: 0 0 var(--vh-space-3); }
.vh-info .vh-verified { vertical-align: middle; }
.vh-info__cta { display: flex; flex-wrap: wrap; gap: var(--vh-space-2); margin-top: var(--vh-space-6); }

/* claim flow (FEATURE-03): the title-row 'Claimed' chip (also used, combined, for the
   Verified-and-Claimed state) + 'Claim' CTA, and the SSR claim page */
.vh-claimed {
  display: inline-flex; align-items: center; gap: var(--vh-space-1);
  font-size: var(--vh-fs-50); font-weight: 600; color: var(--vh-accent);
  background: var(--vh-surface); /* fallback when color-mix is unsupported */
  background: color-mix(in srgb, var(--vh-accent) 12%, var(--vh-bg));
  border: 1px solid var(--vh-border);
  border-color: color-mix(in srgb, var(--vh-accent) 35%, var(--vh-bg));
  padding: var(--vh-space-1) var(--vh-space-2) var(--vh-space-1) var(--vh-space-1); border-radius: 999px;
}
.vh-claimed img { display: block; }
.vh-claim-cta { color: var(--vh-accent); border-color: transparent; background: var(--vh-accent-soft); }
.vh-claim-cta:hover { color: var(--vh-accent); border-color: var(--vh-accent); background: var(--vh-accent-soft); }
.vh-claim__back { margin: 0 0 var(--vh-space-2); font-size: var(--vh-fs-100); }
.vh-claim__error {
  margin: 0 0 var(--vh-space-4); padding: var(--vh-space-3) var(--vh-space-4); border-radius: 10px;
  color: var(--vh-danger); background: var(--vh-surface);
  background: color-mix(in srgb, var(--vh-danger) 8%, var(--vh-bg));
  border: 1px solid color-mix(in srgb, var(--vh-danger) 35%, var(--vh-bg));
}
.vh-claim__pending { margin-top: var(--vh-space-4); }
.vh-claim__token code { word-break: break-all; }
.vh-claim__switch { margin-top: var(--vh-space-4); }
.vh-claim__switch summary { cursor: pointer; min-height: var(--vh-tap); display: inline-flex; align-items: center; }
.vh-claim-form { display: flex; flex-direction: column; gap: var(--vh-space-4); max-width: 42rem; margin-top: var(--vh-space-3); }
.vh-claim-methods { display: flex; flex-direction: column; gap: var(--vh-space-3); margin: 0; padding: var(--vh-space-4); border: 1px solid var(--vh-border); border-radius: 12px; }
.vh-claim-methods legend { font-weight: 600; padding: 0 var(--vh-space-2); }
.vh-claim-methods label { display: flex; gap: var(--vh-space-2); align-items: flex-start; line-height: 1.4; }
.vh-claim-target { display: flex; flex-direction: column; gap: var(--vh-space-2); font-weight: 500; }
.vh-claim-target__hint { color: var(--vh-fg-muted); font-weight: 400; }
.vh-claim-target textarea { resize: vertical; min-height: 6rem; }

/* report link at the foot of an entry (FEATURE-08) */
.vh-entry-report {
  margin: var(--vh-space-6) 0 0; padding-top: var(--vh-space-4);
  border-top: 1px solid var(--vh-border); font-size: var(--vh-fs-75); color: var(--vh-fg-muted);
}

/* browse hub + lists (FEATURE-06) */
.vh-browse-back { margin: 0 0 var(--vh-space-3); font-size: var(--vh-fs-100); }
.vh-browse-section { margin: 0 0 var(--vh-space-6); }
.vh-browse-az { display: flex; flex-wrap: wrap; gap: var(--vh-space-2); }
.vh-browse-letter {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--vh-tap); min-height: var(--vh-tap); padding: 0 var(--vh-space-2);
  border: 1px solid var(--vh-field-border); border-radius: 10px; font-weight: 600; color: var(--vh-fg);
}
.vh-browse-letter:hover { border-color: var(--vh-accent); color: var(--vh-accent); text-decoration: none; }
.vh-browse-chips { display: flex; flex-wrap: wrap; gap: var(--vh-space-3); }
.vh-browse-chip {
  display: inline-flex; align-items: center; gap: var(--vh-space-2); min-height: var(--vh-tap);
  padding: var(--vh-space-2) var(--vh-space-4); border: 1px solid var(--vh-border); border-radius: 999px;
  font-weight: 600; color: var(--vh-fg); background: var(--vh-surface);
}
.vh-browse-chip:hover { border-color: var(--vh-accent); text-decoration: none; }
.vh-browse-chip__n { color: var(--vh-fg-muted); font-weight: 500; font-size: var(--vh-fs-75); }

/* "See also" related cards on the entry page (FEATURE-06) */
.vh-related { margin-top: var(--vh-space-6); }
.vh-related-list {
  list-style: none; margin: 0; padding: 0; display: grid; gap: var(--vh-space-3);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 220px), 1fr));
}
.vh-related-card {
  display: flex; flex-direction: column; gap: var(--vh-space-1); min-width: 0;
  padding: var(--vh-space-3) var(--vh-space-4); border: 1px solid var(--vh-border);
  border-radius: var(--vh-radius); color: var(--vh-fg); background: var(--vh-surface); box-shadow: var(--vh-shadow-sm);
}
.vh-related-card:hover { border-color: var(--vh-accent); text-decoration: none; }
.vh-related-title { font-weight: 600; }
.vh-related-sub { color: var(--vh-fg-muted); font-size: var(--vh-fs-75); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.article-desc-body { margin: var(--vh-space-2) 0 var(--vh-space-1); max-width: 70ch; }
.article-desc-body p { margin: 0 0 var(--vh-space-3); }
/* legacy inline images in the description must not dominate the page (ENTRY-04) */
.article-desc-body img, .article-desc-body .article-image {
  max-width: min(100%, 320px); width: auto; height: auto;
  border-radius: var(--vh-radius); margin: var(--vh-space-2) 0;
}
.article-desc, .article-desc-body { color: var(--vh-fg); }
.vh-wikilink { display: block; margin-top: var(--vh-space-1); font-size: var(--vh-fs-100); color: var(--vh-fg-faint); }
.vh-wikilink a { color: var(--vh-fg-muted); }
.link-article { color: var(--vh-accent); }
/* description cross-links (emitted by renderMarkup) + external-link marker (ENTRY-07 / LINK-02) */
.vh-link, .vh-link-ext { color: var(--vh-accent); font-weight: 500; }
.vh-link:hover, .vh-link-ext:hover { text-decoration: underline; }
.vh-link-ext::after { content: '\2197'; margin-left: 1px; font-size: 0.85em; }
/* A cross-link whose target was removed/blocked (resolved at render) — neutral, never a dead link. */
.vh-link-missing { color: var(--vh-fg-faint); font-style: italic; }
.vh-ext-mark { font-size: 0.85em; color: var(--vh-fg-faint); }
.vh-menu-nav a .vh-ext-mark { margin-left: auto; }

/* lead portrait — fills the floated/centred figure */
.article-image {
  width: 100%; height: auto; border-radius: var(--vh-radius-lg); display: block;
  border: 1px solid var(--vh-border); box-shadow: var(--vh-shadow-sm); background: var(--vh-surface);
}
.vh-attribution { display: block; text-align: center; margin-top: var(--vh-space-1); font-size: var(--vh-fs-50); color: var(--vh-fg-faint); }
.vh-attribution a { color: inherit; }
@media (max-width: 760px) {
  /* The two-column top stacks; the portrait is ordered below the identity + description, centred. */
  .vh-entry-top { flex-direction: column; gap: 0; }
  .vh-entry-figure { order: 2; align-self: center; width: min(260px, 76%); margin-top: var(--vh-space-5); }
  .vh-entry .article-desc-body { max-width: none; }
}

/* handles — scannable cards. Icon · platform label + the handle VALUE (the hero) · open ↗.
   The value wraps (no silent truncation, HANDLE-02) and `min-width:0` lets every flex child
   shrink, so even a 225-char handle stays inside the track (HANDLE-01). A per-platform colour
   accent on the left edge gives the wall of handles scannable cues (HANDLE-04). */
.vh-handles-section { margin: var(--vh-space-6) 0 0; }
.vh-section-h { font-size: var(--vh-fs-400); margin: 0 0 var(--vh-space-3); }
.vh-handles-cat {
  font-size: var(--vh-fs-50); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--vh-fg-faint); margin: var(--vh-space-4) 0 var(--vh-space-2);
}
.vh-handles-cat:first-of-type { margin-top: 0; }
.vh-handles {
  list-style: none; padding: 0; margin: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 264px), 1fr));
  gap: var(--vh-space-2);
}
/* the card chrome lives on the <li> so a copy button can sit beside the link (FEATURE-01) */
.vh-handle {
  display: flex; align-items: stretch; min-width: 0;
  background: var(--vh-bg);
  border: 1px solid var(--vh-border); border-left: 3px solid var(--vh-pa, var(--vh-border-strong));
  border-radius: var(--vh-radius); box-shadow: var(--vh-shadow-sm);
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.vh-handle:hover {
  border-color: var(--vh-accent); border-left-color: var(--vh-pa, var(--vh-accent));
  box-shadow: var(--vh-shadow-md); transform: translateY(-1px);
}
.vh-handle__link {
  display: flex; align-items: center; gap: var(--vh-space-3); min-width: 0; flex: 1 1 auto;
  padding: var(--vh-space-3); min-height: var(--vh-tap);
  color: var(--vh-fg); text-decoration: none; border-radius: var(--vh-radius);
}
.vh-handle__link:hover { text-decoration: none; }
.vh-handle__copy {
  flex: 0 0 auto; align-self: stretch; width: 44px; display: inline-flex; align-items: center; justify-content: center;
  color: var(--vh-fg-muted); background: transparent; border: 0; border-left: 1px solid var(--vh-border); cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.vh-handle__copy:hover, .vh-handle__copy:focus-visible { color: var(--vh-accent); background: var(--vh-accent-soft); }
.vh-handle__copy.is-copied { color: var(--vh-success); background: var(--vh-surface); }
.vh-handle__copy.is-copied { background: color-mix(in srgb, var(--vh-success) 14%, transparent); }
.vh-handle__copy[hidden] { display: none; }
/* copy → check swap with a pop (FEATURE-01) */
.vh-handle__copy-ok { display: none; }
.vh-handle__copy.is-copied .vh-handle__copy-ic { display: none; }
.vh-handle__copy.is-copied .vh-handle__copy-ok { display: block; color: var(--vh-success); animation: vh-pop 0.3s ease; }
@keyframes vh-pop { 0% { transform: scale(0.4); opacity: 0; } 60% { transform: scale(1.25); } 100% { transform: scale(1); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .vh-handle__copy.is-copied .vh-handle__copy-ok { animation: none; } }
.vh-handle__icon {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 7px;
  background: var(--vh-surface); /* fallback (color-mix unsupported / no platform accent) */
  /* a subtle per-platform brand tint behind the self-hosted greyscale icon, so platforms are
     colour-distinguishable at a glance without re-introducing CDN icons (HANDLE-04). */
  background: color-mix(in srgb, var(--vh-pa, transparent) 15%, var(--vh-surface));
  padding: 3px; display: inline-flex; align-items: center; justify-content: center;
}
.vh-handle__icon img { width: 100%; height: 100%; object-fit: contain; display: block; }
.vh-icon-fallback {
  flex: 0 0 auto; display: inline-flex; width: 30px; height: 30px; align-items: center; justify-content: center;
  border-radius: 7px; background: var(--vh-inset); border: 1px solid var(--vh-border);
  color: var(--vh-fg-muted); font: 600 var(--vh-fs-100) var(--vh-font); padding: 0;
}
.vh-handle__body { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.vh-handle__platform {
  display: flex; align-items: center; gap: var(--vh-space-1);
  font-size: var(--vh-fs-50); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--vh-fg-faint);
}
.vh-handle__verified { display: block; flex: 0 0 auto; }
.vh-handle__value { font-size: var(--vh-fs-100); font-weight: 600; color: var(--vh-accent); overflow-wrap: anywhere; word-break: break-word; line-height: 1.35; }
.vh-handle__open { flex: 0 0 auto; align-self: center; color: var(--vh-fg-faint); font-size: var(--vh-fs-200); }
.vh-handle:hover .vh-handle__open { color: var(--vh-accent); }

/* per-platform left-accent colours (CSS only — no CDN icons; HANDLE-04) */
.vh-pa--twitter { --vh-pa: #1d9bf0; } .vh-pa--facebook { --vh-pa: #1877f2; }
.vh-pa--instagram { --vh-pa: #e1306c; } .vh-pa--youtube { --vh-pa: #ff0000; }
.vh-pa--tiktok { --vh-pa: #ff2d55; } .vh-pa--linkedin { --vh-pa: #0a66c2; }
.vh-pa--spotify { --vh-pa: #1db954; } .vh-pa--soundcloud { --vh-pa: #ff5500; }
.vh-pa--snapchat { --vh-pa: #d4b400; } .vh-pa--pinterest { --vh-pa: #e60023; }
.vh-pa--reddit { --vh-pa: #ff4500; } .vh-pa--twitch { --vh-pa: #9146ff; }
.vh-pa--github { --vh-pa: #8b949e; } .vh-pa--threads { --vh-pa: #9a6cff; }
.vh-pa--vk { --vh-pa: #0077ff; } .vh-pa--vimeo { --vh-pa: #1ab7ea; }
.vh-pa--onlyfans { --vh-pa: #00aff0; } .vh-pa--deezer { --vh-pa: #a238ff; }
.vh-pa--tumblr { --vh-pa: #6a8db5; }

.vh-identifiers { margin-top: var(--vh-space-5); }
.vh-identifiers > summary {
  cursor: pointer; display: inline-flex; align-items: center; gap: var(--vh-space-2);
  min-height: var(--vh-tap); font-size: var(--vh-fs-100); font-weight: 500; color: var(--vh-fg-muted);
  padding: var(--vh-space-2) var(--vh-space-4); background: var(--vh-surface); border: 1px solid var(--vh-border); border-radius: 999px;
  list-style: none;
}
.vh-identifiers > summary::-webkit-details-marker { display: none; }
.vh-identifiers > summary::before { content: '+'; font-weight: 600; }
.vh-identifiers[open] > summary::before { content: '−'; }
.vh-identifiers > summary:hover { color: var(--vh-accent); border-color: var(--vh-accent); }
.vh-identifiers .vh-handles { margin-top: var(--vh-space-3); }
.vh-handles-note { color: var(--vh-fg-muted); font-size: var(--vh-fs-100); margin: var(--vh-space-3) 0 var(--vh-space-4); }
.vh-entry-note { margin: var(--vh-space-3) 0 0; font-size: var(--vh-fs-100); color: var(--vh-fg-faint); }
/* honest empty state for QID stubs / contentless entries (ENTRY-06, COPY-01) */
.vh-empty-state { margin-top: var(--vh-space-6); padding: var(--vh-space-6); text-align: center; border: 1px dashed var(--vh-border-strong); border-radius: var(--vh-radius-lg); background: var(--vh-surface); }
.vh-empty-state__h { margin: 0 0 var(--vh-space-2); font-size: var(--vh-fs-400); }
.vh-empty-state p { margin: 0 auto var(--vh-space-4); max-width: 48ch; color: var(--vh-fg-muted); }

/* --------------------------------------------------------------- search --- */
.vh-results { list-style: none; padding: 0; margin: var(--vh-space-4) 0 0; }
.vh-result { position: relative; padding: var(--vh-space-4); margin-bottom: var(--vh-space-2); border: 1px solid var(--vh-border); border-radius: var(--vh-radius); background: var(--vh-bg); box-shadow: var(--vh-shadow-sm); transition: border-color 0.15s, box-shadow 0.15s; }
.vh-result:hover { border-color: var(--vh-accent); box-shadow: var(--vh-shadow-md); }
/* The whole card is the link: stretch the title anchor's hit area over the card (one semantic link,
   accessible name stays the title). Keyboard focus lifts the card via :focus-within. */
.vh-result:focus-within { border-color: var(--vh-accent); box-shadow: var(--vh-shadow-md); outline: 2px solid var(--vh-accent); outline-offset: 2px; }
.vh-result__head { display: flex; align-items: center; gap: var(--vh-space-2); flex-wrap: wrap; }
.vh-result__title { font-weight: 600; font-size: var(--vh-fs-300); }
.vh-result__title::after { content: ''; position: absolute; inset: 0; border-radius: var(--vh-radius); }
.vh-result__title:focus-visible { outline: none; } /* focus shown on the card (:focus-within) */
.vh-result__sub { color: var(--vh-fg-muted); font-size: var(--vh-fs-100); margin-top: var(--vh-space-1); }
.vh-result__snippet { color: var(--vh-fg-muted); font-size: var(--vh-fs-100); margin-top: var(--vh-space-1); }
/* FRONTEND-007: the title's ::after overlay (above) stretches the link over the whole card, which
   also covers the subtitle/snippet text and makes it unselectable. Lift those text blocks above the
   overlay so they stay selectable; the rest of the card area remains the clickable link. */
/* UX-009: snippet/subtitle stay selectable (z-index:1 keeps them above the stretched title overlay)
   but `pointer-events:none` lets a CLICK fall through to the full-card overlay link → the whole card
   navigates. FRONTEND-007 lifted these for selection but accidentally made them swallow clicks. */
.vh-result__sub, .vh-result__snippet { position: relative; z-index: 1; pointer-events: none; }
.vh-result__snippet mark { background: var(--vh-mark-bg); color: var(--vh-mark-fg); padding: 0 2px; border-radius: 3px; }
.vh-searchinfo { color: var(--vh-fg-muted); }
.vh-pager { display: flex; gap: var(--vh-space-4); align-items: center; margin-top: var(--vh-space-5); }
.vh-pager a { font-weight: 500; min-height: var(--vh-tap); display: inline-flex; align-items: center; }
.vh-pager__pos { color: var(--vh-fg-faint); font-size: var(--vh-fs-100); }

/* -------------------------------------------------- generic message page -- */
.article-desc { font-size: var(--vh-fs-300); }
.article-body .article-desc { max-width: 60ch; }

/* back-to-top (injected by site.ts; FEATURE-14) */
.vh-to-top {
  position: fixed; bottom: var(--vh-space-5); right: var(--vh-space-5); z-index: 40;
  width: var(--vh-tap); height: var(--vh-tap); border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--vh-fs-300); color: var(--vh-fg); background: var(--vh-surface-2);
  border: 1px solid var(--vh-field-border); box-shadow: var(--vh-shadow-md); cursor: pointer;
  opacity: 0; visibility: hidden; transform: translateY(8px);
  transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;
}
.vh-to-top.is-visible { opacity: 1; visibility: visible; transform: translateY(0); }
.vh-to-top:hover { border-color: var(--vh-accent); color: var(--vh-accent); }

/* ----------------------------------------------------- in-place editor ---- */
.vh-hidden { display: none !important; }
.vh-edit-btn, .vh-action {
  display: inline-flex; align-items: center; gap: var(--vh-space-2); min-height: var(--vh-tap);
  padding: var(--vh-space-2) var(--vh-space-4); font: 600 var(--vh-fs-100) var(--vh-font); color: var(--vh-fg);
  background: var(--vh-surface); border: 1px solid var(--vh-field-border); border-radius: 999px; cursor: pointer; text-decoration: none;
}
.vh-edit-btn:hover, .vh-action:hover { border-color: var(--vh-accent); color: var(--vh-fg); text-decoration: none; }
/* Edit reads as the primary action in the cluster: an accent ghost (History/Share stay neutral). */
.vh-edit-btn { color: var(--vh-accent); border-color: var(--vh-accent); }
.vh-edit-btn:hover { color: var(--vh-accent); background: var(--vh-accent-soft); }
.vh-edit-btn[hidden], .vh-action[hidden] { display: none; }

.vh-editor { display: block; max-width: 880px; margin: 0 auto; color: var(--vh-fg); }
.vh-editor h2 { margin: 0 0 var(--vh-space-4); }
.vh-editor__section { margin-bottom: var(--vh-space-5); padding-bottom: var(--vh-space-4); border-bottom: 1px solid var(--vh-border); }
.vh-editor__section:last-of-type { border-bottom: 0; }
.vh-editor__h { font-size: var(--vh-fs-400); margin: 0 0 var(--vh-space-3); }
.vh-editor__banner { padding: var(--vh-space-3) var(--vh-space-4); margin-bottom: var(--vh-space-4); border-radius: var(--vh-radius); border: 1px solid var(--vh-border-strong); background: var(--vh-surface); color: var(--vh-fg); }
.vh-editor__banner.is-ok { border-color: var(--vh-success); background: rgba(63, 185, 80, 0.1); background: color-mix(in srgb, var(--vh-success) 8%, var(--vh-surface)); }
.vh-editor__banner.is-warn { border-color: var(--vh-attention); background: rgba(210, 153, 34, 0.12); background: color-mix(in srgb, var(--vh-attention) 8%, var(--vh-surface)); }
.vh-editor__banner.is-error { border-color: var(--vh-danger); background: rgba(207, 34, 46, 0.1); background: color-mix(in srgb, var(--vh-danger) 8%, var(--vh-surface)); }
.vh-editor__banner .vh-banner-actions { margin-top: var(--vh-space-2); display: flex; gap: var(--vh-space-2); flex-wrap: wrap; }

/* ENTRY-LIVE-001: the "this entry has been updated" banner (src/client/site.ts initLiveFreshness),
   shown only while the visitor is actively editing — an idle visitor gets a silent reload instead. */
.vh-freshness-banner {
  display: flex; align-items: center; justify-content: space-between; gap: var(--vh-space-3);
  padding: var(--vh-space-3) var(--vh-space-4); margin-bottom: var(--vh-space-4);
  border-radius: var(--vh-radius); border: 1px solid var(--vh-attention);
  background: color-mix(in srgb, var(--vh-attention) 8%, var(--vh-surface));
  color: var(--vh-fg); font-size: var(--vh-fs-100);
}
.vh-freshness-banner__refresh {
  font: inherit; font-weight: 600; padding: var(--vh-space-1) var(--vh-space-3);
  min-height: var(--vh-tap); color: var(--vh-fg); background: var(--vh-surface);
  border: 1px solid var(--vh-field-border); border-radius: 8px; cursor: pointer; white-space: nowrap;
}
.vh-freshness-banner__refresh:hover { border-color: var(--vh-accent); }

/* INFRA-001: the staging/beta banner (views/BetaBanner) — the same shell as the freshness banner,
   in the ATTENTION colour rather than a neutral one, sitting flush at the very top of the page
   (above all page chrome) because it is standing context about which SITE this is. Full-bleed and
   not dismissible on purpose; a staging page must never be mistakable for production. */
.vh-beta-banner {
  align-items: flex-start; flex-wrap: wrap; border-radius: 0;
  border-width: 0 0 2px 0; margin: 0 0 var(--vh-space-4);
  padding: var(--vh-space-3) var(--vh-space-4);
  background: color-mix(in srgb, var(--vh-attention) 14%, var(--vh-surface));
}
.vh-beta-banner strong { font-weight: 700; }

/* ROUTE-010 §3.3: the "redirected from X" banner (views/RedirectedFromBanner). Same shell as the
   freshness banner above — it only sits above the page chrome instead of inside the article, and
   wraps on a phone because it carries a sentence plus a link rather than a short line + button. */
.vh-redirected { align-items: flex-start; flex-wrap: wrap; margin-top: var(--vh-space-4); }
.vh-redirected strong { font-weight: 600; word-break: break-all; }

/* ROUTE-010 — the entry-404 recovery block (views/NotFound): the suggestions slot plus the
   always-present "Search for …" line. Constrained to the reading measure like .article-desc. */
.vh-notfound { max-width: 70ch; margin-top: var(--vh-space-5); }
.vh-notfound__h { font-size: var(--vh-fs-300); margin: 0 0 var(--vh-space-3); }

/* Admin preview banner (console.tsx `${C}/preview/:vhid`) — prepended via EntryPage's optional
   `banner` prop, which is undefined on the public path, so this never appears there. Sticky top
   strip in the danger tint, deliberately loud so it can't be mistaken for real site chrome. */
.vh-admin-preview-banner {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: center; gap: var(--vh-space-3);
  flex-wrap: wrap; text-align: center;
  padding: var(--vh-space-2) var(--vh-space-4);
  background: var(--vh-danger); color: #fff; font-size: var(--vh-fs-100);
}
.vh-admin-preview-banner a { color: #fff; text-decoration: underline; }

/* ENTRY-LIVE-004: the idle-path soft transition when the freshness poll swaps fresh content into
   article.vh-entry in place (no navigation). `startViewTransition` gets the browser's own
   cross-fade where supported; `.vh-entry--swap-in` is the manual fallback everywhere else — added
   just before the content is replaced, removed a frame later so the new content fades in from
   transparent. Both respect prefers-reduced-motion (the global `*` rule above zeroes this
   transition; the view-transition pseudo-elements have their own explicit override above). */
.vh-entry { transition: opacity 220ms ease; }
.vh-entry.vh-entry--swap-in { opacity: 0; }

/* ENTRY-LIVE-008: the post-save confirmation toast (entry-swap.ts showEntryToast), hosted in
   page chrome (Layout.tsx #vh-toast-root) so it survives the post-save swap unmounting the editor.
   `pointer-events:none` on the host keeps it from blocking clicks on whatever's behind/below it
   everywhere it's empty; individual toasts opt back in so their own close button still works. */
.vh-toast-root {
  position: fixed; z-index: 60; right: var(--vh-space-4); bottom: var(--vh-space-4);
  display: flex; flex-direction: column-reverse; gap: var(--vh-space-2);
  max-width: min(360px, calc(100vw - var(--vh-space-4) * 2));
  pointer-events: none;
}
.vh-toast {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: var(--vh-space-2);
  padding: var(--vh-space-3) var(--vh-space-4);
  border-radius: var(--vh-radius); border: 1px solid var(--vh-field-border);
  background: var(--vh-surface); color: var(--vh-fg); font-size: var(--vh-fs-100);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  animation: vh-toast-in 220ms ease;
}
.vh-toast.is-ok { border-color: var(--vh-success); background: color-mix(in srgb, var(--vh-success) 10%, var(--vh-surface)); }
.vh-toast.is-error { border-color: var(--vh-danger); background: color-mix(in srgb, var(--vh-danger) 10%, var(--vh-surface)); }
.vh-toast__text { flex: 1 1 auto; }
.vh-toast__close {
  flex: 0 0 auto; margin: -4px -4px -4px 0; padding: var(--vh-space-1) var(--vh-space-2);
  min-height: var(--vh-tap); min-width: var(--vh-tap);
  font: inherit; font-size: var(--vh-fs-300); line-height: 1; color: var(--vh-fg-muted);
  background: none; border: 0; border-radius: 6px; cursor: pointer;
}
.vh-toast__close:hover { color: var(--vh-fg); background: color-mix(in srgb, currentColor 10%, transparent); }
@keyframes vh-toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
@media (max-width: 480px) {
  .vh-toast-root { left: var(--vh-space-4); right: var(--vh-space-4); max-width: none; }
}

.vh-field { display: block; margin-bottom: var(--vh-space-4); }
.vh-field > label, .vh-field > span { display: block; margin-bottom: var(--vh-space-1); color: var(--vh-fg-muted); font-size: var(--vh-fs-100); font-weight: 500; }
.vh-field input[type='text'], .vh-field textarea, .vh-field select,
.vh-claim-target input[type='text'], .vh-claim-target textarea,
.vh-console input[type='text'], .vh-console input[type='number'], .vh-console select {
  width: 100%; padding: var(--vh-space-2) var(--vh-space-3); font: inherit; color: var(--vh-fg); background: var(--vh-bg);
  border: 1px solid var(--vh-field-border); border-radius: 8px; min-height: var(--vh-tap);
}
.vh-field textarea { min-height: 8rem; resize: vertical; }
.vh-editor__toolbar { margin-bottom: var(--vh-space-2); display: flex; gap: var(--vh-space-1); flex-wrap: wrap; }
.vh-editor__toolbar button { font: inherit; min-width: var(--vh-tap); min-height: var(--vh-tap); padding: var(--vh-space-2); color: var(--vh-fg); background: var(--vh-surface); border: 1px solid var(--vh-field-border); border-radius: 8px; cursor: pointer; }
.vh-editor__toolbar button:hover { border-color: var(--vh-accent); }
.vh-editor__hint { margin: var(--vh-space-1) 0 var(--vh-space-2); font-size: var(--vh-fs-75); color: var(--vh-fg-muted); }
.vh-tb-link { font-weight: 600; }
/* UX-017: per-field Wikidata provenance indicator (badge / "Let Wikidata manage this" control).
   Classes only — no inline styles (CSP style-src 'self'). NB the `.vh-field > .vh-field__labelrow`
   selector is needed so this flex layout beats the more-specific `.vh-field > span` block rule above
   (the inline-label variant is a direct-child <span> of the .vh-field <label>). */
.vh-field > .vh-field__labelrow,
.vh-field__labelrow { display: flex; align-items: baseline; justify-content: space-between; gap: var(--vh-space-2); flex-wrap: wrap; margin-bottom: var(--vh-space-1); }
/* the inner label text span no longer matches `.vh-field > span`, so restore its muted label styling */
.vh-field__labelrow > span:first-child { display: block; margin-bottom: 0; color: var(--vh-fg-muted); font-size: var(--vh-fs-100); font-weight: 500; }
.vh-field__labelrow--head { margin-bottom: var(--vh-space-3); }
.vh-field__labelrow--head .vh-editor__h { margin: 0; }
.vh-prov { font-size: var(--vh-fs-75); font-weight: 500; line-height: 1.4; }
.vh-prov--synced {
  color: var(--vh-fg-muted);
  /* UX-018 put a "Stop Wikidata managing this" pill next to the badge text — align them on the text
     baseline and let the pill wrap under on a narrow field instead of squeezing the label row. */
  display: inline-flex; align-items: baseline; gap: var(--vh-space-2); flex-wrap: wrap;
}
.vh-prov--armed { color: var(--vh-accent); }
/* BETA: protected/claimed entities never sync — explain why the "Let Wikidata manage this" control
   is absent, and point to reporting the entry (styled like the muted synced badge, but its link
   needs its own accent + underline since it's the interactive part of an otherwise plain note). */
.vh-prov--locked { color: var(--vh-fg-muted); }
.vh-prov--locked a { color: var(--vh-accent); text-decoration: underline; }
/* UX-018's "Stop Wikidata managing this" is the same affordance in the other direction, so it wears
   the same pill exactly — no new values. It sits inline after the synced badge's text, hence the gap. */
.vh-prov__give,
.vh-prov__take {
  font: inherit; font-size: var(--vh-fs-75); font-weight: 500; cursor: pointer; color: var(--vh-accent);
  background: transparent; border: 1px solid var(--vh-field-border); border-radius: 999px;
  padding: var(--vh-space-1) var(--vh-space-2); min-height: var(--vh-tap);
}
.vh-prov__give:hover,
.vh-prov__take:hover { border-color: var(--vh-accent); }
.vh-prov__undo {
  font: inherit; font-size: var(--vh-fs-75); cursor: pointer; color: var(--vh-accent);
  background: transparent; border: none; padding: 0; text-decoration: underline;
}
/* UX-019: the give-control gains a one-line reason beside it. Same inline-flex/gap/wrap treatment as
   `.vh-prov--synced` (which pairs a note with a pill the other way round), so the two label rows sit
   identically and the reason wraps under the pill on a narrow field instead of squeezing it. */
.vh-prov--manual { display: inline-flex; align-items: baseline; gap: var(--vh-space-2); flex-wrap: wrap; }
.vh-prov__why { color: var(--vh-fg-muted); font-weight: 400; }
/* UX-019: the entry-wide sync summary, above the first section. Reads as a muted standfirst, not a
   banner — it is standing context, not something that happened. Tokens only (both themes follow). */
.vh-syncsum {
  margin: 0 0 var(--vh-space-4); padding: var(--vh-space-2) var(--vh-space-3);
  border-left: 3px solid var(--vh-field-border); border-radius: 0 6px 6px 0;
  background: var(--vh-inset); color: var(--vh-fg-muted);
  font-size: var(--vh-fs-75); line-height: 1.5;
}
/* editor loading skeleton (STATE-01) */
.vh-editor--loading { display: flex; flex-direction: column; gap: var(--vh-space-3); }
.vh-skel {
  height: 1.5rem; border-radius: 8px; background: var(--vh-inset);
  background-image: linear-gradient(90deg, var(--vh-inset) 0%, var(--vh-surface-2) 50%, var(--vh-inset) 100%);
  background-size: 200% 100%; animation: vh-shimmer 1.4s ease-in-out infinite;
}
.vh-skel--title { height: 2.4rem; width: 50%; }
.vh-skel--box { height: 8rem; }
@keyframes vh-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .vh-skel { animation: none; } }
/* handle editor — a responsive grid so a high-handle entity isn't one long scroll (EDITOR-07).
   Each card reuses the read-view's .vh-handle__icon/.vh-icon-fallback badge (§entry handles) and
   the form-wide .vh-field label-over-input pattern, so the editor and the public entry page share
   one visual language for "this is a platform handle" instead of two competing ones. */
.vh-handles-edit { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 330px), 1fr)); gap: var(--vh-space-2); margin-bottom: var(--vh-space-3); }
.vh-handle-row { display: flex; gap: var(--vh-space-3); align-items: flex-start; padding: var(--vh-space-3); border: 1px solid var(--vh-border); border-radius: var(--vh-radius); background: var(--vh-bg); box-shadow: var(--vh-shadow-sm); }
.vh-handle-row .vh-handle__icon, .vh-handle-row .vh-icon-fallback { margin-top: 22px; } /* aligns with the Platform <select>, not the row top (the label sits above it) */
.vh-handle-row__fields { display: flex; flex-direction: column; gap: var(--vh-space-2); flex: 1 1 auto; min-width: 0; }
.vh-handle-row__field { margin-bottom: 0; }
.vh-handle-ctl { display: flex; flex-direction: column; gap: var(--vh-space-1); flex: 0 0 auto; margin-top: 22px; }
.vh-handle-row button { font: inherit; min-width: var(--vh-tap); min-height: var(--vh-tap); padding: var(--vh-space-2); color: var(--vh-fg); background: var(--vh-surface); border: 1px solid var(--vh-field-border); border-radius: 8px; cursor: pointer; }
.vh-handle-row button:hover { border-color: var(--vh-accent); }
/* Below ~480px (the breakpoint already used for other single-column stacks in this file) a
   vertical control cluster wastes width better spent on the fields — lay the three actions out
   in a row instead, and drop the icon's top-margin now that the row has room to breathe. */
@media (max-width: 480px) {
  .vh-handle-row { flex-wrap: wrap; }
  .vh-handle-row .vh-handle__icon, .vh-handle-row .vh-icon-fallback { margin-top: 0; }
  .vh-handle-ctl { flex-direction: row; margin-top: 0; margin-left: calc(30px + var(--vh-space-3)); }
}
.vh-editor__handle-actions { display: flex; gap: var(--vh-space-2); flex-wrap: wrap; }
/* sticky action bar so Save is reachable at any scroll on a high-handle entity (EDITOR-02) */
.vh-editor__actions { position: sticky; bottom: 0; margin-top: var(--vh-space-4); padding: var(--vh-space-3) 0; display: flex; gap: var(--vh-space-3); align-items: center; flex-wrap: wrap; background: var(--vh-bg); border-top: 1px solid var(--vh-border); z-index: 5; }
.vh-conflict-table { width: 100%; border-collapse: collapse; font-size: var(--vh-fs-100); margin: var(--vh-space-2) 0; }
.vh-conflict-table th, .vh-conflict-table td { text-align: left; border-bottom: 1px solid var(--vh-border); padding: 6px 8px; vertical-align: top; color: var(--vh-fg); }
.vh-conflict-table th { color: var(--vh-fg-muted); font-weight: 600; }

/* merged WYSIWYG description surface (cross-links Phase 2): one contenteditable, no separate preview */
.vh-richtext {
  margin-top: var(--vh-space-2); min-height: 9rem; padding: var(--vh-space-3);
  border: 1px solid var(--vh-field-border); border-radius: 8px; background: var(--vh-bg);
  color: var(--vh-fg); line-height: 1.6; overflow-wrap: break-word;
}
.vh-richtext:focus { outline: 2px solid var(--vh-accent); outline-offset: 1px; }
.vh-richtext:empty::before { content: attr(data-placeholder); color: var(--vh-fg-faint); }
.vh-richtext > :first-child { margin-top: 0; }
.vh-richtext p { margin: 0 0 var(--vh-space-2); }
.vh-richtext h2 { font-size: var(--vh-fs-400); margin: var(--vh-space-3) 0 var(--vh-space-2); }
.vh-richtext ul, .vh-richtext ol { margin: 0 0 var(--vh-space-2); padding-left: 1.4em; }
/* cross-link pills in the editor: atomic + chip-like so they read as a single unit */
.vh-link-edit {
  text-decoration: none; padding: 1px 6px; border-radius: 6px; white-space: nowrap;
  background: var(--vh-inset); background: color-mix(in srgb, var(--vh-accent) 14%, transparent);
}
.vh-link-edit:hover { text-decoration: none; }
.vh-link-edit--missing { color: var(--vh-fg-faint); background: var(--vh-inset); }
.vh-richtext .vh-link-ext, .vh-richtext .vh-link-edit { cursor: default; }

/* Link command popover — entity-search combobox/listbox + external-URL mode (CSP: class-only) */
.vh-linksearch {
  margin: var(--vh-space-2) 0; border: 1px solid var(--vh-border-strong);
  border-radius: var(--vh-radius); background: var(--vh-surface); overflow: hidden;
}
.vh-linksearch__tabs { display: flex; gap: var(--vh-space-1); align-items: center; border-bottom: 1px solid var(--vh-border); padding: 0 var(--vh-space-2); }
.vh-linksearch__tab {
  font: inherit; padding: var(--vh-space-2) var(--vh-space-2); min-height: var(--vh-tap);
  background: transparent; color: var(--vh-fg-muted); border: 0; border-bottom: 2px solid transparent; cursor: pointer;
}
.vh-linksearch__tab.is-active { color: var(--vh-fg); border-bottom-color: var(--vh-accent); }
.vh-linksearch__close { margin-left: auto; font: inherit; background: transparent; border: 0; color: var(--vh-fg-muted); cursor: pointer; padding: 0 var(--vh-space-2); min-height: var(--vh-tap); }
.vh-linksearch__box { padding: var(--vh-space-3); }
.vh-linksearch__input {
  width: 100%; padding: var(--vh-space-2) var(--vh-space-3); font: inherit; color: var(--vh-fg);
  background: var(--vh-bg); border: 1px solid var(--vh-field-border); border-radius: 8px; min-height: var(--vh-tap);
}
.vh-linksearch__list { list-style: none; margin: var(--vh-space-2) 0 0; padding: 0; max-height: 16rem; overflow-y: auto; }
.vh-linksearch__opt { display: flex; flex-direction: column; gap: 1px; padding: var(--vh-space-2) var(--vh-space-3); border-radius: 8px; cursor: pointer; }
.vh-linksearch__opt.is-active { background: var(--vh-inset); background: color-mix(in srgb, var(--vh-accent) 14%, var(--vh-surface)); }
.vh-linksearch__title { color: var(--vh-fg); font-weight: 500; }
.vh-linksearch__sub { color: var(--vh-fg-muted); font-size: var(--vh-fs-75); }
.vh-linksearch__empty { padding: var(--vh-space-2) var(--vh-space-3); color: var(--vh-fg-muted); }
.vh-linksearch__err { color: var(--vh-danger); font-size: var(--vh-fs-75); margin: 0 0 var(--vh-space-2); }

/* PLATPICKER — replaces the handle row's bare <select> (doc 05 §2.3's "platform picker
   (autocomplete)"). Trigger matches .vh-field select's box model 1:1 (border/radius/min-height/
   padding tokens above) so swapping <select> for <button> doesn't shift the row's rhythm. */
.vh-platpicker-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: var(--vh-space-2);
  width: 100%; font: inherit; text-align: left; color: var(--vh-fg); background: var(--vh-bg);
  border: 1px solid var(--vh-field-border); border-radius: 8px; min-height: var(--vh-tap);
  padding: var(--vh-space-2) var(--vh-space-3); cursor: pointer;
}
.vh-platpicker-trigger:hover { border-color: var(--vh-accent); }
.vh-platpicker-trigger__chev { color: var(--vh-fg-muted); }

/* Mobile-first: a full-page drawer sliding up from the bottom — same backdrop/visibility/
   transition-delay technique as .vh-menu-overlay/.vh-menu-panel (never focusable off-screen), no
   existing bottom-sheet precedent so this part is new. `.vh-platpicker-open` (toggled on <html> by
   editor.ts, mirroring .vh-menu-open) is its own class rather than reusing .vh-menu-open, so the
   picker's scroll-lock isn't coupled to the unrelated nav menu's.
   z-index 190/200: the only existing values in this file are .vh-menu-overlay/.vh-menu-panel's
   90/100 — the nav menu and this picker should never be open together, but a clearly higher tier
   avoids a latent stacking bug if that ever changes, rather than reusing 90/100 outright. */
.vh-platpicker-overlay {
  position: fixed; inset: 0; z-index: 190; background: rgba(8, 11, 16, 0.58);
  opacity: 0; visibility: hidden; transition: opacity 0.2s ease;
}
.vh-platpicker-overlay.is-open { opacity: 1; visibility: visible; }
@supports (backdrop-filter: blur(2px)) { .vh-platpicker-overlay { backdrop-filter: blur(2px); } }
.vh-platpicker-open { overflow: hidden; } /* JS path scroll-lock, mirrors .vh-menu-open */
.vh-platpicker {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 200; max-height: 88vh;
  background: var(--vh-bg); border-radius: var(--vh-radius-lg) var(--vh-radius-lg) 0 0;
  box-shadow: var(--vh-shadow-lg); transform: translateY(100%); visibility: hidden;
  transition: transform 0.24s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 0.24s;
  display: flex; flex-direction: column; overflow: hidden;
}
.vh-platpicker.is-open { transform: translateY(0); visibility: visible; transition-delay: 0s; }
/* Desktop/tablet: a centered modal instead of a full-page drawer. 768px matches the most recent
   responsive-split precedent (.vh-catalog-table's table→card breakpoint, chosen there to match
   "the console's existing sidebar/header collapse points") rather than a new value. */
@media (min-width: 768px) {
  .vh-platpicker {
    left: 50%; right: auto; top: 50%; bottom: auto; width: min(480px, 90vw); max-height: 70vh;
    transform: translate(-50%, -46%); border-radius: var(--vh-radius-lg);
  }
  .vh-platpicker.is-open { transform: translate(-50%, -50%); }
}
.vh-platpicker__head { display: flex; align-items: center; gap: var(--vh-space-2); padding: var(--vh-space-3); border-bottom: 1px solid var(--vh-border); flex: 0 0 auto; }
.vh-platpicker__input {
  flex: 1 1 auto; font: inherit; color: var(--vh-fg); background: var(--vh-bg);
  padding: var(--vh-space-2) var(--vh-space-3); border: 1px solid var(--vh-field-border); border-radius: 8px; min-height: var(--vh-tap);
}
.vh-platpicker__close { margin-left: auto; font: inherit; background: transparent; border: 0; color: var(--vh-fg-muted); cursor: pointer; padding: 0 var(--vh-space-2); min-height: var(--vh-tap); min-width: var(--vh-tap); }
.vh-platpicker__list { list-style: none; margin: 0; padding: var(--vh-space-2); overflow-y: auto; overscroll-behavior: contain; }
.vh-platpicker__opt { display: flex; align-items: center; gap: var(--vh-space-2); padding: var(--vh-space-2) var(--vh-space-3); border-radius: 8px; cursor: pointer; min-height: var(--vh-tap); }
.vh-platpicker__opt.is-active { background: var(--vh-inset); background: color-mix(in srgb, var(--vh-accent) 14%, var(--vh-surface)); }
.vh-platpicker__opt[aria-selected='true'] { font-weight: 600; } /* the row's CURRENT platform */
.vh-platpicker__icon { flex: 0 0 auto; width: 20px; height: 20px; object-fit: contain; display: inline-flex; align-items: center; justify-content: center; font-size: var(--vh-fs-50); font-weight: 700; color: var(--vh-fg-muted); }
.vh-platpicker__name { color: var(--vh-fg); }
.vh-platpicker__empty { padding: var(--vh-space-2) var(--vh-space-3); color: var(--vh-fg-muted); }

.vh-btn-primary, .vh-btn-secondary, .vh-btn-danger {
  font: 600 var(--vh-fs-100) var(--vh-font); padding: var(--vh-control-pad); min-height: var(--vh-tap);
  border-radius: 999px; cursor: pointer; display: inline-flex; align-items: center;
  justify-content: center; gap: var(--vh-space-2); white-space: nowrap;
}
.vh-btn-primary { color: var(--vh-on-accent); background: var(--vh-accent); border: 1px solid transparent; transition: background-color 150ms ease; }
.vh-btn-primary:hover { filter: brightness(1.08); }
.vh-btn-primary:disabled { opacity: 0.55; cursor: default; filter: none; }
/* ENTRY-LIVE-004: the post-save "✓ Saved" confirmation stays disabled (the editor is about to be
   swapped away) but must NOT dim like a normal disabled button — the whole point is a clear,
   celebratory confirmation, not a greyed-out one. */
.vh-btn-primary.is-saved, .vh-btn-primary.is-saved:disabled { background: var(--vh-success); opacity: 1; }
.vh-btn-check { display: inline-block; animation: vh-pop 0.3s ease; }
@media (prefers-reduced-motion: reduce) { .vh-btn-check { animation: none; } }
.vh-btn-secondary { color: var(--vh-fg); background: transparent; border: 1px solid var(--vh-field-border); }
.vh-btn-secondary:hover { border-color: var(--vh-accent); }
.vh-btn-danger { color: var(--vh-danger); background: transparent; border: 1px solid var(--vh-danger); }
.vh-btn-danger:hover { color: var(--vh-on-accent); background: var(--vh-danger); }

/* -------------------------------------------------- management console ---- */
/* admin shell — branded sidebar + grouped nav with an active state (CONSOLE-02/09, BRAND-04) */
.vh-console { max-width: 1200px; margin: var(--vh-space-5) auto 0; color: var(--vh-fg); display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: var(--vh-space-6); align-items: start; }
.vh-console-side { position: sticky; top: var(--vh-space-5); display: flex; flex-direction: column; gap: var(--vh-space-4); min-width: 0; }
.vh-console-brand { display: flex; flex-direction: column; gap: var(--vh-space-1); align-items: flex-start; }
.vh-console-brand img { height: 26px; width: auto; display: block; filter: var(--vh-logo-filter, none); }
.vh-console-tag { font-size: var(--vh-fs-50); font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; color: var(--vh-fg-faint); }
.vh-console-nav { display: flex; flex-direction: column; gap: 2px; }
.vh-nav-group { display: flex; flex-direction: column; gap: 2px; }
.vh-nav-grouph { margin: var(--vh-space-3) 0 var(--vh-space-1); font-size: var(--vh-fs-50); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--vh-fg-faint); }
.vh-console-nav a { display: flex; align-items: center; min-height: 40px; padding: var(--vh-space-2) var(--vh-space-3); border-radius: 8px; color: var(--vh-fg-muted); font-weight: 500; font-size: var(--vh-fs-100); text-decoration: none; }
.vh-console-nav a:hover { background: var(--vh-surface); color: var(--vh-fg); }
.vh-console-nav a.is-active { background: var(--vh-accent-soft); color: var(--vh-accent); font-weight: 600; }
.vh-nav-signout { margin-top: var(--vh-space-2); }
.vh-console-user { margin-top: var(--vh-space-2); padding-top: var(--vh-space-3); border-top: 1px solid var(--vh-border); display: flex; flex-direction: column; gap: 2px; }
.vh-console-who { color: var(--vh-fg); font-size: var(--vh-fs-100); font-weight: 600; }
.vh-console-role { color: var(--vh-fg-faint); font-size: var(--vh-fs-50); text-transform: uppercase; letter-spacing: 0.04em; }
.vh-console-main { min-width: 0; }
.vh-console-main h1 { margin-top: 0; font-size: var(--vh-fs-500); }
.vh-console-list { line-height: 1.8; }
.vh-table-wrap { overflow-x: auto; max-width: 100%; border: 1px solid var(--vh-border); border-radius: var(--vh-radius); }
.vh-console-table { width: 100%; border-collapse: collapse; font-size: var(--vh-fs-100); }
.vh-console-table caption { text-align: left; color: var(--vh-fg-muted); font-size: var(--vh-fs-75); padding: 0 0 var(--vh-space-2); }
.vh-console-table th { text-align: left; border-bottom: 2px solid var(--vh-border-strong); padding: var(--vh-space-2) var(--vh-space-3); color: var(--vh-fg-muted); font-weight: 600; font-size: var(--vh-fs-75); white-space: nowrap; }
.vh-console-table td { border-bottom: 1px solid var(--vh-border); padding: var(--vh-space-2) var(--vh-space-3); vertical-align: middle; }
.vh-console-table tbody tr:nth-child(even) { background: var(--vh-surface); }
.vh-console-table tbody tr:hover { background: var(--vh-accent-soft); }

/* UX-CATALOG: platform catalog admin table — a real <table> with visible <th> column labels (the
   old version crammed 9 controls into ONE cell via aria-label only) so ~60 rows stay scannable and
   comparable column-to-column (e.g. rank) on desktop/tablet. Below 768px it degrades to a stacked
   label:value card per row — same semantic <table>/<th>/<td>, no duplicated markup — via the
   data-th attribute technique below (breakpoint matches the console's existing sidebar/header
   collapse points, not a new one invented for this page). */
.vh-catalog-table td { vertical-align: top; }
.vh-cat-id { display: flex; align-items: center; gap: var(--vh-space-2); min-height: var(--vh-tap); }
.vh-cat-icon { border-radius: 6px; flex: 0 0 auto; }
.vh-cat-icon--empty {
  width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center;
  background: var(--vh-surface); color: var(--vh-fg-muted); font-size: var(--vh-fs-50); font-weight: 700;
  border-radius: 6px;
}
/* the per-row save <form> carries no visible fields (they live in sibling <td>s via form="…") */
.vh-catalog-row-form { display: contents; }
.vh-cat-flags, .vh-cat-enabled { display: flex; flex-direction: column; gap: var(--vh-space-1); }
.vh-flag-chip {
  display: flex; align-items: center; gap: var(--vh-space-2); min-height: var(--vh-tap);
  padding: var(--vh-space-1) var(--vh-space-2); margin: 0 calc(var(--vh-space-2) * -1);
  border-radius: 8px; font-size: var(--vh-fs-75); color: var(--vh-fg-muted); white-space: nowrap; cursor: pointer;
}
.vh-flag-chip:hover { background: var(--vh-surface); }
.vh-flag-chip input { min-width: 0; min-height: 0; width: 16px; height: 16px; flex: 0 0 auto; }
.vh-flag-chip--primary { font-weight: 600; color: var(--vh-fg); }
.vh-cat-actions { display: flex; flex-direction: column; align-items: flex-start; gap: var(--vh-space-2); }
.vh-cat-delete-form { margin: 0; }
.vh-cat-save { min-width: 5.5rem; }
@media (max-width: 768px) {
  /* Stack the semantic table into a card per row instead of horizontal-scrolling a 10-column
     table on a phone — the <th>/<td> pairing stays in the DOM (a11y), CSS just re-flows it. */
  .vh-catalog-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .vh-catalog-table, .vh-catalog-table tbody, .vh-catalog-table tr, .vh-catalog-table td { display: block; width: 100%; }
  .vh-catalog-table tr {
    margin-bottom: var(--vh-space-4); padding: var(--vh-space-3);
    border: 1px solid var(--vh-border); border-radius: var(--vh-radius);
    background: var(--vh-bg); box-shadow: var(--vh-shadow-sm);
  }
  .vh-catalog-table tbody tr:nth-child(even) { background: var(--vh-bg); } /* the card border replaces the zebra stripe */
  .vh-catalog-table td { border-bottom: 0; padding: var(--vh-space-2) 0; }
  .vh-catalog-table td[data-th]::before {
    content: attr(data-th); display: block; margin-bottom: var(--vh-space-1);
    font-size: var(--vh-fs-75); font-weight: 600; color: var(--vh-fg-muted);
  }
  .vh-catalog-table td:first-child { padding-top: 0; }
  .vh-catalog-table td:last-child { padding-bottom: 0; }
  .vh-cat-actions { flex-direction: row; flex-wrap: wrap; align-items: center; }
}

/* toolbar: "Add a platform" / "Filter" as two clearly separated cards instead of bare inline-forms
   bleeding straight into the table below them. */
.vh-catalog-toolbar { display: flex; flex-wrap: wrap; gap: var(--vh-space-3); margin-bottom: var(--vh-space-4); }
.vh-catalog-toolbar__form {
  display: block; flex: 1 1 320px; margin: 0; padding: var(--vh-space-3) var(--vh-space-4);
  border: 1px solid var(--vh-border); border-radius: var(--vh-radius); background: var(--vh-surface);
}
.vh-catalog-toolbar__h { margin: 0 0 var(--vh-space-2); font-size: var(--vh-fs-75); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--vh-fg-muted); }
.vh-catalog-toolbar__row { display: flex; flex-wrap: wrap; gap: var(--vh-space-2); align-items: center; }
.vh-catalog-toolbar__row input[type='text'] { flex: 1 1 12rem; min-width: 0; }

.vh-review-item { border: 1px solid var(--vh-border); border-radius: var(--vh-radius); padding: var(--vh-space-4); margin-bottom: var(--vh-space-4); background: var(--vh-bg); box-shadow: var(--vh-shadow-sm); }
.vh-review-item h3 { margin: 0 0 var(--vh-space-3); font-size: var(--vh-fs-300); }
.vh-pre { background: var(--vh-surface); color: var(--vh-fg); border: 1px solid var(--vh-border); border-radius: 8px; padding: var(--vh-space-3); overflow: auto; white-space: pre-wrap; word-break: break-word; font-size: var(--vh-fs-75); }
/* readable proposal summary (CONSOLE-01) */
.vh-proposal { margin-bottom: var(--vh-space-3); }
.vh-proposal-fields { display: grid; grid-template-columns: minmax(72px, max-content) 1fr; gap: var(--vh-space-1) var(--vh-space-4); margin: 0 0 var(--vh-space-3); }
.vh-proposal-row { display: contents; }
.vh-proposal-fields dt { color: var(--vh-fg-muted); font-weight: 600; font-size: var(--vh-fs-75); text-transform: uppercase; letter-spacing: 0.03em; }
.vh-proposal-fields dd { margin: 0; color: var(--vh-fg); overflow-wrap: anywhere; }
.vh-proposal-handles p { margin: var(--vh-space-1) 0; }
.vh-raw-toggle { margin-top: var(--vh-space-2); }
.vh-raw-toggle summary { cursor: pointer; color: var(--vh-fg-muted); font-size: var(--vh-fs-75); }
/* dashboard stat cards (FEATURE-09) */
.vh-stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--vh-space-3); margin: 0 0 var(--vh-space-5); }
.vh-stat { display: block; padding: var(--vh-space-4); border: 1px solid var(--vh-border); border-radius: var(--vh-radius); background: var(--vh-bg); box-shadow: var(--vh-shadow-sm); color: inherit; }
a.vh-stat:hover { border-color: var(--vh-accent); text-decoration: none; }
.vh-stat-num { display: block; font-size: var(--vh-fs-600); font-weight: 700; line-height: 1.1; }
.vh-stat-label { display: block; margin-top: var(--vh-space-1); font-size: var(--vh-fs-75); color: var(--vh-fg-muted); }

/* account area (FEATURE-11): summary line, role badge, submission status badges */
.vh-account-meta { margin: 0 0 var(--vh-space-5); color: var(--vh-fg-muted); }
.vh-role-badge {
  display: inline-block; padding: var(--vh-space-1) var(--vh-space-2); border-radius: 999px;
  font-size: var(--vh-fs-50); font-weight: 600; color: var(--vh-accent); background: var(--vh-accent-soft);
}
.vh-status {
  display: inline-block; padding: var(--vh-space-1) var(--vh-space-2); border-radius: 999px;
  font-size: var(--vh-fs-50); font-weight: 600; text-transform: capitalize;
  color: var(--vh-fg-muted); background: var(--vh-surface); border: 1px solid var(--vh-border);
}
.vh-status--approved, .vh-status--resolved {
  color: var(--vh-success); background: var(--vh-surface);
  background: color-mix(in srgb, var(--vh-success) 12%, var(--vh-bg));
  border-color: color-mix(in srgb, var(--vh-success) 35%, var(--vh-bg));
}
.vh-status--rejected, .vh-status--spam {
  color: var(--vh-danger); background: var(--vh-surface);
  background: color-mix(in srgb, var(--vh-danger) 8%, var(--vh-bg));
  border-color: color-mix(in srgb, var(--vh-danger) 35%, var(--vh-bg));
}
.vh-status--trending {
  color: var(--vh-accent); background: var(--vh-surface);
  background: color-mix(in srgb, var(--vh-accent) 12%, var(--vh-bg));
  border-color: color-mix(in srgb, var(--vh-accent) 35%, var(--vh-bg));
}

/* submission detail (beta want, 2026-09-11): two-way comment thread */
.vh-comment-thread { display: flex; flex-direction: column; gap: var(--vh-space-3); margin: 0 0 var(--vh-space-5); }
.vh-comment { padding: var(--vh-space-3); border: 1px solid var(--vh-border); border-radius: var(--vh-radius); background: var(--vh-surface); }
.vh-comment-meta { display: flex; flex-wrap: wrap; gap: var(--vh-space-2); align-items: center; margin: 0 0 var(--vh-space-1); font-size: var(--vh-fs-75); }
.vh-comment-body { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }

/* a reporter's own report history on /:key/report (beta want, 2026-09-11) */
.vh-report-history { margin: 0 0 var(--vh-space-5); }
.vh-report-history h2 { font-size: var(--vh-fs-100); margin: 0 0 var(--vh-space-3); }
.vh-report-history ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--vh-space-3); }
.vh-report-history li { padding: var(--vh-space-3) var(--vh-space-4); border: 1px solid var(--vh-border); border-radius: 10px; }
.vh-report-history li p { margin: 0; }
.vh-report-history li p + p { margin-top: var(--vh-space-2); }

/* console entity finder (FEATURE-10): search-by-name jump on Policy/History */
.vh-finder { margin: 0 0 var(--vh-space-5); }
.vh-finder .vh-inline-form input[type='text'] { flex: 1 1 18rem; min-width: 0; }
.vh-finder-results {
  list-style: none; margin: var(--vh-space-3) 0 0; padding: 0; max-width: 42rem;
  border: 1px solid var(--vh-border); border-radius: var(--vh-radius); overflow: hidden;
}
.vh-finder-results li + li { border-top: 1px solid var(--vh-border); }
.vh-finder-results a {
  display: flex; justify-content: space-between; gap: var(--vh-space-3); align-items: baseline;
  padding: var(--vh-space-3) var(--vh-space-4); color: var(--vh-fg); min-height: var(--vh-tap);
}
.vh-finder-results a:hover { background: var(--vh-surface); text-decoration: none; }
.vh-finder-title { font-weight: 600; }
.vh-finder-meta { color: var(--vh-fg-muted); font-size: var(--vh-fs-75); }

/* readable audit detail + console footer (CONSOLE-10 / FOOTER-04) */
.vh-detail { margin: 0; display: grid; gap: var(--vh-space-1); font-size: var(--vh-fs-75); }
.vh-detail-row { display: flex; gap: var(--vh-space-2); }
.vh-detail-row dt { flex: 0 0 auto; color: var(--vh-fg-muted); font-weight: 600; }
.vh-detail-row dd { margin: 0; color: var(--vh-fg); word-break: break-word; }
.vh-detail-empty { color: var(--vh-fg-faint); }
.vh-console-foot { margin-top: var(--vh-space-7); padding-top: var(--vh-space-4); border-top: 1px solid var(--vh-border); color: var(--vh-fg-faint); font-size: var(--vh-fs-75); }
.vh-inline-form { display: inline-flex; flex-wrap: wrap; gap: var(--vh-space-2); align-items: center; margin: var(--vh-space-1) var(--vh-space-2) var(--vh-space-1) 0; }
.vh-stack-form { display: flex; flex-direction: column; gap: var(--vh-space-3); max-width: 560px; }
.vh-stack-form label { display: flex; flex-direction: column; gap: var(--vh-space-1); color: var(--vh-fg-muted); font-size: var(--vh-fs-100); font-weight: 500; }
/* A checkbox reads as a row (box beside its text), not the label-above-control column. */
.vh-stack-form label.vh-check { flex-direction: row; align-items: center; gap: var(--vh-space-2); }
.vh-stack-form label.vh-check input { flex: 0 0 auto; }
.vh-stack-form input, .vh-stack-form textarea, .vh-stack-form select { font: inherit; color: var(--vh-fg); background: var(--vh-bg); border: 1px solid var(--vh-field-border); border-radius: 8px; padding: var(--vh-space-2) var(--vh-space-3); }
/* Scoped to bare console buttons only — design-system .vh-btn-* variants keep their own
   styling instead of being clobbered then re-overridden per variant (CONSOLE-07). */
.vh-console button:not([class*='vh-btn-']) { font: 600 var(--vh-fs-100) var(--vh-font); padding: var(--vh-control-pad); color: var(--vh-fg); background: var(--vh-surface); border: 1px solid var(--vh-field-border); border-radius: 999px; cursor: pointer; }
.vh-console button:not([class*='vh-btn-']):hover { border-color: var(--vh-accent); }
.vh-w5 { width: 5rem; } .vh-w6 { width: 9rem; }
.vh-note { color: var(--vh-fg-muted); font-size: 0.85rem; }

/* -------------------------------------------------- history + revision ---- */
.vh-history { margin-top: var(--vh-space-2); }
.vh-history-list { list-style: none; padding: 0; margin: var(--vh-space-4) 0 0; }
/* date-group header (HIST-01) */
.vh-hist-date { list-style: none; margin: var(--vh-space-5) 0 var(--vh-space-1); padding-top: var(--vh-space-3); border-top: 1px solid var(--vh-border); font-size: var(--vh-fs-50); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--vh-fg-faint); }
.vh-history-list > .vh-hist-date:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
/* NB: do NOT add `.vh-history-list > li { display: block }` — its (0,1,1) specificity beats the
 * `.vh-hist-row` (0,1,0) flex rule and collapses the row into inline text. */
.vh-hist-row { display: flex; flex-wrap: wrap; gap: var(--vh-space-1) var(--vh-space-3); align-items: baseline; padding: var(--vh-space-3) var(--vh-space-1); border-bottom: 1px solid var(--vh-border); }
.vh-hist-row:hover { background: var(--vh-surface); }
.vh-hist-row--hidden { opacity: 0.55; }
.vh-hist-ts { font-variant-numeric: tabular-nums; color: var(--vh-fg-faint); font-size: var(--vh-fs-75); flex: 0 0 auto; min-width: 0; }
.vh-hist-link { display: inline-flex; align-items: center; min-height: 28px; font-size: var(--vh-fs-50); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; color: var(--vh-fg-muted); background: var(--vh-surface); border: 1px solid var(--vh-border); border-radius: 999px; padding: var(--vh-space-1) var(--vh-space-3); }
.vh-hist-link:hover { color: var(--vh-accent); border-color: var(--vh-accent); text-decoration: none; }
.vh-hist-actor { font-weight: 600; }
/* Native no-JS tooltip (title attr) for a known legacy-wiki technical account (e.g. "AB" =
   Auto-Bot) — a subtle dotted underline + help cursor, no visual weight added beyond that. */
.vh-actor-tip { text-decoration: underline dotted; text-decoration-color: var(--vh-fg-faint); text-underline-offset: 2px; cursor: help; }
.vh-hist-summary { color: var(--vh-fg-muted); }
.vh-badge { font-size: 0.68rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; padding: 2px 8px; border-radius: 999px; color: var(--vh-fg-muted); background: var(--vh-surface); border: 1px solid var(--vh-border); }
.vh-bot-group > summary { display: inline-flex; align-items: center; min-height: var(--vh-tap); cursor: pointer; color: var(--vh-fg-muted); padding: var(--vh-space-2) var(--vh-space-3); font-weight: 500; }
.vh-revision > p:first-child { margin-top: 0; }
.vh-revision h2 { font-size: 1.15rem; margin: 22px 0 10px; }
.vh-handle-diff { margin: var(--vh-space-3) 0; }
.vh-handle-diff p { margin: var(--vh-space-2) 0; }
.vh-rev-meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; margin: 10px 0; color: var(--vh-fg-muted); }
.vh-rev-meta strong { color: var(--vh-fg); }
.vh-warn { color: var(--vh-attention); }
.vh-suppress { margin: var(--vh-space-3) 0; }
.vh-legacy-render { border: 1px solid var(--vh-border); border-radius: var(--vh-radius); padding: 14px; overflow-x: auto; background: var(--vh-surface); }
/* +/− markers so add/remove read without colour (WCAG 1.4.1 — HIST-03) */
.vh-field-diff .vh-diff-before { color: var(--vh-attention); }
.vh-field-diff .vh-diff-before::before { content: '− '; }
.vh-field-diff .vh-diff-after { color: var(--vh-success); }
.vh-field-diff .vh-diff-after::before { content: '+ '; }
.wt-diff { white-space: pre-wrap; word-break: break-word; font-size: var(--vh-fs-75); line-height: 1.5; overflow-x: auto; background: var(--vh-surface); color: var(--vh-fg); border: 1px solid var(--vh-border); border-radius: 8px; padding: var(--vh-space-3); }
.wt-diff-add { display: block; background: rgba(63, 185, 80, 0.16); background: color-mix(in srgb, var(--vh-success) 16%, transparent); }
.wt-diff-add::before { content: '+ '; font-weight: 700; }
.wt-diff-del { display: block; background: rgba(207, 34, 46, 0.16); background: color-mix(in srgb, var(--vh-danger) 16%, transparent); }
.wt-diff-del::before { content: '− '; font-weight: 700; }
.wt-diff-ctx { display: block; color: var(--vh-fg-muted); }
.wt-diff-ctx::before { content: '\00a0\00a0'; }
code { font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Menlo, Consolas, monospace; font-size: 0.9em; background: var(--vh-surface); padding: 1px 5px; border-radius: 5px; }

/* ----------------------------------------------------------- responsive --- */
@media (max-width: 880px) {
  /* console: sidebar collapses to a horizontal nav strip (no 3-line wrap — CONSOLE-02) */
  .vh-console { grid-template-columns: 1fr; gap: var(--vh-space-4); }
  .vh-console-side { position: static; }
  .vh-console-nav { flex-direction: row; flex-wrap: nowrap; overflow-x: auto; gap: var(--vh-space-1); padding-bottom: var(--vh-space-1); -webkit-overflow-scrolling: touch; }
  .vh-nav-group { flex-direction: row; gap: var(--vh-space-1); }
  .vh-nav-grouph { display: none; }
  .vh-console-nav a { white-space: nowrap; }
  .vh-nav-signout { margin-top: 0; }
  .vh-console-user { flex-direction: row; gap: var(--vh-space-2); align-items: baseline; border-top: 0; padding-top: 0; margin-top: 0; }
}
@media (max-width: 768px) {
  body { padding: 0 var(--vh-space-3) var(--vh-space-7); }
  .vh-site-header { gap: var(--vh-space-2); }
  /* compact square mark replaces the wide wordmark; search fills the freed row and the menu sits
     at the right edge (no spacer competing for width) — fixes the 561–768 cramped search (HEADER-06) */
  .vh-logo-wide { display: none; }
  .vh-logo-mark { display: block; }
  .vh-header-spacer { display: none; }
  .vh-site-header .vh-sb { flex: 1 1 auto; max-width: none; min-width: 0; }
  .article-desc-body { max-width: none; }
}
@media (max-width: 560px) {
  /* tappable icon-only menu within a phone width (the wordmark→mark swap happens at ≤768 above) */
  .vh-menu-btn { padding: var(--vh-space-2) var(--vh-space-3); }
  .vh-menu-btn__label { display: none; }
}
@media (max-width: 520px) {
  .vh-featured__row { grid-template-columns: 1fr 1fr; }
  .vh-featured__card { padding: var(--vh-space-3) var(--vh-space-4); }
  /* .vh-handles auto-collapses to one column via minmax(min(100%,264px),1fr) — no rule needed */
}

/* -------------------------------------------------- editor: image (MEDIA-002) ---- */
/* Doc 05 §2.3's image field: the current portrait + its source, then either the Commons picker or
   the action row. All same-origin imagery (CSP img-src 'self') — Commons thumbs come through the
   /img/c preview proxy. No inline styles anywhere (🔒 style-src 'self'). */
.vh-image { display: grid; gap: var(--vh-space-3); }
.vh-image__current { display: flex; gap: var(--vh-space-3); align-items: flex-start; }
.vh-image__thumb { width: 100px; height: 100px; object-fit: cover; border-radius: 10px; border: 1px solid var(--vh-border); background: var(--vh-surface); flex: 0 0 auto; }
.vh-image__meta { min-width: 0; }
.vh-image__meta p { margin: 0 0 var(--vh-space-1); }
.vh-image__actions { display: flex; flex-wrap: wrap; gap: var(--vh-space-2); align-items: center; }
.vh-note--error { color: var(--vh-danger); }
.vh-note--muted { color: var(--vh-fg-faint); }
.vh-note--ok { color: var(--vh-success); }

/* Review queue status tabs (pending vs. resolved history) — same underline-active pattern as
   .vh-linksearch__tab, its own BEM scope since this is a plain SSR <a> nav, not that popover's JS tabs. */
.vh-review-tabs { display: flex; gap: var(--vh-space-3); align-items: center; flex-wrap: wrap; border-bottom: 1px solid var(--vh-border); margin: var(--vh-space-3) 0 var(--vh-space-4); }
.vh-review-tabs__link { padding: var(--vh-space-2) 0; min-height: var(--vh-tap); display: inline-flex; align-items: center; color: var(--vh-fg-muted); font-weight: 500; text-decoration: none; border-bottom: 2px solid transparent; }
.vh-review-tabs__link:hover { color: var(--vh-fg); }
.vh-review-tabs__link.is-active { color: var(--vh-fg); border-bottom-color: var(--vh-accent); }

/* The upload control is a <label> wrapping a visually-hidden <input type=file>, so it needs the
   button's focus ring to come from the input's :focus-visible (the input is what receives focus). */
.vh-image__upload { display: inline-flex; cursor: pointer; }
.vh-image__upload input:focus-visible + .vh-btn-secondary,
.vh-image__upload:focus-within .vh-btn-secondary { outline: 2px solid var(--vh-accent); outline-offset: 2px; }

/* Commons picker: a responsive thumbnail grid. `auto-fill` + minmax keeps it from collapsing to one
   very wide column on a phone and from stretching thumbnails on a desktop. */
.vh-imgpick { display: grid; gap: var(--vh-space-2); }
.vh-imgpick__grid { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--vh-space-2); grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); max-height: 22rem; overflow-y: auto; }
.vh-imgpick__item { margin: 0; }
.vh-imgpick__btn { display: grid; gap: var(--vh-space-1); justify-items: center; width: 100%; padding: var(--vh-space-2); font: inherit; text-align: center; color: var(--vh-fg); background: var(--vh-surface); border: 1px solid var(--vh-border); border-radius: 10px; cursor: pointer; }
.vh-imgpick__btn:hover:not(:disabled) { border-color: var(--vh-accent); }
.vh-imgpick__btn:disabled { opacity: 0.55; cursor: default; }
.vh-imgpick__btn img { width: 100px; height: 100px; object-fit: cover; border-radius: 8px; background: var(--vh-bg); }
/* Long Commons filenames must not blow out the grid column. */
.vh-imgpick__name { font-size: var(--vh-fs-50); line-height: 1.3; color: var(--vh-fg-muted); overflow-wrap: anywhere; }
.vh-imgpick__busy { font-size: var(--vh-fs-50); color: var(--vh-accent); }
.vh-imgpick__actions { display: flex; gap: var(--vh-space-2); }

/* -------------------------------------------------- console charts (SYNC-007) ----
   Server-rendered inline SVG (see src/views/Chart.tsx): under CSP `style-src 'self'` a bar's
   length cannot be an inline style, so it is an SVG geometry attribute and everything that CAN
   be a class — every colour here — is one. Same theme tokens as the rest of the console, so the
   charts follow light/dark and the forced-colors block above like any other component. */
.vh-chart { margin: 0 0 var(--vh-space-5); }
.vh-chart-cap { color: var(--vh-fg-muted); font-size: var(--vh-fs-75); font-weight: 600; margin-bottom: var(--vh-space-2); }
.vh-chart-svg { display: block; width: 100%; height: auto; overflow: visible; }
.vh-chart-empty { margin: 0; padding: var(--vh-space-4); border: 1px dashed var(--vh-border); border-radius: var(--vh-radius); }
.vh-chart-grid { stroke: var(--vh-border); stroke-width: 1; }
.vh-chart-axis { stroke: var(--vh-border-strong); stroke-width: 1; }
.vh-chart-tick, .vh-chart-xlab { fill: var(--vh-fg-faint); font-size: 10px; font-family: var(--vh-font); }
.vh-chart-bar { stroke: none; }
.vh-chart-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.vh-chart-area { stroke: none; opacity: 0.16; }
/* Fills. `-changed` reuses the success token, `-error` the danger token, so the semantics of a
   colour on a chart match the same colour on a status pill elsewhere in the console. */
.vh-chart-fill-accent { fill: var(--vh-accent); }
.vh-chart-fill-changed { fill: var(--vh-success); }
.vh-chart-fill-quiet { fill: var(--vh-border-strong); }
.vh-chart-fill-error { fill: var(--vh-danger); }
.vh-chart-fill-warn { fill: var(--vh-fg-faint); }
.vh-chart-line-accent { stroke: var(--vh-accent); fill: var(--vh-accent); }
.vh-chart-line-error { stroke: var(--vh-danger); fill: var(--vh-danger); }
.vh-chart-legend { list-style: none; display: flex; flex-wrap: wrap; gap: var(--vh-space-3); margin: var(--vh-space-2) 0 0; padding: 0; font-size: var(--vh-fs-75); color: var(--vh-fg-muted); }
.vh-chart-legend li { display: inline-flex; align-items: center; gap: var(--vh-space-1); }
.vh-chart-key { display: inline-block; width: 10px; height: 10px; border-radius: 2px; background: currentColor; }
/* The key swatches reuse the SVG fill classes, which set `fill` — map that to a background here so
   one class can paint both a <rect> and a legend chip. */
.vh-chart-key.vh-chart-fill-accent { background: var(--vh-accent); }
.vh-chart-key.vh-chart-fill-changed { background: var(--vh-success); }
.vh-chart-key.vh-chart-fill-quiet { background: var(--vh-border-strong); }
.vh-chart-key.vh-chart-fill-error { background: var(--vh-danger); }
.vh-chart-key.vh-chart-fill-warn { background: var(--vh-fg-faint); }

/* Horizontal distribution bars (staleness histogram). Grid so labels/values align across rows;
   stacks to label-over-bar on a phone rather than squeezing the bar to nothing. */
.vh-hbar { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--vh-space-2); }
.vh-hbar-row { display: grid; grid-template-columns: minmax(5.5rem, 8rem) 1fr minmax(6rem, max-content); gap: var(--vh-space-3); align-items: center; font-size: var(--vh-fs-75); }
.vh-hbar-label { color: var(--vh-fg-muted); }
.vh-hbar-svg { display: block; width: 100%; height: 12px; }
.vh-hbar-track { fill: var(--vh-surface-2); }
.vh-hbar-val { color: var(--vh-fg); text-align: right; font-variant-numeric: tabular-nums; }
.vh-hbar-pct { color: var(--vh-fg-faint); }
@media (max-width: 560px) {
  .vh-hbar-row { grid-template-columns: 1fr max-content; }
  .vh-hbar-svg { grid-column: 1 / -1; order: 3; }
}

/* Two-up chart/panel row that collapses to one column on a phone, matching .vh-stat-row's shape. */
.vh-chart-grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--vh-space-4); }
/* A "what changed" comparison panel (cadence effect): before/after side by side. */
.vh-compare { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--vh-space-3); margin: 0 0 var(--vh-space-4); }
.vh-compare-col { padding: var(--vh-space-3) var(--vh-space-4); border: 1px solid var(--vh-border); border-radius: var(--vh-radius); background: var(--vh-bg); }
.vh-compare-col h4 { margin: 0 0 var(--vh-space-2); font-size: var(--vh-fs-75); text-transform: uppercase; letter-spacing: 0.04em; color: var(--vh-fg-faint); }
.vh-compare-col dl { margin: 0; display: grid; grid-template-columns: 1fr max-content; gap: var(--vh-space-1) var(--vh-space-3); font-size: var(--vh-fs-100); }
.vh-compare-col dt { color: var(--vh-fg-muted); }
.vh-compare-col dd { margin: 0; font-variant-numeric: tabular-nums; }
.vh-compare-col--now { border-color: var(--vh-accent); }

/* Per-entity "Attention & sync" panel on the console's Policy page (SYNC-008). Nothing bespoke —
   it reuses .vh-console-table/.vh-table-wrap; this only spaces the section from the entity header
   and the policy form below it, matching the rhythm of the sync dashboard's sections. */
.vh-entry-attention { margin: 0 0 var(--vh-space-5); }
.vh-entry-attention h2 { margin-top: var(--vh-space-5); }
