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

/* The ohi.dev fonts, licensed under the OFL (fonts/OFL.txt).
   The ascent and descent overrides centre capitals in the line box, so text sits in the middle of
   buttons and fields. With the font's own 80/20 split it sits about a pixel and a half high. */
@font-face { font-family: 'LT Superior'; src: url('fonts/LTSuperior-Regular.woff2') format('woff2'); font-weight: 400; font-display: swap; ascent-override: 85%; descent-override: 15%; }
@font-face { font-family: 'LT Superior'; src: url('fonts/LTSuperior-Medium.woff2') format('woff2'); font-weight: 500; font-display: swap; ascent-override: 85%; descent-override: 15%; }
@font-face { font-family: 'LT Superior'; src: url('fonts/LTSuperior-SemiBold.woff2') format('woff2'); font-weight: 600; font-display: swap; ascent-override: 85%; descent-override: 15%; }
@font-face { font-family: 'LT Superior'; src: url('fonts/LTSuperior-Bold.woff2') format('woff2'); font-weight: 700; font-display: swap; ascent-override: 85%; descent-override: 15%; }
@font-face { font-family: 'Red Hat Mono'; src: url('fonts/RedHatMono-400.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Red Hat Mono'; src: url('fonts/RedHatMono-500.woff2') format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Red Hat Mono'; src: url('fonts/RedHatMono-600.woff2') format('woff2'); font-weight: 600; font-display: swap; }

/* Keep showing the old page until the next one is ready, so moving between pages doesn't flash.
   Pages switch at once, with no animation. */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation: none; }

/* Colors follow the ohi.dev tokens */
:root {
  color-scheme: dark;

  /* ohi.dev's own surfaces are only a few percent apart, which is fine for its flat pages but not
     for panels on a page, so the surfaces here are a few steps lighter */
  --bg: #0a0b0e;
  --surface: #15181e;
  --surface-2: #1e222a;
  --surface-3: #0e1014;

  --text: #e5e4e4;
  --text-secondary: #a6acb8;
  --text-muted: #959cab;
  --text-on-accent: #13203a;

  /* As on ohi.dev, controls get a stronger border than containers and dividers, but a softer one
     than ohi.dev's, since the playground has many controls next to each other */
  --border: #2c323e;
  --border-subtle: #22262f;
  --border-strong: #4c5363;

  --accent: #bfcdea;
  --accent-strong: #8ea8e8;
  --accent-soft: rgba(191, 205, 234, 0.14);
  --accent-border: rgba(191, 205, 234, 0.4);

  --danger: #ec8891;
  --danger-soft: rgba(224, 108, 117, 0.14);
  --danger-border: rgba(224, 108, 117, 0.4);
  --success: #98c379;
  --success-soft: rgba(152, 195, 121, 0.14);
  --purple: #c678dd;
  --cyan: #56b6c2;
  --orange: #d19a66;
  --blue: #61afef;

  --brand: #9fc131;
  --brand-strong: #8aab2a;
  --brand-ink: #13203a;

  --tag-bg: #172036;
  --tag-text: #bfcdea;

  /* The waving hand in the header */
  --hand-fill: #9fc131;
  --hand-line: #13203a;
  --hand-wave: #8ea8e8;

  --radius: 8px;
  --radius-sm: 5px;
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3);
  --code-font: 'Red Hat Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, ui-monospace, monospace;

  font-family: 'LT Superior', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
}

:root[data-theme='light'] {
  color-scheme: light;

  --bg: #f5f7fb;
  --surface: #ffffff;
  --surface-2: #eff2fa;
  --surface-3: #f5f7fb;

  --text: #13203a;
  --text-secondary: #545e73;
  --text-muted: #666e83;
  --text-on-accent: #ffffff;

  --border: #e1e5ee;
  --border-subtle: #eceff5;
  --border-strong: #bcc2cf;

  --accent: #3154a2;
  --accent-strong: #24407f;
  --accent-soft: rgba(49, 84, 162, 0.08);
  --accent-border: rgba(49, 84, 162, 0.30);

  --danger: #c0303a;
  --danger-soft: rgba(228, 86, 73, 0.12);
  --danger-border: rgba(192, 48, 58, 0.40);
  --success: #2f6f22;
  --success-soft: rgba(80, 161, 79, 0.16);
  --purple: #8d3aa8;
  --cyan: #0e7490;
  --orange: #a15319;
  --blue: #3367d6;

  --brand: #9fc131;
  --brand-strong: #8aab2a;
  --brand-ink: #13203a;

  --tag-bg: #eff2fa;
  --tag-text: #3154a2;

  --hand-fill: #9fc131;
  --hand-line: #13203a;
  --hand-wave: #3154a2;

  --shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.08);
}

