/* Vivarium reproduction page styles — shared across all
 * src/layer1_wasm/<slug>/index.html and src/layer2_docker/<slug>/index.html
 * pages. Matches the Vivarium Core design system established by the
 * docs/landing site (teal #00d4aa accents, Space Grotesk + Inter +
 * JetBrains Mono fonts, dark canvas with bioluminescent atmosphere).
 *
 * Light/dark switching follows the user's OS preference via
 * prefers-color-scheme — these pages are usually opened in a fresh tab
 * from a verdict link, so we cannot rely on a cross-page theme toggle.
 */

@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap");

/* Theme tokens — matches rspress's pattern: html gets a `dark` class when
 * the user (or the inline early-theme script) decides dark mode is on,
 * else stays at the default :root values. The toggle in chrome.js writes
 * to localStorage['rspress-theme-appearance'] and flips html.dark, so
 * docs site and reproduction pages share one source of truth.
 */

:root {
  color-scheme: light;

  --vh-bg: #f5fbf9;
  --vh-bg-deep: #ffffff;
  --vh-text: #04221c;
  --vh-text-muted: #3a554f;
  --vh-text-dim: #5a7570;
  --vh-accent-teal: #00b894;
  --vh-accent-teal-bright: #00d4aa;
  --vh-accent-violet: #5229d4;
  --vh-accent-violet-bright: #7c5cff;
  --vh-accent-coral: #d7383b;
  --vh-hairline: rgba(4, 34, 28, 0.12);
  --vh-card-bg: rgba(4, 34, 28, 0.025);
  --vh-window-chrome: #e8ede9;
  --vh-window-bg: #00110e;
  --vh-code-bg: #001713;
  --vh-terminal-bg: #0a1f1a;
  --vh-terminal-border: rgba(0, 212, 170, 0.45);
  --vh-reproduced-bg: rgba(0, 212, 170, 0.12);
  --vh-reproduced-fg: #00624d;
  --vh-reproduced-border: rgba(0, 212, 170, 0.4);
  --vh-unreproduced-bg: rgba(255, 113, 108, 0.12);
  --vh-unreproduced-fg: #82071e;
  --vh-unreproduced-border: rgba(255, 113, 108, 0.4);
  --vh-pending-bg: rgba(124, 92, 255, 0.12);
  --vh-pending-fg: #4816cb;
  --vh-pending-border: rgba(124, 92, 255, 0.4);
}

html.dark {
  color-scheme: dark;

  --vh-bg: #00110e;
  --vh-bg-deep: #000508;
  --vh-text: #ffffff;
  --vh-text-muted: #a8b3b0;
  --vh-text-dim: #71807d;
  --vh-accent-teal: #00d4aa;
  --vh-accent-teal-bright: #33e5ba;
  --vh-accent-violet: #7c5cff;
  --vh-accent-violet-bright: #a18eff;
  --vh-accent-coral: #ff716c;
  /* Bumped from 8% to 22% white so chip / ghost-button borders
   * (kicker tag, "Bug context" / "Upstream issue" / "Fix candidate
   * PR" chips, runner Edit / Reset, Path A buttons) read against the
   * dark background. The previous 8% was visually flush with the
   * page background in dark mode, leaving the buttons looking
   * border-less even though the rule was applied. */
  --vh-hairline: rgba(255, 255, 255, 0.22);
  --vh-card-bg: rgba(255, 255, 255, 0.025);
  --vh-window-chrome: #18181b;
  --vh-window-bg: #000508;
  --vh-reproduced-fg: #33e5ba;
  --vh-unreproduced-fg: #ff716c;
  --vh-pending-fg: #a18eff;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--vh-bg);
  color: var(--vh-text);
  font-family:
    "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  line-height: 1.6;
  position: relative;
  overflow-x: hidden;
}

/* Subtle radial atmosphere — matches the home hero's "lit from within"
 * teal/violet glow. Only painted on dark mode where it has impact. */
html.dark body::before {
  content: "";
  position: fixed;
  top: -10%;
  left: -10%;
  width: 60%;
  height: 50%;
  background: radial-gradient(circle, rgba(0, 212, 170, 0.08), transparent 60%);
  filter: blur(60px);
  pointer-events: none;
  z-index: -1;
}

html.dark body::after {
  content: "";
  position: fixed;
  bottom: -10%;
  right: -10%;
  width: 50%;
  height: 50%;
  background: radial-gradient(
    circle,
    rgba(124, 92, 255, 0.06),
    transparent 60%
  );
  filter: blur(60px);
  pointer-events: none;
  z-index: -1;
}

/* ---------- Top nav (mirrors rspress's <header class="rp-nav">) ---------- */

.vh-topnav {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 60px;
  padding: 0 1.25rem;
  border-bottom: 1px solid var(--vh-hairline);
  background: var(--vh-bg);
  font-family: "Inter", sans-serif;
}

@media (min-width: 1280px) {
  .vh-topnav {
    padding: 0 2rem;
  }
}

.vh-topnav__left,
.vh-topnav__right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.vh-topnav__brand-link {
  display: inline-flex;
  align-items: center;
  font-family: "Inter", sans-serif;
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  color: var(--vh-text);
  text-decoration: none;
  border: 0;
  transition: opacity 0.18s ease;
}

.vh-topnav__brand-link:hover {
  opacity: 0.7;
  border-bottom: 0;
}

