/*
 * 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;
  /* The sticky site header's height: its tallest control (search field / menu button, both
     --vh-tap) + its block padding + its bottom border. Anything pinned beneath it sticks here. */
  --vh-header-h: calc(var(--vh-tap) + 2 * var(--vh-space-3) + 1px);

  --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);
  min-height: var(--vh-header-h); /* never shorter than what is pinned under it assumes */
  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; min-height: 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); }
/* Pinned variant — the history pages, whose lists and diffs run to many screens: the way back to
   the entry rides directly under the sticky site header (below its z-index, so the search
   suggestions still open over it). One line, ellipsised, so a long title can never grow the bar —
   its height is exactly --vh-tap, which is what the scroll-padding below relies on to keep
   keyboard focus and in-page jumps from landing underneath the two bars. */
.vh-claim__back--pinned {
  position: sticky; top: var(--vh-header-h); z-index: 30;
  display: flex; align-items: center; min-height: var(--vh-tap);
  margin-bottom: var(--vh-space-3);
  background: var(--vh-bg); border-bottom: 1px solid var(--vh-border);
}
.vh-claim__back--pinned a { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
html:has(.vh-claim__back--pinned) { scroll-padding-top: calc(var(--vh-header-h) + var(--vh-tap)); }
.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; }

/* "About this image" (/:key/image) — the rights record as a label/value grid, the same shape as the
   console's .vh-proposal-fields, collapsing to one column on a phone. */
.vh-image-info__figure { width: min(260px, 76%); margin: 0 0 var(--vh-space-5); }
.vh-image-rights { display: grid; grid-template-columns: minmax(7rem, max-content) 1fr; gap: var(--vh-space-2) var(--vh-space-4); margin: 0 0 var(--vh-space-5); }
.vh-image-rights dt { color: var(--vh-fg-muted); font-weight: 600; }
.vh-image-rights dd { margin: 0; overflow-wrap: anywhere; }
.vh-image-rights dd .vh-note { margin: var(--vh-space-1) 0 0; }
.vh-rights-tag {
  display: inline-block; margin-left: var(--vh-space-2); padding: 0 var(--vh-space-2); border-radius: 999px;
  font-size: var(--vh-fs-50); font-weight: 600; color: var(--vh-fg-muted); background: var(--vh-surface); border: 1px solid var(--vh-border);
}
.vh-rights-tag--free {
  color: var(--vh-success);
  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));
}
@media (max-width: 560px) {
  .vh-image-rights { grid-template-columns: 1fr; gap: 0 0; }
  .vh-image-rights dd { margin-bottom: var(--vh-space-3); }
}
/* the reported image, on the report form and in the console's report views */
.vh-report-image { display: flex; align-items: center; gap: var(--vh-space-3); margin: 0; }
.vh-report-image img { width: 64px; height: 64px; object-fit: cover; border-radius: 8px; border: 1px solid var(--vh-border); background: var(--vh-surface); }
.vh-report-image figcaption { margin: 0; }
@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; }
/* `> input[type='search']`: the editor's Commons image search is a search box inside a .vh-field
   and rendered browser-bare without it. Direct child only, so a search box that brings its own
   styling (the platform picker's, inside its own head) can never be reached by this rule. */
.vh-field input[type='text'], .vh-field input[type='url'], .vh-field > input[type='search'], .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; }
/* EDITOR-013: the Ctrl/⌘+Enter save hint, at the quiet end of the action bar. Hidden by default
   and revealed only on a wide, fine-pointer viewport — a phone or tablet keyboard has no Ctrl key
   to offer, so the hint there is just noise crowding the bar. 768px is this file's established
   desktop split (.vh-platpicker's drawer→modal breakpoint, .vh-catalog-table's table→card one),
   not a new value. <kbd> matches the search panel's "Press Enter" note. */
.vh-editor__kbdhint { display: none; }
@media (min-width: 768px) and (pointer: fine) {
  .vh-editor__kbdhint { display: inline; margin-left: auto; font-size: var(--vh-fs-75); color: var(--vh-fg-muted); }
}
.vh-editor__kbdhint kbd {
  font: inherit; font-size: 0.85em; padding: 1px 6px;
  border: 1px solid var(--vh-field-border); border-radius: 5px;
  background: var(--vh-inset); background: color-mix(in srgb, var(--vh-fg) 8%, transparent);
}
.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; }
/* Edit conflict: each clashing field offers both versions as a radio pair — pick the cell to keep. */
.vh-conflict-pick { display: flex; align-items: baseline; gap: var(--vh-space-2); cursor: pointer; overflow-wrap: anywhere; }
.vh-conflict-pick input { margin: 0; flex: none; accent-color: var(--vh-accent); }