html, body { height: 100%; }
body { background: var(--bg); }

.ic { width: 1em; height: 1em; display: inline-block; vertical-align: middle; flex-shrink: 0; }
/* `.ic { display }` beats the UA `[hidden] { display:none }`, so toggled icons
   (e.g. the theme moon/sun) would both render – hide them explicitly. */
.ic[hidden] { display: none; }

/* ── Layout ── */

.app-shell { display: flex; flex-direction: column; height: 100vh; }

.app-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 24px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.brand { display: flex; align-items: center; gap: 12px; }
.brand-home { display: flex; flex-shrink: 0; }
.brand-mark { width: 30px; height: 30px; }
.brand-mark .hand-fill { fill: var(--hand-fill); }
.brand-mark .hand-line { fill: var(--hand-line); }
.brand-mark .hand-wave { fill: var(--hand-wave); }
.brand-text { display: flex; flex-direction: column; line-height: 1.2; }
.brand-title { align-self: flex-start; font-size: 16px; font-weight: 500; letter-spacing: -0.01em; color: var(--text); text-decoration: none; }
.brand-title b { font-weight: 700; }
/* "LinkML" in the accent color, as on the landing page and in the docs */
.brand-title span { color: var(--accent); }
.brand-sub { font-size: 12.5px; color: var(--text-secondary); }

.header-actions { display: flex; align-items: center; gap: 10px; }

/* GitHub repo button (icon + name + star/fork counts), mkdocs-material style */
.repo-link {
  display: inline-flex;
  align-items: center;
  /* Keeps its natural width whatever else is in the header: squeezing it only ever cuts into the
     repo name and the counts, which are the whole point of it. */
  flex-shrink: 0;
  gap: 9px;
  padding: 5px 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text-secondary);
  text-decoration: none;
  line-height: 1.15;
}
.repo-link:hover { color: var(--text); border-color: var(--accent-border); }
.repo-icon { width: 21px; height: 21px; flex-shrink: 0; }
.repo-meta { display: flex; flex-direction: column; gap: 1px; }
.repo-name { font-size: 12.5px; font-weight: 600; color: var(--text); }
.repo-stats { display: flex; gap: 11px; font-size: 11px; color: var(--text-muted); }
.repo-stat { display: inline-flex; align-items: center; gap: 3px; }
.repo-stat-ic { width: 12px; height: 12px; fill: currentColor; }

.icon-btn {
  appearance: none;
  border: none;
  background: none;
  cursor: pointer;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
}
.icon-btn:hover { background: var(--accent-soft); color: var(--text); }

/* Bordered square icon buttons (help + theme), matching the repo button height */
.theme-toggle,
.help-toggle {
  width: 40px;
  height: 40px;
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--text);
}
.theme-toggle .ic,
.help-toggle .ic { width: 20px; height: 20px; }
.theme-toggle:hover,
.help-toggle:hover { background: var(--accent-soft); border-color: var(--accent-border); color: var(--text); }

/* ── Dialogs ── */
.modal {
  /* `* { margin: 0 }` in the reset kills the UA margin:auto that centres a modal
     dialog, pinning it top-left – restore it. */
  margin: auto;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
}
.modal::backdrop { background: rgba(0, 0, 0, 0.5); }
.modal-head {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  background: var(--surface);
  border-bottom: 1px solid var(--border-subtle);
}
.modal-head h2 { font-size: 16px; font-weight: 700; }
.modal-close { width: 32px; height: 32px; color: var(--text-secondary); }
.modal-close:hover { background: var(--accent-soft); color: var(--text); }