.vh-topnav__menu {
  display: none;
  flex: 1 1 auto;
  justify-content: flex-end;
  align-items: center;
  gap: 1.5rem;
  margin: 0 1rem;
}

@media (min-width: 960px) {
  .vh-topnav__menu {
    display: flex;
  }
}

.vh-topnav__link {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--vh-text-muted);
  text-decoration: none;
  border: 0;
  white-space: nowrap;
  transition: color 0.18s ease;
}

.vh-topnav__link:hover {
  color: var(--vh-text);
  border-bottom: 0;
}

.vh-topnav__icon,
.vh-topnav__theme {
  width: 36px;
  height: 36px;
  border-radius: 9999px;
  border: 0;
  background: transparent;
  color: var(--vh-text-muted);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  transition:
    background 0.18s ease,
    color 0.18s ease;
}

.vh-topnav__icon:hover,
.vh-topnav__theme:hover {
  background: color-mix(in srgb, var(--vh-text) 8%, transparent);
  color: var(--vh-text);
  border: 0;
}

.vh-topnav__icon svg,
.vh-topnav__theme svg {
  width: 18px;
  height: 18px;
}

main {
  max-width: 880px;
  margin: 0 auto;
  padding: 1.75rem 1.5rem 2rem;
  position: relative;
  z-index: 1;
}

@media (min-width: 768px) {
  main {
    padding: 2.25rem 2rem 2.5rem;
  }
}

/* ---------- Output section + progress bar (loading UX) ----------
 *
 * The progress bar and the `<pre id="output">` share a grid cell so
 * swapping between them doesn't shift the rest of the page. Min-height
 * keeps the cell at a comfortable size even before either element has
 * any content.
 */

.vh-output-section {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto;
}

.vh-output-section h2 {
  /* keep the section heading on its own row above the swap zone */
  grid-row: 1 / 2;
}

.vh-output-section .vh-progress,
.vh-output-section .vh-output {
  grid-row: 2 / 3;
  grid-column: 1 / 2;
  min-height: 7rem;
  transition: opacity 0.45s ease;
}

.vh-output {
  /* Hidden until the reproduction is done; chrome.js adds .is-revealed */
  opacity: 0;
  pointer-events: none;
}

.vh-output.is-revealed {
  opacity: 1;
  pointer-events: auto;
}

.vh-progress {
  margin: 0;
  padding: 1.5rem 1.5rem 1.25rem;
  border: 1px solid var(--vh-terminal-border);
  border-radius: 8px;
  background: var(--vh-terminal-bg);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.vh-progress.is-done {
  opacity: 0;
  pointer-events: none;
}

.vh-progress__bar {
  height: 6px;
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
}

:root:not(.dark) .vh-progress__bar {
  background: rgba(0, 0, 0, 0.08);
}

.vh-progress__fill {
  height: 100%;
  width: 5%;
  background: linear-gradient(
    90deg,
    var(--vh-accent-teal-bright),
    var(--vh-accent-violet-bright)
  );
  transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
  box-shadow: 0 0 12px rgba(0, 212, 170, 0.45);
}

.vh-progress__row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 0.6rem;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vh-progress__label {
  color: var(--vh-accent-teal-bright);
}

.vh-progress__bytes {
  color: var(--vh-text-dim);
}

/* ---------- Header ----------
 *
 * Originally this targeted the bare `<header>` inside `<main>`, but
 * `chrome.js` now also injects a `.vh-topnav` `<header>` at the top
 * of `<body>`. Scoping to `.vh-main__header` keeps the original
 * spacing for the recipe content header without bleeding into the
 * topnav (which had a `margin-bottom: 1.75rem` ghost gap below it
 * before this fix). */

.vh-main__header {
  margin-bottom: 1.75rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--vh-hairline);
}

.kicker {
  display: inline-block;
  margin: 0 0 1.25rem;
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--vh-hairline);
  border-radius: 9999px;
  background: rgba(0, 212, 170, 0.05);
  font-family:
    "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.65rem;
  color: var(--vh-accent-teal);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

h1 {
  margin: 0 0 1rem;
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: clamp(1.75rem, 4vw, 2.75rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--vh-text);
}

h1 a {
  color: inherit;
  border-bottom: 2px solid var(--vh-accent-teal);
  text-decoration: none;
  transition: border-color 0.18s ease;
}

h1 a:hover {
  border-bottom-color: var(--vh-accent-teal-bright);
}

h2 {
  margin: 0 0 1rem;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--vh-text-dim);
  font-weight: 600;
}

.lede {
  margin: 0;
  font-size: 1.05rem;
  line-height: 1.65;
  color: var(--vh-text-muted);
  max-width: 640px;
}

/* ---------- Body sections ---------- */

section {
  margin-bottom: 1.75rem;
}

a {
  color: var(--vh-accent-teal);
  text-decoration: none;
  border-bottom: 1px solid rgba(0, 212, 170, 0.4);
  transition: border-color 0.18s ease;
}

a:hover {
  border-bottom-color: var(--vh-accent-teal);
}

code {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.92em;
  padding: 0.1em 0.35em;
  background: var(--vh-card-bg);
  border-radius: 4px;
  color: var(--vh-text);
  border: 1px solid var(--vh-hairline);
}

/* ---------- Reproduction script (code block) ---------- */

pre {
  margin: 0;
  padding: 1.25rem 1.5rem;
  background: var(--vh-code-bg);
  border: 1px solid var(--vh-hairline);
  border-radius: 8px;
  overflow-x: auto;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.85rem;
  line-height: 1.65;
  color: #e0e6e3;
}