/* 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 */
/* PLATPICKER-VV: the drawer is laid out against the VISUAL viewport, not the layout one — editor.ts
   writes its box into --vh-vv-top/-left/-h/-w and keeps them current, so the keyboard opening
   shrinks the drawer to sit above it (search box still at the top, list scrolling in what is left),
   and closing it or pinch-zooming never strands the search box off-screen. The fallbacks are the
   layout viewport (no visualViewport API). A fixed height, not a max-height, so the sheet does not
   shrink from the top — moving the search box under the user's thumb — as typing filters the list. */
.vh-platpicker {
  position: fixed; z-index: 200;
  left: var(--vh-vv-left, 0px); width: var(--vh-vv-w, 100%);
  top: calc(var(--vh-vv-top, 0px) + var(--vh-vv-h, 100vh) * 0.12); height: calc(var(--vh-vv-h, 100vh) * 0.88);
  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%; width: min(480px, 90vw); height: auto;
    top: calc(var(--vh-vv-top, 0px) + var(--vh-vv-h, 100vh) / 2); max-height: calc(var(--vh-vv-h, 100vh) * 0.7);
    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; }
/* font-size restated after `font: inherit`: below 16px iOS Safari zooms the page on focus, and the
   drawer must not depend on whatever context it happens to be mounted in to stay at 16px. */
.vh-platpicker__input {
  flex: 1 1 auto; font: inherit; font-size: max(16px, 1em); 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, .vh-status--blocked {
  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; }
/* inline secondary text inside a line/cell that is not itself a note (counts, "unrecorded") */
.vh-muted { 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-handle-diff__list { list-style: none; margin: 0 0 var(--vh-space-3); padding: 0; display: flex; flex-wrap: wrap; gap: var(--vh-space-1) var(--vh-space-2); }
.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). The cells stay body text;
 * only the words that changed are tinted — <del> struck through, <ins> underlined, so the change
 * reads without colour too. Same tints as the wikitext diff (.wt-diff-add/-del) below. */
.vh-field-diff .vh-diff-before, .vh-field-diff .vh-diff-after { overflow-wrap: anywhere; }
.vh-field-diff .vh-diff-before::before { content: '− '; color: var(--vh-danger); font-weight: 700; }
.vh-field-diff .vh-diff-after::before { content: '+ '; color: var(--vh-success); font-weight: 700; }
.vh-diff-del, .vh-diff-ins { color: var(--vh-fg); border-radius: 3px; padding: 0 2px; }
.vh-diff-del { background: rgba(207, 34, 46, 0.16); background: color-mix(in srgb, var(--vh-danger) 16%, transparent); text-decoration: line-through; text-decoration-color: var(--vh-danger); }
.vh-diff-ins { background: rgba(63, 185, 80, 0.16); background: color-mix(in srgb, var(--vh-success) 16%, transparent); text-decoration: underline; text-decoration-color: var(--vh-success); text-underline-offset: 2px; }
.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); }

/* Image rights register (console /media) */
.vh-rights-thumb { display: block; width: 64px; height: 64px; object-fit: cover; border-radius: 8px; border: 1px solid var(--vh-border); background: var(--vh-surface); margin-bottom: var(--vh-space-1); }
.vh-rights-form { display: flex; flex-wrap: wrap; gap: var(--vh-space-2); align-items: center; margin-top: var(--vh-space-1); }
.vh-rights-form label { flex: 1 1 12rem; }
.vh-rights-uses { margin: 0; padding-left: var(--vh-space-4); }

/* 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); }

/* Image rights upload panel (editor): same frame as the Commons picker below; its fields are the
   standard .vh-field stack, and the rights confirmation is a checkbox row. */
.vh-upload .vh-field { margin-bottom: 0; }
.vh-upload .vh-editor__h { margin: 0; }
.vh-upload .vh-check { display: flex; gap: var(--vh-space-2); align-items: flex-start; line-height: 1.4; }
.vh-upload .vh-check input { flex: 0 0 auto; margin-top: 0.2em; }

/* 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); }
/* MEDIA-006: the tile's fixed 100px box — shimmers (.vh-skel) until the preview loads, so a queued
   or retrying thumbnail never shows the browser's broken-image glyph. */
.vh-imgpick__thumb { display: grid; place-items: center; width: 100px; height: 100px; border-radius: 8px; overflow: hidden; }
.vh-imgpick__thumb.vh-skel img { opacity: 0; }
.vh-imgpick__thumb--failed { padding: var(--vh-space-1); font-size: var(--vh-fs-50); line-height: 1.3; color: var(--vh-fg-muted); background: var(--vh-inset); }
/* 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); }

/* -------------------------------------------------- image preview / lightbox (MEDIA-007) ----
   client/lightbox.ts. CSP `style-src 'self'`: every state is a class or a native element here.
   Triggers: .vh-zoom (the entry figure's link, the editor's current image) and the picker's
   per-tile .vh-imgpick__zoom. */