/* ── Load dialog ── */
/* The URL section's input, hint, error and label styles are shared by the other dialogs. */
.load-dialog {
  width: min(600px, calc(100vw - 32px));
  max-height: calc(100vh - 32px);
}
.load-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px 20px;
}
.load-section + .load-section { border-top: 1px solid var(--border-subtle); }
.load-section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.ohi-link { display: inline-flex; color: var(--text); border-radius: var(--radius-sm); }
.ohi-link:hover { opacity: 0.8; }
.ohi-logo { display: block; height: 24px; width: auto; }
/* Same reason as `.ic[hidden]`: `.dir-toggle` beats the UA `[hidden] { display: none }`. */
.load-section-head .dir-toggle[hidden] { display: none; }
.load-row { display: flex; align-items: center; gap: 12px; }
.load-row .url-input { flex: 1; min-width: 0; }
.load-row-text { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.load-row .btn { flex-shrink: 0; }
.ohi-results {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: min(40vh, 320px);
  overflow-y: auto;
}
.ohi-results:empty { display: none; }
.ohi-hit {
  appearance: none;
  display: flex;
  flex-direction: column;
  gap: 1px;
  width: 100%;
  /* The right side is kept clear for the View on ohi.dev link. */
  padding: 7px 40px 7px 10px;
  text-align: left;
  font: inherit;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.ohi-hit:hover, .ohi-hit:focus-visible { background: var(--accent-soft); border-color: var(--accent-border); }
.ohi-item { position: relative; }
/* Pinned to the card's top-right corner, over the room the card leaves for it. It stays at full
   strength on a greyed-out card: that ontology can still be looked at on ohi.dev. */
.ohi-view {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 28px;
  height: 28px;
}
.ohi-view:focus-visible { background: var(--accent-soft); color: var(--text); }
.ohi-view .ic { width: 15px; height: 15px; }
.ohi-hit-title { font-size: 13px; font-weight: 600; }
.ohi-hit-meta { font-family: var(--code-font); font-size: 11.5px; color: var(--text-muted); }
.ohi-hit-desc {
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-secondary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ohi-hit-note { font-size: 12px; font-weight: 600; color: var(--text-secondary); }
.ohi-hit--missing { opacity: 0.5; cursor: not-allowed; }
.ohi-hit--missing:hover { background: var(--surface-2); border-color: var(--border-subtle); }
/* While one ontology downloads the rest wait, and the one loading stays lit. */
.ohi-results--busy .ohi-hit { cursor: progress; }
.ohi-hit--loading { border-color: var(--brand); }
.url-body { display: flex; flex-direction: column; gap: 8px; padding: 16px 20px; }
.url-label { font-size: 13px; font-weight: 600; }
.url-input {
  font: inherit;
  font-size: 13px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--text);
  outline: none;
}
.url-input:focus { border-color: var(--accent); }
.url-hint { font-size: 12px; line-height: 1.45; color: var(--text-muted); }
.url-error {
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--danger);
  background: var(--danger-soft);
  border: 1px solid var(--danger-border);
  border-radius: var(--radius-sm);
  padding: 7px 9px;
}
.url-foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 20px 16px;
  border-top: 1px solid var(--border-subtle);
}

/* ── Share dialog ── */
/* Shares the URL dialog's body, input, hint, error and footer styles. */
.share-dialog { width: min(560px, calc(100vw - 32px)); }
.share-body { display: flex; flex-direction: column; gap: 8px; }
/* Same reason as `.ic[hidden]`: the rule above beats the UA `[hidden] { display: none }`. */
.share-body[hidden] { display: none; }
.share-dialog .url-input { font-family: var(--code-font); font-size: 12px; }
.share-size { font-size: 12px; color: var(--text-muted); }
.share-size--warn { color: var(--orange); }
.url-hint code { font-family: var(--code-font); font-size: 12px; }
/* Same as the links in the help dialog. */
.url-hint a { color: var(--accent-strong); text-decoration: none; }
.url-hint a:hover { text-decoration: underline; }

/* ── Help / FAQ dialog ── */
.faq-dialog {
  width: min(640px, calc(100vw - 32px));
  max-height: min(80vh, 720px);
}
.faq-body { padding: 8px 20px 20px; overflow-y: auto; }
.faq-body section { padding: 12px 0; border-bottom: 1px solid var(--border-subtle); }
.faq-body h3 { font-size: 13.5px; font-weight: 600; color: var(--text); margin-bottom: 5px; }
.faq-body p { font-size: 13px; color: var(--text-secondary); margin-bottom: 6px; }
.faq-body ul { margin: 4px 0 0; padding-left: 18px; }
.faq-body li { font-size: 13px; color: var(--text-secondary); margin: 3px 0; }
.faq-body a { color: var(--accent-strong); text-decoration: none; }
.faq-body a:hover { text-decoration: underline; }
.faq-body code { font-family: var(--code-font); font-size: 12px; }
.faq-foot { padding-top: 12px; font-size: 12px; color: var(--text-muted); }