pre code {
  background: transparent;
  padding: 0;
  border: 0;
  border-radius: 0;
  font-size: inherit;
  color: inherit;
}

/* ---------- Verdict pill (the money element) ---------- */

.verdict {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1.25rem;
  border-radius: 8px;
  border: 1px solid;
  font-family: "JetBrains Mono", monospace;
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
}

.verdict::before {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 9999px;
  background: currentColor;
  content: "";
  flex-shrink: 0;
}

.verdict.pending {
  background: var(--vh-pending-bg);
  color: var(--vh-pending-fg);
  border-color: var(--vh-pending-border);
}

.verdict.pending::before {
  animation: vh-pulse 1.6s ease-in-out infinite;
}

.verdict.reproduced {
  background: var(--vh-reproduced-bg);
  color: var(--vh-reproduced-fg);
  border-color: var(--vh-reproduced-border);
  box-shadow: 0 0 24px rgba(0, 212, 170, 0.18);
}

.verdict.unreproduced {
  background: var(--vh-unreproduced-bg);
  color: var(--vh-unreproduced-fg);
  border-color: var(--vh-unreproduced-border);
  box-shadow: 0 0 24px rgba(255, 113, 108, 0.18);
}

@keyframes vh-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.4;
  }
}

/* ---------- Output / terminal block ---------- */

#output {
  background: var(--vh-terminal-bg);
  border: 1px solid var(--vh-terminal-border);
}

/* ---------- Meta line under the verdict ---------- */

.meta {
  margin: 0.85rem 0 0;
  font-size: 0.85rem;
  color: var(--vh-text-dim);
  font-family: "JetBrains Mono", monospace;
  letter-spacing: 0.02em;
}

/* ---------- Footer ---------- */

main > footer {
  margin-top: 1.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--vh-hairline);
}

main > footer .meta {
  font-family: "Inter", sans-serif;
  font-size: 0.85rem;
  line-height: 1.65;
  color: var(--vh-text-muted);
  letter-spacing: 0;
}

main > footer code {
  font-size: 0.85em;
}

/* ---------- Site-wide footer (matches rspress's themeConfig footer) ---------- */

.vh-footer {
  margin-top: 4rem;
  padding: 2rem 1.5rem;
  border-top: 1px solid var(--vh-hairline);
  background: var(--vh-bg);
  font-family: "Inter", sans-serif;
}

.vh-footer__msg {
  margin: 0;
  text-align: center;
  font-size: 0.85rem;
  color: var(--vh-text-dim);
}

.vh-footer__msg a {
  color: var(--vh-accent-teal);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 0.18s ease;
}

.vh-footer__msg a:hover {
  border-bottom-color: var(--vh-accent-teal);
}

/* ---------- Path A — Layer 1 source-substitution branch-fix panel ----------
 *
 * Mounted by `_shared/path_a.ts` into a `<section id="path-a-mount">` that
 * recipes opt into. Shares the same teal/violet token language as the rest
 * of the recipe page chrome.
 */

.vh-path-a {
  margin-top: 2rem;
  padding: 1.5rem;
  border: 1px solid var(--vh-hairline);
  border-radius: 8px;
  background: var(--vh-card-bg);
}

.vh-path-a__eyebrow {
  margin: 0 0 0.5rem;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  color: var(--vh-accent-teal);
}

.vh-path-a__heading {
  margin: 0 0 0.5rem;
  font-family: "Space Grotesk", sans-serif;
  font-size: 1.4rem;
  letter-spacing: -0.01em;
}

.vh-path-a__lead {
  margin: 0 0 1.25rem;
  color: var(--vh-text-muted);
  font-size: 0.95rem;
}

.vh-path-a__field {
  display: block;
  margin-bottom: 1rem;
}

.vh-path-a__field-label {
  display: block;
  margin-bottom: 0.35rem;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--vh-text-muted);
}

.vh-path-a__textarea,
.vh-path-a__input {
  width: 100%;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--vh-hairline);
  border-radius: 6px;
  background: var(--vh-bg-deep);
  color: var(--vh-text);
  font-family: "JetBrains Mono", monospace;
  font-size: 0.85rem;
  line-height: 1.5;
}

.vh-path-a__textarea {
  min-height: 12rem;
  resize: vertical;
}

.vh-path-a__textarea:focus,
.vh-path-a__input:focus {
  outline: 2px solid var(--vh-accent-teal);
  outline-offset: -1px;
}

.vh-path-a__file {
  font-family: "Inter", sans-serif;
  font-size: 0.85rem;
  color: var(--vh-text-muted);
}

.vh-path-a__actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.vh-path-a__btn {
  padding: 0.5rem 1rem;
  border-radius: 6px;
  font-family: "Inter", sans-serif;
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  transition:
    background 0.18s ease,
    color 0.18s ease;
}

.vh-path-a__btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.vh-path-a__btn--primary {
  border: 0;
  background: var(--vh-accent-teal);
  color: #00110e;
}

.vh-path-a__btn--primary:not(:disabled):hover {
  background: var(--vh-accent-teal-bright);
}

.vh-path-a__btn--ghost {
  border: 1px solid var(--vh-hairline);
  background: transparent;
  color: var(--vh-text);
}

.vh-path-a__btn--ghost:not(:disabled):hover {
  background: color-mix(in srgb, var(--vh-text) 8%, transparent);
}