.vh-zoom { display: block; cursor: zoom-in; border-radius: var(--vh-radius-lg); }
.vh-zoom--btn { padding: 0; border: 0; background: none; font: inherit; flex: 0 0 auto; border-radius: 10px; }
.vh-zoom--btn .vh-image__thumb { display: block; }
.vh-imgpick__item { position: relative; }
.vh-imgpick__zoom {
  position: absolute; top: 6px; right: 6px; width: 28px; height: 28px; padding: 0; display: grid; place-items: center;
  font: inherit; font-size: 16px; line-height: 1; color: #fff; background: rgba(0, 0, 0, 0.62);
  border: 0; border-radius: 8px; cursor: zoom-in;
}
.vh-imgpick__zoom:hover { background: rgba(0, 0, 0, 0.8); }

html.vh-lightbox-open { overflow: hidden; }
.vh-lightbox {
  position: fixed; inset: 0; width: 100vw; height: 100vh; height: 100dvh; max-width: none; max-height: none;
  margin: 0; padding: 0; border: 0; color: #fff; background: rgba(8, 10, 14, 0.94); overflow: hidden;
}
.vh-lightbox::backdrop { background: rgba(8, 10, 14, 0.94); }
.vh-lightbox:not([open]) { display: none; }
/* Both pictures share one grid cell, so the full resolution lands EXACTLY over the thumbnail. */
/* minmax(0, 1fr): the one cell is the SCREEN, not the picture's natural size — without the 0 floor a
   tall image grows the row and overflows the viewport instead of being contained. */
.vh-lightbox__stage { display: grid; grid-template: minmax(0, 1fr) / minmax(0, 1fr); width: 100%; height: 100%; cursor: zoom-out; }
.vh-lightbox__img {
  grid-area: 1 / 1; width: 100%; height: 100%; min-width: 0; min-height: 0; max-width: none; object-fit: contain;
  /* leave the caption and the close button clear of the picture */
  padding: 56px 16px 72px; box-sizing: border-box;
}
.vh-lightbox__img--full { opacity: 0; transition: opacity 0.25s ease; }
.vh-lightbox__img--full.is-loaded { opacity: 1; }
.vh-lightbox__img--full:not([src]) { visibility: hidden; }
.vh-lightbox__progress {
  position: absolute; left: 0; top: 0; width: 100%; height: 4px; border: 0; border-radius: 0;
  appearance: none; -webkit-appearance: none; background: rgba(255, 255, 255, 0.16); accent-color: var(--vh-accent);
}
.vh-lightbox__progress::-webkit-progress-bar { background: rgba(255, 255, 255, 0.16); }
.vh-lightbox__progress::-webkit-progress-value { background: var(--vh-accent); transition: width 0.15s linear; }
.vh-lightbox__progress::-moz-progress-bar { background: var(--vh-accent); }
/* No value yet (waiting for the first byte): a moving stripe rather than an empty track. */
.vh-lightbox__progress:indeterminate {
  background-image: linear-gradient(90deg, transparent 0%, var(--vh-accent) 50%, transparent 100%);
  background-size: 200% 100%; animation: vh-shimmer 1.2s linear infinite;
}
.vh-lightbox__progress:indeterminate::-webkit-progress-bar { background: transparent; }
.vh-lightbox__progress:indeterminate::-moz-progress-bar { background: transparent; }
.vh-lightbox__progress[hidden] { display: none; }
.vh-lightbox__close {
  position: absolute; top: 10px; right: 12px; width: 44px; height: 44px; padding: 0; display: grid; place-items: center;
  font: inherit; font-size: 30px; line-height: 1; color: #fff; background: rgba(255, 255, 255, 0.12);
  border: 0; border-radius: 50%; cursor: pointer;
}
.vh-lightbox__close:hover { background: rgba(255, 255, 255, 0.24); }
.vh-lightbox__close:focus-visible { outline: 2px solid #fff; outline-offset: 2px; border-radius: 50%; }
.vh-lightbox__caption {
  position: absolute; left: 0; right: 0; bottom: 0; margin: 0; padding: 14px 16px calc(14px + env(safe-area-inset-bottom));
  text-align: center; font-size: var(--vh-fs-75); line-height: 1.4; color: rgba(255, 255, 255, 0.86);
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));
}
.vh-lightbox__caption a { color: #fff; text-decoration: underline; }
.vh-lightbox__caption[hidden] { display: none; }
@media (forced-colors: active) {
  .vh-lightbox__close, .vh-imgpick__zoom { border: 1px solid ButtonText; }
}

/* -------------------------------------------------- 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); }