/* Label/value pairs, laid out as two columns. `auto` on the first keeps the values aligned with
   each other while the labels stay only as wide as the longest one. */
.build-info {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 3px 14px;
  margin-top: 8px;
  font-size: 13px;
}
.build-info dt { color: var(--text-muted); }
.build-info dd {
  font-family: var(--code-font);
  font-size: 12px;
  color: var(--text);
  /* Version strings have no spaces to break at, and the runtime one can be long. */
  overflow-wrap: anywhere;
}

.app-footer {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 16px;
  padding: 10px 24px;
  background: var(--surface);
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-muted);
}
.footer-brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--text-muted);
  text-decoration: none;
}
.footer-logo {
  display: block;
  height: 15px;
  width: 111px; /* preserves the 2322.4 x 314.3 aspect ratio */
  background-color: var(--text);
  -webkit-mask: url("neverblink-logo.svg") left center / contain no-repeat;
  mask: url("neverblink-logo.svg") left center / contain no-repeat;
}
.footer-links { display: flex; align-items: center; flex-wrap: wrap; }
.footer-links a,
.footer-links button {
  appearance: none;
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
  padding: 0 12px;
  color: var(--text-muted);
  text-decoration: none;
}
.footer-links :first-child { padding-left: 0; }
.footer-links :not(:first-child) { border-left: 1px solid var(--border-subtle); }
.footer-links a:hover,
.footer-links button:hover { color: var(--text); }

.workspace {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 14px 24px;
  gap: 14px;
}

/* ── Toolbar ── */

.toolbar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 8px 10px;
}

/* Segmented control, set apart from the tabs next to it by its border and its own background. */
.dir-toggle {
  display: flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
/* Deliberately tight: the toolbar already has eleven target tabs to fit, and a wider control here
   costs a whole extra row of toolbar on a 1280px window. */
.dir-btn {
  appearance: none;
  border: none;
  background: none;
  cursor: pointer;
  /* With the track's padding and border, as tall as the target picker and Generate beside it */
  padding: 5px 9px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
  border-radius: calc(var(--radius-sm) - 2px);
  white-space: nowrap;
  transition: background 0.1s, color 0.1s;
}
.dir-btn:hover { color: var(--text); background: var(--accent-soft); }
/* A raised-pill treatment does not work here: in the dark theme --surface is darker than the
   --surface-2 track, so the selected half would recede instead of standing out. */
.dir-btn--active,
.dir-btn--active:hover { color: var(--text-on-accent); background: var(--accent); font-weight: 600; }

/* ── Target combobox ── */

.combo { position: relative; }
.combo-button {
  display: flex;
  align-items: center;
  width: 150px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  /* Room on the right for the caret, which sits over the button. */
  padding: 7px 30px 7px 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  outline: none;
  cursor: pointer;
}
.combo-button:focus-visible,
.combo-button[aria-expanded="true"] { border-color: var(--accent); }
.combo-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.combo-caret {
  position: absolute;
  right: 9px;
  top: 50%;
  translate: 0 -50%;
  width: 15px;
  height: 15px;
  color: var(--text-muted);
  pointer-events: none;
}

.combo-popup {
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--shadow);
}
.combo-popup[hidden] { display: none; }

.combo-search {
  display: block;
  width: 100%;
  margin-bottom: 4px;
  font: inherit;
  font-size: 13px;
  padding: 6px 10px;
  border: 1px solid var(--border-strong);
  border-radius: calc(var(--radius-sm) - 2px);
  background: var(--surface-2);
  color: var(--text);
  outline: none;
}
.combo-search:focus { border-color: var(--accent); }
.combo-search::placeholder { color: var(--text-muted); }

.combo-list {
  max-height: 320px;
  overflow-y: auto;
  list-style: none;
}

.combo-option {
  padding: 7px 11px;
  font-size: 13px;
  color: var(--text-secondary);
  border-radius: calc(var(--radius-sm) - 2px);
  white-space: nowrap;
  cursor: pointer;
}
/* Two marks that stack: which one is selected, and which one the keyboard is on. */
.combo-option--selected { color: var(--text); font-weight: 600; }
.combo-option--active { background: var(--accent-soft); color: var(--text); }

.combo-empty { padding: 7px 11px; font-size: 13px; color: var(--text-muted); }