.vh-path-a__status {
  margin: 0.75rem 0 0;
  font-size: 0.85rem;
}

.vh-path-a__status--info {
  color: var(--vh-text-muted);
}

.vh-path-a__status--ok {
  color: var(--vh-reproduced-fg);
}

.vh-path-a__status--error {
  color: var(--vh-unreproduced-fg);
}

.vh-path-a__result-heading {
  margin: 1.5rem 0 0.5rem;
  font-family: "Space Grotesk", sans-serif;
  font-size: 1rem;
  letter-spacing: -0.01em;
  color: var(--vh-text-muted);
}

.vh-path-a__downloads {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.vh-path-a__download-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--vh-hairline);
  border-radius: 6px;
  background: var(--vh-bg-deep);
  flex-wrap: wrap;
}

.vh-path-a__download-side {
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--vh-text-muted);
  min-width: 12rem;
}

.vh-path-a__verdict {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  font-weight: 500;
  padding: 0.15rem 0.5rem;
  border-radius: 4px;
  border: 1px solid;
}

.vh-path-a__verdict--reproduced {
  background: var(--vh-reproduced-bg);
  color: var(--vh-reproduced-fg);
  border-color: var(--vh-reproduced-border);
}

.vh-path-a__verdict--unreproduced {
  background: var(--vh-unreproduced-bg);
  color: var(--vh-unreproduced-fg);
  border-color: var(--vh-unreproduced-border);
}

.vh-path-a__download-link {
  margin-left: auto;
  font-size: 0.85rem;
  color: var(--vh-accent-teal);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 0.18s ease;
}

.vh-path-a__download-link:hover {
  border-bottom-color: var(--vh-accent-teal);
}

.vh-path-a__compare-link {
  margin: 0.75rem 0 0;
}

.vh-path-a__compare-anchor {
  font-size: 0.9rem;
  color: var(--vh-accent-teal);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 0.18s ease;
}

.vh-path-a__compare-anchor:hover {
  border-bottom-color: var(--vh-accent-teal);
}

.vh-path-a__semantics {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--vh-hairline);
}

.vh-path-a__semantics-heading {
  margin: 0 0 0.35rem;
  font-family: "Space Grotesk", sans-serif;
  font-size: 0.95rem;
  color: var(--vh-text-muted);
}

.vh-path-a__semantics-body {
  margin: 0;
  font-size: 0.85rem;
  color: var(--vh-text-muted);
}

/* ============================================================================
 * Phase 8 V″ — desktop two-pane split + right-overlay description drawer.
 * ADR-0035. Activates at >=1024px; below that breakpoint the recipe page
 * keeps its existing single-column stack (no class needed for the
 * fallback — the existing rules above already paint that path).
 *
 * Recipes opt in by structuring their `<main>` as:
 *
 *   <main class="vh-main">
 *     <header class="vh-main__header"> ... </header>
 *     <div    class="vh-main__cols">
 *       <section class="vh-main__col vh-main__col--script"> ... </section>
 *       <section class="vh-main__col vh-main__col--output"> ... </section>
 *     </div>
 *     <footer> ... </footer>
 *   </main>
 *
 * The drawer (`<aside class="vh-drawer">`) and its wash
 * (`<div class="vh-drawer-wash">`) are injected from `_assets/chrome.js`
 * at runtime, populated from a per-recipe `<template id="bug-context">`
 * the recipe HTML inlines in `<head>`.
 * ========================================================================== */

@media (min-width: 1024px) {
  main.vh-main {
    max-width: 1280px;
  }
}

/* ---------- Header strip with right-aligned verdict aside ---------- */

.vh-main__header {
  margin-bottom: 1.25rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--vh-hairline);
}

.vh-main__header .lede {
  margin-top: 0.85rem;
  max-width: 720px;
}

.vh-main__header-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 1rem;
}

@media (min-width: 1024px) {
  .vh-main__header {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 2rem;
    align-items: start;
  }
  .vh-main__header-text {
    grid-column: 1 / 2;
    min-width: 0;
  }
  .vh-main__header-aside {
    grid-column: 2 / 3;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.75rem;
  }
}

/* ---------- Inline-icon ghost / primary chips for the action row ---------- */

.vh-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.4rem 0.75rem;
  border-radius: 9999px;
  border: 1px solid var(--vh-hairline);
  background: transparent;
  color: var(--vh-text-muted);
  font-family: "Inter", sans-serif;
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  cursor: pointer;
  text-decoration: none;
  transition:
    background 0.18s ease,
    color 0.18s ease,
    border-color 0.18s ease;
}

.vh-chip:hover {
  background: color-mix(in srgb, var(--vh-text) 6%, transparent);
  color: var(--vh-text);
  border-color: color-mix(
    in srgb,
    var(--vh-accent-teal) 35%,
    var(--vh-hairline)
  );
  border-bottom: 1px solid
    color-mix(in srgb, var(--vh-accent-teal) 35%, var(--vh-hairline));
}

.vh-chip svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* ---------- Two-column work surface ---------- */

.vh-main__cols {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}

@media (min-width: 1024px) {
  .vh-main__cols {
    grid-template-columns: minmax(0, 6fr) minmax(0, 4fr);
    column-gap: 1.75rem;
    align-items: stretch;
  }
}

.vh-main__col {
  display: flex;
  flex-direction: column;
  margin: 0;
  min-width: 0;
}

.vh-main__col h2 {
  margin-bottom: 0.85rem;
}

/* ---------- Editable script + Run button (the runner) ---------- */

.vh-runner {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.vh-runner__viewport {
  position: relative;
  border: 1px solid var(--vh-hairline);
  border-radius: 8px;
  background: var(--vh-code-bg);
  overflow: hidden;
}

.vh-runner__viewport pre {
  margin: 0;
  border: 0;
  border-radius: 0;
  max-height: 32rem;
  overflow: auto;
}

.vh-runner__textarea {
  display: none;
  width: 100%;
  margin: 0;
  padding: 1.25rem 1.5rem;
  border: 0;
  background: transparent;
  color: #e0e6e3;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.85rem;
  line-height: 1.65;
  resize: vertical;
  min-height: 14rem;
  max-height: 32rem;
}

.vh-runner.is-editing .vh-runner__viewport pre {
  display: none;
}

.vh-runner.is-editing .vh-runner__textarea {
  display: block;
}

.vh-runner__textarea:focus {
  outline: 0;
}

.vh-runner__viewport:focus-within {
  border-color: color-mix(
    in srgb,
    var(--vh-accent-teal) 40%,
    var(--vh-hairline)
  );
  box-shadow: 0 0 0 3px rgba(0, 212, 170, 0.12);
}

.vh-runner__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.vh-runner__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.55rem 1rem;
  border-radius: 6px;
  font-family: "Inter", sans-serif;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition:
    background 0.18s ease,
    color 0.18s ease,
    border-color 0.18s ease;
}

.vh-runner__btn svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.vh-runner__btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.vh-runner__btn--primary {
  /* Match the REPRODUCED pill / kicker colour family — the previous
   * full-saturation accent teal swallowed the dark "Run" label, and a
   * partly-transparent teal still felt out of step with the rest of
   * the chrome. Reusing `--vh-reproduced-bg/fg/border` keeps the
   * button visually grouped with the verdict pills and the kicker
   * tag, and the teal text on the soft teal wash stays legible. */
  border: 1px solid var(--vh-reproduced-border);
  background: var(--vh-reproduced-bg);
  color: var(--vh-reproduced-fg);
}

.vh-runner__btn--primary:not(:disabled):hover {
  background: color-mix(in srgb, var(--vh-accent-teal) 22%, transparent);
  border-color: var(--vh-accent-teal-bright);
}

.vh-runner__btn--ghost {
  border: 1px solid var(--vh-hairline);
  background: transparent;
  color: var(--vh-text);
}

.vh-runner__btn--ghost:not(:disabled):hover {
  background: color-mix(in srgb, var(--vh-text) 8%, transparent);
}

.vh-runner__hint {
  margin-left: auto;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.7rem;
  color: var(--vh-text-dim);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.vh-runner__status {
  margin: 0;
  font-size: 0.85rem;
  color: var(--vh-text-muted);
  min-height: 1.2em;
}

.vh-runner__status--ok {
  color: var(--vh-reproduced-fg);
}

.vh-runner__status--error {
  color: var(--vh-unreproduced-fg);
}

/* ---------- Description drawer (right slide-in overlay) ---------- */

.vh-drawer-wash {
  position: fixed;
  inset: 60px 0 0 0;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
  z-index: 55;
}

.vh-drawer-wash.is-visible {
  opacity: 1;
  pointer-events: auto;
}

.vh-drawer {
  position: fixed;
  top: 60px;
  right: 0;
  bottom: 0;
  width: min(480px, 100vw);
  /* Theme-aware surface — white in light mode, near-black in dark.
   * Earlier draft used --vh-window-bg (always deep-dark, matches the
   * code editor) but that broke the page's light-mode contract. */
  background: var(--vh-bg-deep);
  border-left: 1px solid var(--vh-hairline);
  transform: translateX(100%);
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  z-index: 60;
  overflow-y: auto;
  padding: 1.5rem 1.75rem 2rem;
  color: var(--vh-text);
}

.vh-drawer.is-open {
  transform: translateX(0);
  box-shadow: -24px 0 60px rgba(0, 0, 0, 0.45);
}

.vh-drawer__close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--vh-text-muted);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    background 0.18s ease,
    color 0.18s ease;
}

.vh-drawer__close:hover {
  background: color-mix(in srgb, var(--vh-text) 8%, transparent);
  color: var(--vh-text);
}

.vh-drawer__close svg {
  width: 16px;
  height: 16px;
}

.vh-drawer__eyebrow {
  margin: 0 0 0.5rem;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--vh-accent-teal);
}

.vh-drawer__heading {
  margin: 0 0 1.25rem;
  font-family: "Space Grotesk", sans-serif;
  font-size: 1.4rem;
  letter-spacing: -0.01em;
  color: var(--vh-text);
}

.vh-drawer__body {
  font-size: 0.95rem;
  line-height: 1.65;
  color: var(--vh-text-muted);
}

.vh-drawer__body p {
  margin: 0 0 0.85rem;
}

.vh-drawer__body p:last-child {
  margin-bottom: 0;
}

.vh-drawer__body code {
  font-size: 0.92em;
  background: var(--vh-card-bg);
  border: 1px solid var(--vh-hairline);
  border-radius: 4px;
  padding: 0.05em 0.3em;
}

.vh-drawer__divider {
  margin: 1.25rem 0;
  border: 0;
  border-top: 1px solid var(--vh-hairline);
}