.toolbar-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

/* `contents` rather than `flex`: the options are a group in the markup only, because app.ts
   refills them wholesale. As a flex item the group would wrap internally and leave the slack on
   its own last line, where the Auto toggle beside it cannot reach - so Auto dropped to a line of
   its own with half the row empty. Dissolving the box puts every control in one wrap context. */
.options-row { display: contents; }
.opt-field { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary); white-space: nowrap; }
/* Same reason as `.ic[hidden]`: the `display: flex` above beats the UA `[hidden]` rule. */
.opt-field[hidden] { display: none; }
/* Divider after each option (incl. the last, to fence it off from the Auto
   toggle) so adjacent controls don't read as one blur. */
.opt-field::after {
  content: "";
  align-self: center;
  width: 1px;
  height: 22px;
  margin-left: 6px;
  background: var(--border);
}
.opt-field input[type="checkbox"] { accent-color: var(--accent); cursor: pointer; }
.opt-field input[type="text"],
.opt-field input[type="number"],
.opt-field select {
  font: inherit;
  font-size: 13px;
  padding: 5px 8px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--text);
  outline: none;
}
.opt-field input[type="text"] { width: 168px; }
.opt-field input[type="number"] { width: 56px; }
.opt-field input:focus, .opt-field select:focus { border-color: var(--accent); }
.opt-field label { cursor: pointer; }

.auto-toggle {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 13px;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0 2px;
}
.auto-toggle input { accent-color: var(--brand); cursor: pointer; }

/* ── Buttons ── */

.btn {
  appearance: none;
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 8px 16px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: opacity 0.1s, background 0.1s;
}
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* Same reason as `.ic[hidden]`: `display: inline-flex` above beats the UA `[hidden]` rule. */
.btn[hidden] { display: none; }
.btn-sm { font-size: 12px; padding: 5px 11px; }

.btn-primary { background: var(--brand); color: var(--brand-ink); }
.btn-primary:hover:not(:disabled) { background: var(--brand-strong); }

/* The outline is a shadow, so it adds no size to the button */
.btn-secondary { background: var(--surface-2); color: var(--text); box-shadow: inset 0 0 0 1px var(--border-strong); }
.btn-secondary:hover:not(:disabled) { background: var(--tag-bg); }

/* Momentary confirmation (see flashDone). Takes over the background too, so it stays legible on
   btn-primary, whose brand green is close enough to --success to swallow it. Doubled class and an
   explicit :hover to outweigh the variant hover rules above - the cursor is still on the button. */
.btn.btn--done,
.btn.btn--done:hover { background: var(--success-soft); color: var(--success); }

/* ── Split panels ── */