.vh-drawer__meta-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem 1rem;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
}

.vh-drawer__meta-key {
  color: var(--vh-text-dim);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vh-drawer__meta-val {
  color: var(--vh-text);
  word-break: break-word;
}

.vh-drawer__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.65rem 1rem;
  border-radius: 6px;
  background: var(--vh-accent-teal);
  color: #00110e;
  font-family: "Inter", sans-serif;
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
  border-bottom: 0;
  transition: background 0.18s ease;
}

.vh-drawer__cta:hover {
  background: var(--vh-accent-teal-bright);
  border-bottom: 0;
}

.vh-drawer__cta svg {
  width: 14px;
  height: 14px;
}

.vh-drawer__secondary {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.5rem;
  font-size: 0.85rem;
  color: var(--vh-text-muted);
  border-bottom: 1px solid transparent;
  transition: border-color 0.18s ease;
}

.vh-drawer__secondary:hover {
  border-bottom-color: var(--vh-accent-teal);
}

.vh-drawer__secondary svg {
  width: 12px;
  height: 12px;
}

/* Body scroll lock when drawer is open. */
body.vh-drawer-open {
  overflow: hidden;
}

/* ============================================================================
 * Phase 8 V″ — second-pass header & viewport-fit refinements (2026-05-09).
 *
 * The earlier draft (above this block) gave the right structural shape
 * but two complaints surfaced after the maintainer stepped through it:
 *   1. The header took 3 rows (kicker + title + action chips) plus a
 *      verdict-aside column; on a laptop screen this pushed the script
 *      and output below the fold.
 *   2. The reproduction script's `<pre>` inflated to fit its content,
 *      so a long script made the page itself scroll — which the
 *      maintainer wants to avoid (page-scroll degrades the
 *      script-vs-output side-by-side framing).
 *
 * This block:
 *   - Folds the header into a single row at desktop width: kicker (now
 *     inline as a small pill prefix to the h1), title, action chips,
 *     verdict pill — all inline-aligned.
 *   - Hides the meta line on desktop (the runtime version info is in
 *     the drawer's metadata grid; replaying it under the verdict on
 *     the page itself wastes vertical space).
 *   - Constrains both columns to a viewport-relative max-height so the
 *     reproduction script and the output panel each become independent
 *     scroll containers.
 * ========================================================================== */

@media (min-width: 1024px) {
  /* Header: collapse the previous grid (text-column / aside-column)
   * into a single inline row. Override the rule defined above. */
  .vh-main__header {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    column-gap: 1rem;
    row-gap: 0.6rem;
    margin-bottom: 1rem;
    padding-bottom: 0.85rem;
  }

  .vh-main__header-text {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    column-gap: 0.85rem;
    row-gap: 0.5rem;
    flex: 1 1 0;
    min-width: 0;
  }

  .vh-main__header-text .kicker {
    margin: 0;
    flex-shrink: 0;
    font-size: 0.6rem;
    padding: 0.22rem 0.6rem;
  }

  .vh-main__header-text h1 {
    margin: 0;
    font-size: clamp(1rem, 1.7vw, 1.3rem);
    line-height: 1.15;
    flex: 0 1 auto;
    min-width: 0;
  }

  .vh-main__header-actions {
    margin-top: 0;
    gap: 0.4rem;
    flex-shrink: 0;
  }

  .vh-main__header-aside {
    align-items: center;
    flex-direction: row;
    flex-shrink: 0;
    gap: 0.5rem;
  }

  /* Compact verdict pill — fixed-width so LOADING… / RUNNING… /
   * REPRODUCED / UNREPRODUCED all paint at the same horizontal extent
   * (no layout shift on state change, no row-wrap on long labels). */
  .vh-main__header-aside .verdict {
    padding: 0.25rem 0.65rem;
    font-size: 0.72rem;
    min-width: 9rem;
    justify-content: center;
  }

  /* Meta line ("Python 3.13 · SQLite 3.46 · …") moved into the drawer's
   * metadata grid; hide on desktop so it doesn't compete for vertical
   * room next to the verdict pill. */
  .vh-main__header-aside .meta {
    display: none;
  }

  /* ----- Viewport-fit columns (independent scroll containers) -----
   *
   * Approach: turn the body into a flex column so the topnav + main +
   * site-footer chain occupies *exactly* one viewport. Inside `main`,
   * the slim header and the recipe footer are flex-shrink:0; the
   * `.vh-main__cols` row consumes the remaining vertical space. Inside
   * each column, the runner's `<pre>` / `<textarea>` and the output
   * panel scroll internally. Body itself never scrolls. */
  body:has(main.vh-main) {
    height: 100vh;
    margin: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  body:has(main.vh-main) > .vh-topnav,
  body:has(main.vh-main) > .vh-footer {
    flex-shrink: 0;
  }

  body:has(main.vh-main) > main.vh-main {
    flex: 1 1 0;
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  body:has(main.vh-main) > main.vh-main > .vh-main__header,
  body:has(main.vh-main) > main.vh-main > footer {
    flex-shrink: 0;
  }

  body:has(main.vh-main) > main.vh-main > .vh-main__cols {
    flex: 1 1 0;
    min-height: 0;
  }

  /* Phase 8 V″ doesn't run inside path-a, but if the recipe HTML places
   * `<section id="path-a-mount">` between cols and footer (php-12167),
   * keep it shrink-zero so it doesn't squash cols. */
  body:has(main.vh-main) > main.vh-main > section[id="path-a-mount"] {
    flex-shrink: 0;
  }

  .vh-main__col {
    height: 100%;
    min-height: 0;
    overflow: hidden;
  }

  .vh-main__col--script {
    display: flex;
    flex-direction: column;
  }

  .vh-main__col--script > h2 {
    flex-shrink: 0;
  }

  /* Runner & viewport stretch to fill the script column. */
  .vh-runner {
    flex: 1 1 auto;
    min-height: 0;
  }

  .vh-runner__viewport {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
  }

  .vh-runner__viewport pre {
    height: 100%;
    max-height: none;
    min-height: 0;
  }

  .vh-runner__textarea {
    height: 100%;
    max-height: none;
    min-height: 0;
    resize: none;
  }

  .vh-runner__actions,
  .vh-runner__status {
    flex-shrink: 0;
  }

  /* Output column — chrome.js adds the `vh-output-section` class to the
   * same `<section>`. The base `.vh-main__col` rule sets
   * `display: flex` (for the script column's runner stack); restore
   * grid here, with an explicit two-row template so the head row
   * stays at content height and the output/progress row fills the
   * rest via `1fr`. */
  .vh-output-section.vh-main__col--output {
    display: grid;
    grid-template-rows: auto 1fr;
  }

  /* Output `<pre id="output">` — full-fill the grid cell, scroll
   * internally if JSON is long. */
  .vh-output-section.vh-main__col--output .vh-output {
    min-height: 0;
    height: 100%;
    overflow: auto;
  }

  /* Progress overlay — same grid cell as output. Force `height: 100%`
   * so the box fills the column during loading instead of collapsing
   * to its content size. Centred flex layout (existing) keeps the
   * bar + label visually centered inside the tall box. */
  .vh-output-section.vh-main__col--output .vh-progress {
    min-height: 0;
    height: 100%;
  }

  .vh-main__col--output #output {
    margin: 0;
    height: 100%;
    max-height: none;
  }

  /* Tighter `main` chrome — title close to the top, footer line just
   * a hair below the cols. */
  main.vh-main {
    padding-top: 0;
    padding-bottom: 0.3rem;
  }

  .vh-main__header {
    margin-bottom: 0.4rem;
    padding-bottom: 0.25rem;
  }

  /* Output column `.vh-runner__head` (chrome.js wraps its <h2> for
   * height parity with the script column). On desktop, give it a
   * fixed min-height matching the actions row so the script and
   * output content areas line up exactly. */
  .vh-main__col--output > .vh-runner__head {
    min-height: 30px;
  }
  .vh-main__col--script > .vh-runner__head {
    min-height: 30px;
  }

  /* Apache License footer — keep visible but compact (2026-05-09
   * maintainer ask). Drops from ~128px to ~28px. */
  body:has(main.vh-main) > .vh-footer {
    margin-top: 0;
    padding: 0.35rem 1rem;
    font-size: 0.72rem;
    flex-shrink: 0;
  }

  body:has(main.vh-main) > .vh-footer .vh-footer__msg {
    line-height: 1.2;
  }

  /* Action buttons (Run / Reset) move next to the script column's h2
   * so the action row stops eating column height. The runner injects
   * a `.vh-runner__head` flex row that wraps both. */
  .vh-runner__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    margin: 0 0 0.5rem;
    flex-shrink: 0;
  }

  .vh-runner__head h2 {
    margin: 0;
    flex-shrink: 0;
  }

  .vh-runner__head .vh-runner__actions {
    margin: 0;
    gap: 0.4rem;
    flex-wrap: nowrap;
  }

  .vh-runner__head .vh-runner__btn {
    padding: 0.35rem 0.65rem;
    font-size: 0.78rem;
  }

  .vh-runner__head .vh-runner__btn svg {
    width: 12px;
    height: 12px;
  }

  /* Hide hint chip on desktop — the always-editable textarea is
   * obvious enough; the hint just costs a button slot. */
  .vh-runner__head .vh-runner__hint {
    display: none;
  }

  /* Hide the runner status line on desktop V″ so the script column's
   * black viewport fills the same vertical area as the output column's
   * `<pre id="output">`. The verdict pill carries the state literal and
   * `__VIVARIUM_VERDICT_MESSAGE__` carries the full message for any
   * tooling / future overlay that wants it. */
  .vh-runner__status {
    display: none;
  }

  /* Slimmer top nav — Vivarium / Vision / Roadmap / etc. The 60px
   * default is a desktop-shell convention; for the V″ recipe page
   * it eats too much real-estate above the title. */
  body:has(main.vh-main) > .vh-topnav {
    height: 44px;
  }

  /* Drawer top offset must follow the top-nav height. */
  body:has(main.vh-main) ~ .vh-drawer,
  body:has(main.vh-main) > .vh-drawer {
    top: 44px;
  }

  body:has(main.vh-main) ~ .vh-drawer-wash,
  body:has(main.vh-main) > .vh-drawer-wash {
    inset: 44px 0 0 0;
  }
}

/* ---------- Variant head (Phase 8 — two-variant fix-candidate verification) ----------
 * Used by recipes that run the same reproduction script against two
 * package builds (baseline vs fix-candidate). Each variant has a small
 * heading block — title + spec — placed OUTSIDE the `<pre>` so the
 * JSON inside is the only thing in the dark output box. The page-level
 * `#verdict` pill in the top header still mirrors the baseline; the
 * fix-candidate's verdict is read from the JSON's `crashed` field
 * (intentional: a per-variant pill in red would visually flag the
 * fix-candidate's `unreproduced` as a problem when in this context it
 * is the desired outcome). */