.split {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.panel {
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.panel-head {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border-subtle);
}

.panel-title { font-size: 13px; font-weight: 600; color: var(--text); display: flex; align-items: baseline; gap: 8px; }
.panel-title-sub { font-size: 12px; font-weight: 400; color: var(--text-muted); }
/* Wraps because the input panel's four buttons do not fit a narrow window on one line. */
.panel-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* Sized to sit level with the Copy button next to it. Same reason as `.btn[hidden]`: the
   `display: grid` on .icon-btn beats the UA [hidden] rule. */
.reverse-btn { width: 26px; height: 26px; }
.reverse-btn[hidden] { display: none; }
.reverse-btn .ic { width: 15px; height: 15px; }

.status-pill {
  font-size: 11px;
  font-weight: 600;
  padding: 2px 9px;
  border-radius: 100px;
  background: var(--success-soft);
  color: var(--success);
  font-variant-numeric: tabular-nums;
}
.status-pill--error { background: var(--danger-soft); color: var(--danger); }
.status-pill--busy { background: var(--accent-soft); color: var(--accent); animation: pill-pulse 1.1s ease-in-out infinite; }

@keyframes pill-pulse {
  50% { opacity: 0.45; }
}

/* While a generation is in flight the previous output stays put and is only dimmed, so switching
   tabs on a large schema never blanks the panel. */
.panel-output .editor,
.panel-output .report,
.panel-output .diagram,
.panel-output .md-page,
.panel-output .file-tabs { transition: opacity 120ms ease; }
.panel--busy .editor,
.panel--busy .report,
.panel--busy .diagram,
.panel--busy .md-page,
.panel--busy .file-tabs { opacity: 0.45; }

@media (prefers-reduced-motion: reduce) {
  .status-pill--busy { animation: none; }
  .panel-output .editor,
  .panel-output .report,
  .panel-output .diagram,
  .panel-output .md-page,
  .panel-output .file-tabs { transition: none; }
}

/* ── CodeMirror editor hosts (input + read-only output) ── */
.editor {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  background: var(--surface-3);
}
.editor .cm-editor { height: 100%; }
.editor .cm-scroller { overflow: auto; }
.cm-output--error .cm-content { color: var(--danger); }

.file-tabs {
  flex-shrink: 0;
  display: flex;
  gap: 2px;
  overflow-x: auto;
  /* `overflow-x: auto` on its own makes the browser compute `overflow-y: auto` too, and the tabs'
     -1px bottom margin (see .file-tab) leaves exactly 1px hanging below the content box - enough for
     a phantom vertical scrollbar at some zoom levels. The 1px of bottom padding takes that overhang
     back inside the padding box, so there is nothing left to scroll; `overflow-y: hidden` then keeps
     it that way without clipping the active tab's underline. The top padding gives up the same 1px,
     so the bar's height does not change. */
  overflow-y: hidden;
  padding: 5px 8px 1px;
  border-bottom: 1px solid var(--border-subtle);
}
/* `hidden` alone loses to `.file-tabs { display: flex }`; hide it explicitly so
   single-file / non-multifile output shows no empty tab bar. */
.file-tabs[hidden] { display: none; }
.file-tab {
  appearance: none;
  border: none;
  background: none;
  cursor: pointer;
  padding: 6px 12px;
  font-size: 12px;
  font-family: var(--code-font);
  color: var(--text-secondary);
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.file-tab:hover { color: var(--text); }
.file-tab--active { color: var(--accent); border-bottom-color: var(--accent); }

/* ── Rendered Mermaid diagram ── */
.diagram {
  flex: 1;
  min-height: 0;
  /* Scrolls while showing a notice; the interactive state below takes over for a real diagram. */
  overflow: auto;
  padding: 12px;
  background: var(--surface-3);
}
.diagram[hidden] { display: none; }
.diagram svg { display: block; max-width: none; height: auto; }

/* Set once a diagram is actually on screen: panning replaces scrolling, so the canvas can be
   dragged past the edges and zoomed without the scrollbars fighting the transform. */
.diagram--interactive {
  overflow: hidden;
  padding: 0;
  cursor: grab;
  /* Let the browser hand us the touch gestures instead of scrolling the panel. */
  touch-action: none;
}
.diagram--interactive.diagram--panning { cursor: grabbing; }
.diagram-canvas {
  transform-origin: 0 0;
  will-change: transform;
}

/* Shown instead of a diagram that is too big to draw, and for render failures. */
.diagram-notice {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  max-width: 52ch;
  margin: 8px auto;
  padding: 14px 16px;
  border: 1px solid var(--border-subtle);
  border-radius: 8px;
  background: var(--surface-2);
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-secondary);
}
.diagram-notice strong { color: var(--text); font-weight: 600; }
.diagram-notice--error { border-color: var(--danger); }
.diagram-notice--error strong { color: var(--danger); }

/* ── Rendered Markdown page (documentation target) ──
   Close to how GitHub shows a Markdown file, in the playground's own colours. */
.md-page {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 16px 24px 32px;
  background: var(--surface-3);
  color: var(--text);
  font-size: 14px;
  line-height: 1.6;
  overflow-wrap: break-word;
}
.md-page[hidden] { display: none; }
.md-page > :first-child { margin-top: 0; }
.md-page h1,
.md-page h2,
.md-page h3,
.md-page h4 {
  margin: 24px 0 12px;
  font-weight: 600;
  line-height: 1.25;
  /* Anchor links scroll a heading to the top of the panel; leave it a little room. */
  scroll-margin-top: 8px;
}
.md-page h1,
.md-page h2 { padding-bottom: 6px; border-bottom: 1px solid var(--border-subtle); }
.md-page h1 { font-size: 1.8em; }
.md-page h2 { font-size: 1.45em; }
.md-page h3 { font-size: 1.2em; }
.md-page p,
.md-page ul,
.md-page ol,
.md-page table,
.md-page pre,
.md-page blockquote { margin: 0 0 14px; }
.md-page ul,
.md-page ol { padding-left: 2em; }
.md-page li + li { margin-top: 2px; }
.md-page a { color: var(--blue); text-decoration: none; }
.md-page a:hover { text-decoration: underline; }
.md-page code {
  padding: 0.15em 0.4em;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-family: var(--code-font);
  font-size: 85%;
}
.md-page pre {
  padding: 12px 14px;
  overflow: auto;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.md-page pre code { padding: 0; background: none; font-size: 12px; }
.md-page blockquote {
  padding: 0 1em;
  border-left: 3px solid var(--border);
  color: var(--text-secondary);
}
.md-page table {
  display: block;
  max-width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
}
.md-page th,
.md-page td { padding: 5px 12px; border: 1px solid var(--border); }
.md-page th { font-weight: 600; text-align: left; background: var(--surface-2); }
.md-page tr:nth-child(2n) td { background: var(--surface); }

@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; }
  .toolbar-right { margin-left: 0; }
}