.vh-variant-head {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-bottom: 0.6rem;
}

.vh-variant-head--secondary {
  /* Generous breathing room between the two variants on tall / square
   * monitors; tight viewports compress this below (see media block). */
  margin-top: 1.4rem;
}

/* Wide-and-short displays (16:10, 16:9 laptops, e.g. 1440x900) feel
 * cramped vertically — the second variant head's 1.4rem top margin
 * pushes the fix-candidate `<pre>` further down than the column has
 * room for. Match it to the baseline head's natural 0-margin top in
 * those viewports so both heads sit flush against their respective
 * panels. */
@media (min-aspect-ratio: 16/10), (max-height: 900px) {
  .vh-variant-head--secondary {
    margin-top: 0.4rem;
  }
}

.vh-variant-head__title {
  margin: 0;
}

.vh-variant-head__spec {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.78rem;
  opacity: 0.7;
  word-break: break-all;
  background: transparent;
  border: 0;
  padding: 0;
}

/* Each variant's `<pre>` consumes an equal share of the output
 * column's remaining space (= column height minus the two
 * `.vh-variant-head` blocks). Achieved with `flex: 1` once the
 * column is set to `display: flex; flex-direction: column` via
 * the `vh-output-multi` opt-out below — that two-piece arrangement
 * adapts to any viewport without hard-coding a pixel or `vh`
 * value. The `min-height` is intentionally small (8rem ≈ 128px,
 * roughly 5 JSON lines + padding) so very wide-and-short
 * viewports — e.g. 21:9 / 2:1 ultra-wides where the overall
 * column already ends up short — do not have the floor force
 * the fix-candidate panel below the fold. Long output scrolls
 * inside the box instead of pushing the rest of the page down. */
.vh-variant-output {
  margin: 0;
  flex: 1 1 0;
  min-height: 8rem;
  overflow: auto;
  box-sizing: border-box;
}

/* Multi-variant recipes (e.g. astroid-2993): opt out of chrome.js's
 * single-output grid by adding `vh-output-multi` to the output column.
 * The grid that chrome.js applies via `.vh-output-section` assumes one
 * `<pre id="output">` sharing a single cell with the progress bar; a
 * multi-variant recipe has several `<pre>` panels stacked plus extra
 * `.vh-variant-head` rows, which the single-cell grid does not
 * accommodate (`#output`'s `height: 25rem` collapses to its content
 * size when it is treated as a sized grid item). The opt-out clears
 * the explicit `grid-row` / `grid-column` placements chrome.js style
 * applies to `.vh-progress` and `.vh-output`, and forces the column
 * back to plain block flow. The progress bar (still inserted before
 * `#output`) shows above the baseline pre during the load. */
.vh-main__col--output.vh-output-multi {
  display: flex;
  flex-direction: column;
}

.vh-main__col--output.vh-output-multi .vh-output,
.vh-main__col--output.vh-output-multi .vh-progress {
  grid-row: auto;
  grid-column: auto;
}

/* Variant heads (h2 + future spec text) sit at content height so the
 * two `<pre>` panels divide the rest of the column. */
.vh-main__col--output.vh-output-multi .vh-variant-head {
  flex: 0 0 auto;
}

/* The baseline variant wraps its `<pre>` in `.vh-variant-stage`. The
 * stage is a flex item that takes its 1fr share of the column AND
 * acts as a positioning context so chrome.js's progress overlay
 * (which the chrome injects before #output) stacks ON TOP of #output
 * instead of taking its own row in the column. That keeps the column
 * height constant across the load lifecycle (no CLS while the overlay
 * appears / fades out) and lets the overlay's "loading…" UI cover
 * the full baseline panel during install. Children are absolute-
 * positioned so the stage's own flex sizing stays clean (a `display:
 * grid` stage interacted poorly with `flex: 1 1 0`, biasing the
 * column's split between baseline and fix-candidate). */
.vh-main__col--output.vh-output-multi .vh-variant-stage {
  position: relative;
  flex: 1 1 0;
  min-height: 8rem;
}

.vh-main__col--output.vh-output-multi .vh-variant-stage > .vh-progress,
.vh-main__col--output.vh-output-multi .vh-variant-stage > .vh-output,
.vh-main__col--output.vh-output-multi .vh-variant-stage > #output {
  position: absolute;
  inset: 0;
  grid-row: auto;
  grid-column: auto;
  min-height: 0;
  height: auto;
  max-height: none;
}

.vh-main__col--output.vh-output-multi .vh-variant-stage > .vh-progress {
  z-index: 2;
}

/* Override the wide-viewport `@media (min-width: 1024px)` rule that
 * stretches `.vh-output` (and `#output` directly) to `height: 100%`,
 * and the desktop-flex layout `.vh-runner__viewport pre { max-height
 * 32rem }` constraint that would otherwise cap our share. Specificity
 * here (3 classes / 2 classes+1 id) is enough to win over the
 * @media rule. */
.vh-main__col--output.vh-output-multi #output-fix {
  height: auto;
  flex: 1 1 0;
  min-height: 8rem;
  max-height: none;
}

#output-fix {
  background: var(--vh-terminal-bg);
  border: 1px solid var(--vh-terminal-border);
}