@media (max-width: 640px) {
  /* Tighter everywhere; reclaim the horizontal margins for content width. */
  .app-header { padding: 8px 12px; }
  .app-footer { padding: 8px 12px; }
  .workspace { padding: 8px 0 10px; gap: 8px; }
  .toolbar { padding: 6px 8px; gap: 8px; border-radius: 0; }
  .toolbar-right { gap: 8px; }
  .split { gap: 8px; }
  .panel { border-radius: 0; }

  .brand-home { display: none; }
  .brand { gap: 8px; }
  .header-actions { gap: 8px; }
  /* The repo name and its star/fork counts say the same thing in less width. */
  .repo-link { padding: 4px 9px; }
  .repo-icon { display: none; }

  /* With Auto on, Generate only repeats what has already happened, so the row is better spent on
     the options. It comes back the moment Auto is switched off, which is when it is the only way
     to run anything. */
  .toolbar-right:has(#autoGenerate:checked) #generateBtn { display: none; }

  /* Clear is the one of the three that the editor itself can do, so it goes first when the row
     runs out of width. Dropping the format sub-label buys the rest: it is what pushes the
     remaining buttons onto a second line, and the direction toggle above says the same thing. */
  #clearInput { display: none; }
  .panel-title-sub { display: none; }

  /* Share keeps its icon but loses its label; the load button has no icon to fall back
     on. The button carries an aria-label, so what it does is still announced. */
  #share .btn-label { display: none; }
  /* `.ic` is 1em, so with the label gone the button would come out shorter than the ones beside
     it. 1.5em is the line box the text had, which puts it back at the same height. */
  #share .ic { width: 1.5em; height: 1.5em; }
  #share { padding: 5px 8px; }

  /* Footer: keep just the "Built by NeverBlink" mark; the link row is too much. */
  .footer-links { display: none; }
  .app-footer { justify-content: flex-start; }
}

/* ── Validation report ─────────────────────────────────────────────────────── */

.report {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.report[hidden] { display: none; }

.report-summary {
  flex-shrink: 0;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}
.report-summary--ok {
  color: var(--success);
  background: var(--success-soft);
  border-color: var(--success);
}
.report-summary--fatal,
.report-summary--error {
  color: var(--danger);
  background: var(--danger-soft);
  border-color: var(--danger-border);
}
.report-summary--warning {
  color: var(--orange);
  background: rgba(209, 154, 102, 0.14);
  border-color: rgba(209, 154, 102, 0.4);
}

.report-item {
  border: 1px solid var(--border-subtle);
  border-left: 3px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.report-item--fatal { border-left-color: var(--danger); }
.report-item--error { border-left-color: var(--danger); }
.report-item--warning { border-left-color: var(--orange); }

.report-item-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.report-badge {
  flex-shrink: 0;
  font-family: var(--code-font);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 6px;
  border-radius: 3px;
  background: var(--tag-bg);
  color: var(--tag-text);
}
.report-item--fatal .report-badge,
.report-item--error .report-badge { background: var(--danger-soft); color: var(--danger); }
.report-item--warning .report-badge { background: rgba(209, 154, 102, 0.16); color: var(--orange); }

.report-message {
  font-size: 13px;
  line-height: 1.45;
  color: var(--text);
  overflow-wrap: anywhere;
}

.report-snippet {
  margin: 0;
  font-family: var(--code-font);
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-secondary);
  white-space: pre;
  overflow-x: auto;
}

.report-location {
  font-family: var(--code-font);
  font-size: 11px;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

.report-details {
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}
