/* Three colour schemes, one attribute.

   The workspace shows a tenant's own page in an iframe next to ChatCMS's own
   panels, and against a bright site the bright chrome stops reading as
   chrome: the reader cannot tell where their page ends and our UI begins.
   So the chrome has three grounds -- bright, a mid graphite that sits
   between any page and either extreme, and an Adobe-ish dark -- selected by
   `data-theme` on <html>, which the server renders from the theme cookie so
   the first paint is already right (a class flipped by JS on DOMContentLoaded
   is a flash of the wrong theme on every single navigation).

   Every colour the chrome paints is a token below, and all three blocks
   declare the same set: a token defined in `:root` alone would silently keep
   its bright value on a dark ground, which is invisible in review and
   obvious to a user. `tests/test_web_theming.py` enforces both that
   completeness and a contrast floor for every text-on-ground pair, because
   nobody can eyeball 50 colours x 3 themes.

   Two things deliberately do NOT follow the theme, and the line between them
   is "chrome or content": the little pictures of the starter sites
   (.template-preview*) and the iframe's own backdrop stand in for somebody
   else's page, and re-tinting those would be lying about what the site looks
   like. The preview overlay (static/overlay/overlay.css) is a third: it
   lives inside the tenant's document and is already built to stay legible
   over anything. */
:root {
  color-scheme: light;
  /* ground and ink */
  --bg: #f4f7fb;
  --surface: #ffffff;
  --surface-muted: #f8fafc;
  --surface-sunken: #eef2f8;
  --field: #ffffff;
  --ink: #172033;
  --label: #424d63;
  --muted: #657087;
  --line: #dfe5ef;
  --line-strong: #cbd4e2;
  --chip: #edf0f5;
  --chip-ink: #59647a;
  --mark: #fde9a9;
  --mark-ink: #172033;
  /* brand */
  --brand: #3156d3;
  --brand-strong: #2545b4;
  --brand-soft: #e9edff;
  --brand-line: #cad5f6;
  --brand-ink: #344574;
  --on-brand: #ffffff;
  --wash-user: #e7ecff;
  --wash-admin: #eee8ff;
  --focus-ring: rgba(49, 86, 211, .28);
  --focus-soft: rgba(49, 86, 211, .12);
  /* status */
  --success: #147a55;
  --success-soft: #e7f6ef;
  --success-line: #a9e7c1;
  --warning: #a15c00;
  --warning-soft: #fff3d9;
  --warning-line: #efd89c;
  --danger: #c0343e;
  --danger-soft: #fdebed;
  --danger-line: #e7aeb3;
  --danger-wash: #fdf3f4;
  /* the rail: sidebar, brand block, account */
  --rail: #10182b;
  --rail-ink: #eef2ff;
  --rail-muted: #9ca8c4;
  --rail-line: #38445e;
  --rail-hover: rgba(255, 255, 255, .09);
  --rail-accent: #7890ff;
  --rail-avatar: #293755;
  /* output that is content, not chrome */
  --terminal: #111827;
  --terminal-ink: #d5deef;
  --terminal-muted: #7e8aa2;
  --tooltip: #1b2438;
  --tooltip-ink: #eef2ff;
  /* depth */
  --shadow: 0 12px 32px rgba(31, 42, 68, .08);
  --shadow-md: 0 16px 40px rgba(23, 32, 51, .18);
  --shadow-lg: 0 28px 80px rgba(15, 23, 42, .28);
  --scrim: rgba(15, 23, 42, .55);
  --curtain: rgba(247, 249, 252, .93);
  --bar: rgba(255, 255, 255, .97);
  /* the preview mat and the diff */
  --stage: #e8edf5;
  --diff-add: #e9f7ef;
  --diff-del: #fdecee;
  --diff-add-word: #a9e7c1;
  --diff-del-word: #f9c9cf;
}

[data-theme="mid"] {
  color-scheme: dark;
  /* ground and ink */
  --bg: #4a505b;
  --surface: #575e6a;
  --surface-muted: #4f5661;
  --surface-sunken: #454b55;
  --field: #3a3f49;
  --ink: #f2f4f8;
  --label: #d3d9e3;
  --muted: #c1c8d5;
  --line: #656d7b;
  --line-strong: #7b8492;
  --chip: #5b6270;
  --chip-ink: #e6eaf1;
  --mark: #d8a63f;
  --mark-ink: #1c1a12;
  /* brand */
  --brand: #aabaff;
  --brand-strong: #c3cdff;
  --brand-soft: #444d6e;
  --brand-line: #6c7aa8;
  --brand-ink: #dbe2ff;
  --on-brand: #12162a;
  --wash-user: #5b6480;
  --wash-admin: #655b80;
  --focus-ring: rgba(170, 186, 255, .45);
  --focus-soft: rgba(170, 186, 255, .22);
  /* status */
  --success: #6ede9f;
  --success-soft: #3c5a4b;
  --success-line: #5c8a72;
  --warning: #f0bd63;
  --warning-soft: #5b4c2e;
  --warning-line: #8a7442;
  --danger: #ffb0b6;
  --danger-soft: #5e3a3f;
  --danger-line: #8d5b61;
  --danger-wash: #4f3438;
  /* the rail: sidebar, brand block, account */
  --rail: #333944;
  --rail-ink: #eef1f7;
  --rail-muted: #a9b2c2;
  --rail-line: #4c5462;
  --rail-hover: rgba(255, 255, 255, .10);
  --rail-accent: #aabaff;
  --rail-avatar: #4a5364;
  /* output that is content, not chrome */
  --terminal: #23272f;
  --terminal-ink: #d5dae4;
  --terminal-muted: #8d95a3;
  --tooltip: #2b303a;
  --tooltip-ink: #eef1f7;
  /* depth */
  --shadow: 0 12px 32px rgba(0, 0, 0, .22);
  --shadow-md: 0 16px 40px rgba(0, 0, 0, .32);
  --shadow-lg: 0 28px 80px rgba(0, 0, 0, .45);
  --scrim: rgba(12, 14, 18, .60);
  --curtain: rgba(74, 80, 91, .93);
  --bar: rgba(79, 86, 97, .97);
  /* the preview mat and the diff */
  --stage: #3e444e;
  --diff-add: #33503f;
  --diff-del: #533338;
  --diff-add-word: #487053;
  --diff-del-word: #855058;
}

[data-theme="dark"] {
  color-scheme: dark;
  /* ground and ink */
  --bg: #1b1d21;
  --surface: #26292e;
  --surface-muted: #212429;
  --surface-sunken: #1a1c20;
  --field: #16181b;
  --ink: #dfe3ea;
  --label: #b9c0cb;
  --muted: #939bab;
  --line: #363a41;
  --line-strong: #494e57;
  --chip: #343841;
  --chip-ink: #c3cad6;
  --mark: #7a5a12;
  --mark-ink: #ffe9b0;
  /* brand */
  --brand: #7d97ff;
  --brand-strong: #9db0ff;
  --brand-soft: #2a3252;
  --brand-line: #46527e;
  --brand-ink: #c7d2ff;
  --on-brand: #0d1226;
  --wash-user: #232a44;
  --wash-admin: #2a2344;
  --focus-ring: rgba(125, 151, 255, .42);
  --focus-soft: rgba(125, 151, 255, .22);
  /* status */
  --success: #5fd39a;
  --success-soft: #1d3a2c;
  --success-line: #326b4f;
  --warning: #e6ab52;
  --warning-soft: #3d2f16;
  --warning-line: #6b5326;
  --danger: #f4848d;
  --danger-soft: #3f2126;
  --danger-line: #703a41;
  --danger-wash: #33191d;
  /* the rail: sidebar, brand block, account */
  --rail: #131519;
  --rail-ink: #e7ebf3;
  --rail-muted: #98a1b2;
  --rail-line: #2c3038;
  --rail-hover: rgba(255, 255, 255, .08);
  --rail-accent: #7d97ff;
  --rail-avatar: #2f3542;
  /* output that is content, not chrome */
  --terminal: #121417;
  --terminal-ink: #cfd6e2;
  --terminal-muted: #7d8492;
  --tooltip: #2c313a;
  --tooltip-ink: #e7ebf3;
  /* depth */
  --shadow: 0 12px 32px rgba(0, 0, 0, .50);
  --shadow-md: 0 16px 40px rgba(0, 0, 0, .60);
  --shadow-lg: 0 28px 80px rgba(0, 0, 0, .70);
  --scrim: rgba(0, 0, 0, .66);
  --curtain: rgba(27, 29, 33, .94);
  --bar: rgba(33, 36, 41, .97);
  /* the preview mat and the diff */
  --stage: #101216;
  --diff-add: #1d3a2c;
  --diff-del: #3f2126;
  --diff-add-word: #2f6a4c;
  --diff-del-word: #7a3c44;
}

:root {
  --radius: 14px;
  --sidebar: 248px;
  font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { min-width: 320px; background: var(--bg); }
body { margin: 0; color: var(--ink); background: var(--bg); font-size: 15px; line-height: 1.5; }
button, input, select, textarea { font: inherit; }
button, a { -webkit-tap-highlight-color: transparent; }
a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
.app-shell { min-height: 100vh; display: grid; grid-template-columns: var(--sidebar) minmax(0, 1fr); }
.sidebar { position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; padding: 22px 16px 18px; background: var(--rail); color: var(--rail-ink); z-index: 20; }
.brand { display: flex; align-items: center; gap: 12px; padding: 0 8px 26px; color: var(--rail-ink); }
.brand:hover { text-decoration: none; }
/* The logo keeps its colours in all three themes -- a mark that re-tints
   with the UI is not a mark. Same for .portal-identity below and for the
   .template-preview* thumbnails, which are pictures of somebody else's
   site. */
.brand-mark { display: grid; place-items: center; width: 36px; height: 36px; border-radius: 10px; background: linear-gradient(145deg, #6b83ff, #3156d3); font-weight: 800; box-shadow: 0 5px 18px rgba(63, 91, 220, .4); }
.brand strong, .brand small, .account strong, .account small { display: block; }
.brand strong { letter-spacing: -.02em; }
.brand small, .account small { color: var(--rail-muted); font-size: 12px; }
.primary-nav { display: grid; gap: 6px; }
.primary-nav a { display: flex; align-items: center; gap: 11px; padding: 10px 12px; border-radius: 9px; color: var(--rail-muted); font-weight: 600; }
.primary-nav a:hover, .primary-nav a.active { color: var(--rail-ink); background: var(--rail-hover); text-decoration: none; }
.primary-nav a.active { box-shadow: inset 3px 0 var(--rail-accent); }
.sidebar-foot { margin-top: auto; display: grid; gap: 14px; }
.account { display: flex; gap: 10px; align-items: center; min-width: 0; padding: 8px 10px; border-radius: 9px; color: var(--rail-ink); }
.account:hover, .account.active { background: var(--rail-hover); text-decoration: none; }
.account.active { box-shadow: inset 3px 0 var(--rail-accent); }

/* Language switcher.

   A segmented control of real submit buttons, not a <select>: two options
   fit, one click switches, and it works with JavaScript off -- which the
   login page needs, since it renders before any module has loaded.

   The login card is the only place it still appears: there is no session
   there, so there is no profile to read the language from. Everywhere
   behind a login it was replaced by the radio group on /profile, which
   saves the choice with the rest of the account rather than leaving it in
   a cookie three different pages each offered to set. The base rule keeps
   the dark palette it was written with; `.login-lang` re-tints it. */
.lang-switch { display: inline-flex; gap: 2px; padding: 2px; border-radius: 9px; background: var(--rail-hover); }
.lang-option { flex: 1; padding: 5px 10px; border: 0; border-radius: 7px; background: transparent; color: var(--rail-muted); font: inherit; font-size: 11px; font-weight: 750; letter-spacing: .06em; cursor: pointer; }
.lang-option:hover { color: var(--rail-ink); }
.lang-option.active { color: var(--rail-ink); background: var(--rail-line); }
.lang-option:focus-visible { outline: 2px solid var(--rail-accent); outline-offset: 1px; }

.login-lang { display: flex; justify-content: center; margin-top: 18px; }
/* Restated rather than inherited: this switcher sits inside a card that
   grid-lays-out its own form, and one stray descendant selector already
   stacked it once. */
.login-lang .lang-switch { display: inline-flex; }
.login-lang .lang-switch { background: var(--surface-sunken); }
.login-lang .lang-option { color: var(--muted); }
.login-lang .lang-option:hover, .login-lang .lang-option.active { color: var(--ink); }
.login-lang .lang-option.active { background: var(--surface); box-shadow: 0 1px 3px rgba(23, 32, 51, .16); }

.avatar { flex: 0 0 auto; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; background: var(--rail-avatar); font-weight: 700; }
.main-column { min-width: 0; }
.topbar { height: 68px; padding: 0 clamp(18px, 3vw, 42px); display: flex; align-items: center; justify-content: space-between; gap: 20px; border-bottom: 1px solid var(--line); background: var(--bar); backdrop-filter: blur(10px); position: sticky; top: 0; z-index: 10; }
.topbar-actions { display: flex; gap: 8px; align-items: center; }
.content { padding: 34px clamp(18px, 3vw, 42px) 64px; max-width: 1500px; width: 100%; margin: 0 auto; }
.eyebrow { color: var(--muted); font-size: 13px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.breadcrumb { color: var(--muted); font-size: 14px; }
.breadcrumb a { color: inherit; }
.breadcrumb span { margin: 0 7px; color: var(--muted); }
.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; margin-bottom: 28px; }
.page-head h1 { margin: 2px 0 6px; font-size: clamp(27px, 4vw, 38px); line-height: 1.15; letter-spacing: -.035em; }
.page-head p { margin: 0; color: var(--muted); max-width: 720px; }
.actions { display: flex; flex-wrap: wrap; gap: 9px; align-items: center; }
.button, .icon-button { border: 1px solid transparent; border-radius: 9px; cursor: pointer; font-weight: 700; transition: background .16s, border-color .16s, transform .16s; }
.button { min-height: 40px; padding: 8px 14px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; background: var(--brand); color: var(--on-brand); }
.button:hover { background: var(--brand-strong); text-decoration: none; }
.button:active { transform: translateY(1px); }
.button.secondary { color: var(--ink); background: var(--surface); border-color: var(--line-strong); }
.button.secondary:hover { background: var(--surface-muted); }
.button.ghost { color: var(--muted); background: transparent; border-color: var(--line); }
.sidebar .button.ghost { color: var(--rail-ink); border-color: var(--rail-line); }
.button.danger { background: var(--danger); }
.button.danger-outline { color: var(--danger); background: var(--surface); border-color: var(--danger-line); }
.button.small { min-height: 32px; padding: 5px 10px; font-size: 13px; }
.button.full { width: 100%; }
.button:disabled { cursor: not-allowed; opacity: .55; transform: none; }
.icon-button { width: 40px; height: 40px; background: transparent; color: var(--ink); }
.mobile-menu { display: none; }
.grid { display: grid; gap: 18px; }
.grid.cards { grid-template-columns: repeat(auto-fit, minmax(245px, 1fr)); }
.grid.two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: 0 1px 2px rgba(31, 42, 68, .03); }
.card-head { padding: 18px 20px; display: flex; align-items: center; justify-content: space-between; gap: 16px; border-bottom: 1px solid var(--line); }
.card-head h2, .card-head h3 { margin: 0; font-size: 16px; }
.card-body { padding: 20px; }
.site-card { padding: 21px; display: grid; gap: 18px; transition: transform .18s, box-shadow .18s, border-color .18s; }
.site-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); border-color: var(--line-strong); }
.site-card h2 { margin: 0; font-size: 19px; }
.site-card p { margin: 3px 0 0; color: var(--muted); font-size: 13px; }
.site-card-top { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; }
.site-card-meta { display: flex; gap: 18px; color: var(--muted); font-size: 13px; }
.site-card-actions { display: flex; gap: 8px; }
.metric { padding: 19px; }
.metric-label { display: block; color: var(--muted); font-size: 13px; font-weight: 700; }
.metric-value { display: block; margin-top: 5px; font-size: 26px; line-height: 1.2; font-weight: 750; letter-spacing: -.03em; }
.metric-detail { color: var(--muted); font-size: 12px; }
.badge { display: inline-flex; align-items: center; gap: 6px; width: max-content; padding: 3px 9px; border-radius: 999px; background: var(--chip); color: var(--chip-ink); font-size: 12px; font-weight: 750; white-space: nowrap; }
.badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.badge.success { color: var(--success); background: var(--success-soft); }
.badge.warning { color: var(--warning); background: var(--warning-soft); }
.badge.danger { color: var(--danger); background: var(--danger-soft); }
.toolbar { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-bottom: 18px; }
.search { position: relative; flex: 1; max-width: 420px; }
.search input { padding-left: 38px; }
.search::before { content: "⌕"; position: absolute; left: 13px; top: 8px; color: var(--muted); font-size: 19px; }
.field { display: grid; gap: 6px; }
.field > span, .field label { font-size: 13px; font-weight: 700; color: var(--label); }
.field small { color: var(--muted); }
input, select, textarea { width: 100%; color: var(--ink); background: var(--field); border: 1px solid var(--line-strong); border-radius: 8px; padding: 9px 11px; min-height: 40px; }
textarea { min-height: 92px; resize: vertical; }
input:focus, select:focus, textarea:focus { border-color: var(--brand); outline: 3px solid var(--focus-soft); }
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 15px; align-items: start; }
.form-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 20px; }
.help { color: var(--muted); font-size: 13px; }
.card-body > .help:first-child { margin-top: 0; }
.error-text { min-height: 20px; color: var(--danger); font-size: 13px; }
/* Inline, per-field complaint (the "Site name" input). Needs to beat
   `.field small`, which paints every hint muted. */
.field small.field-error { color: var(--danger); font-weight: 600; }
input[aria-invalid="true"] { border-color: var(--danger); }
input[aria-invalid="true"]:focus { outline-color: var(--danger-soft); }
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 13px 15px; text-align: left; border-bottom: 1px solid var(--line); vertical-align: middle; }
th { color: var(--muted); background: var(--surface-muted); font-size: 11px; letter-spacing: .05em; text-transform: uppercase; }
tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--surface-muted); }
.row-title { font-weight: 700; color: var(--ink); }
.row-subtitle { color: var(--muted); font-size: 12px; }
.tabs { display: flex; gap: 3px; overflow-x: auto; border-bottom: 1px solid var(--line); margin-bottom: 22px; }
.tab { padding: 11px 14px; border: 0; border-bottom: 2px solid transparent; background: transparent; color: var(--muted); cursor: pointer; font-weight: 700; white-space: nowrap; }
.tab[aria-selected="true"] { color: var(--brand); border-color: var(--brand); }
.tab-panel[hidden] { display: none; }
.stack { display: grid; gap: 18px; }
.split { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(280px, .55fr); gap: 18px; align-items: start; }
.terminal { min-height: 150px; max-height: 460px; overflow: auto; margin: 0; padding: 16px; border-radius: 10px; background: var(--terminal); color: var(--terminal-ink); font: 12.5px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
/* The placeholder text is a translated string, so it comes from the
   element's own data-empty attribute rather than being frozen into the
   stylesheet -- CSS has no access to the catalog. */
.terminal:empty::before { content: attr(data-empty); color: var(--terminal-muted); }
.file-list { display: grid; max-height: 360px; overflow: auto; border: 1px solid var(--line); border-radius: 9px; }
.file-row { display: grid; grid-template-columns: auto 48px minmax(0, 1fr); gap: 10px; align-items: center; padding: 9px 11px; border-bottom: 1px solid var(--line); }
.file-row:last-child { border-bottom: 0; }
.file-row input { width: 16px; min-height: auto; }
.file-code { font: 700 12px ui-monospace, monospace; color: var(--warning); }
.file-path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 13px ui-monospace, monospace; }
.empty { padding: 42px 20px; text-align: center; color: var(--muted); }
.empty strong { display: block; color: var(--ink); font-size: 17px; margin-bottom: 5px; }
.check-list { display: grid; gap: 8px; }
.check-item { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--line); }
.check-item:last-child { border: 0; }
.check-icon { display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; background: var(--success-soft); color: var(--success); font-size: 12px; font-weight: 900; }
.check-item.fail .check-icon { color: var(--danger); background: var(--danger-soft); }
.check-item strong, .check-item small { display: block; }
.check-item small { color: var(--muted); }
.callout { padding: 14px 16px; border-radius: 10px; border: 1px solid var(--brand-line); background: var(--brand-soft); color: var(--brand-ink); }
.callout.warning { border-color: var(--warning-line); background: var(--warning-soft); color: var(--warning); }
.danger-zone { border-color: var(--danger-line); }
.danger-zone .card-head { color: var(--danger); border-color: var(--danger-line); }
dialog { width: min(560px, calc(100vw - 30px)); border: 0; border-radius: 16px; padding: 0; box-shadow: var(--shadow-lg); }
dialog::backdrop { background: var(--scrim); backdrop-filter: blur(2px); }
.dialog-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; padding: 20px 22px 14px; }
.dialog-head h2 { margin: 0; font-size: 20px; }
.dialog-head p { margin: 4px 0 0; color: var(--muted); }
.dialog-body { padding: 8px 22px 22px; }
.dialog-close { border: 0; background: transparent; color: var(--muted); cursor: pointer; font-size: 22px; }
.toast-region { position: fixed; top: 82px; right: 20px; z-index: 100; display: grid; gap: 8px; width: min(380px, calc(100vw - 40px)); }
.toast { padding: 13px 15px; border: 1px solid var(--line); border-left: 4px solid var(--brand); border-radius: 10px; background: var(--surface); box-shadow: var(--shadow); animation: toast-in .2s ease-out; }
.toast.success { border-left-color: var(--success); }
.toast.error { border-left-color: var(--danger); }
.toast strong, .toast span { display: block; }
.toast span { color: var(--muted); font-size: 13px; margin-top: 2px; }
/* The iframe's own backdrop, i.e. the tenant's page before it paints --
   content, not chrome, so it stays white whatever the chrome is doing. */
.preview-frame { width: 100%; height: min(68vh, 700px); border: 1px solid var(--line); border-radius: 10px; background: #fff; }

/* "Still building" curtain over a preview iframe.
   Driven by app.js's previewWatcher; used by both dashboards. A preview
   container takes seconds (and after a fresh `npm install`, minutes) to
   answer, and the frame behind this is either blank or a proxy error page
   until it does -- which is what made people reach for a hard refresh. */
.preview-stage { position: relative; }
.preview-status { position: absolute; inset: 0; z-index: 3; display: grid; place-items: center; padding: 20px; overflow: auto; background: var(--curtain); }
.preview-status-card { display: grid; justify-items: center; gap: 10px; max-width: 340px; text-align: center; }
/* A crash is the one state with something to *read*, and it was given the
   same 340px card as "starting…" -- a container log in a box the size of a
   tooltip, in the middle of an otherwise empty stage. It gets the room
   instead: the card widens and the log takes most of the height it is
   sitting in. */
.preview-status[data-state="crashed"] .preview-status-card,
.preview-status[data-state="starting"] .preview-status-card { max-width: min(960px, 100%); width: 100%; }
.preview-status-card strong { font-size: 15px; }
.preview-status-card p { margin: 0; color: var(--muted); font-size: 13px; }
.preview-spinner { width: 26px; height: 26px; border-radius: 50%; border: 3px solid var(--line-strong); border-top-color: var(--brand); animation: chatcms-spin .9s linear infinite; }
.preview-status[data-state="failed"] .preview-spinner,
.preview-status[data-state="stopped"] .preview-spinner,
.preview-status[data-state="crashed"] .preview-spinner { display: none; }
/* The log is a disclosure while a preview starts and open once it has
   crashed -- the reason for a failure must not need a click to be found,
   and a healthy start does not need a wall of log in front of it. */
.preview-insight { justify-self: stretch; width: 100%; text-align: left; border: 1px solid var(--line-strong); border-radius: 10px; background: var(--surface); }
.preview-insight > summary { display: flex; align-items: baseline; gap: 8px; padding: 8px 11px; border-radius: inherit; cursor: pointer; user-select: none; font-size: 12.5px; font-weight: 650; color: var(--ink); }
.preview-insight[open] > summary { border-bottom: 1px solid var(--line); border-radius: 10px 10px 0 0; }
.preview-insight .preview-status-detail { border-radius: 0 0 10px 10px; }
/* The crashed container's own log. Left-aligned and scrollable inside the
   centred card: it is evidence to read, not a caption. */
.preview-status-detail { justify-self: stretch; width: 100%; max-width: 100%; max-height: min(62vh, 640px); overflow: auto; margin: 0; padding: 12px 14px; border-radius: 8px; background: var(--terminal); color: var(--terminal-ink); font: 12.5px/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; text-align: left; white-space: pre-wrap; overflow-wrap: anywhere; }
@keyframes chatcms-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .preview-spinner { animation: none; border-top-color: var(--brand); } }
.definition-list { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 10px 22px; margin: 0; }
.definition-list dt { color: var(--muted); }
.definition-list dd { margin: 0; font-weight: 650; overflow-wrap: anywhere; }
.inline-status { min-height: 22px; color: var(--muted); font-size: 13px; }
.justify-center { justify-content: center; }
.mt-14 { margin-top: 14px; }
.mt-15 { margin-top: 15px; }
.mt-18 { margin-top: 18px; }
.checkbox-line input { width: auto; min-height: auto; }
.template-browser { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 13px; margin-top: 18px; }
.template-card { position: relative; display: grid; overflow: hidden; border: 2px solid var(--line); border-radius: 12px; background: var(--surface); cursor: pointer; transition: border-color .16s, box-shadow .16s, transform .16s; }
.template-card:hover { transform: translateY(-2px); border-color: var(--line-strong); box-shadow: var(--shadow); }
.template-card:has(input:checked) { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }
.template-card > input { position: absolute; width: 1px; height: 1px; min-height: 0; opacity: 0; }
.template-preview { display: block; min-height: 132px; padding: 13px; background: linear-gradient(145deg, #16152a, #302660 58%, #874ccc); }
.template-preview-blog { background: linear-gradient(145deg, #14213d, #34537e 58%, #ef8354); }
.template-preview-starlight { background: linear-gradient(145deg, #111827, #4338ca 58%, #a855f7); }
.template-preview-starlog { background: linear-gradient(145deg, #0b1220, #172554 58%, #22d3ee); }
.template-preview-portfolio { background: linear-gradient(145deg, #2b2118, #8a4b2b 58%, #f5ba68); }
.template-preview-empty { background: linear-gradient(145deg, #e8edf5, #cbd5e1 58%, #94a3b8); }
.template-window { display: block; height: 106px; overflow: hidden; border-radius: 7px; background: #fbf9ff; box-shadow: 0 10px 30px rgba(9, 8, 24, .3); }
.template-window-bar { display: block; height: 15px; border-bottom: 1px solid #e4dff0; background: #f1edf7; }
.template-window-bar::before { content: "•••"; display: block; padding-left: 6px; color: #a39aae; font: 11px/11px sans-serif; letter-spacing: 1px; }
.template-window-hero { display: grid; place-items: center; height: calc(100% - 15px); padding: 12px; color: #2a2140; text-align: center; background: radial-gradient(circle at 78% 20%, #f1c8ff, transparent 27%), linear-gradient(145deg, #fff, #f1edff); }
.template-preview-blog .template-window-hero { background: linear-gradient(160deg, #fffaf4, #fce3cf); color: #5c2c18; }
.template-preview-starlight .template-window-hero { background: linear-gradient(90deg, #ede9fe 28%, #fafaff 28%); color: #4338ca; }
.template-preview-starlog .template-window-hero { background: linear-gradient(145deg, #111827, #1e293b); color: #67e8f9; }
.template-preview-portfolio .template-window-hero { background: radial-gradient(circle at 75% 30%, #facf8a, transparent 24%), #fff8ec; color: #713f12; }
.template-preview-empty .template-window-hero { background: #fff; color: #64748b; }
.template-window-hero strong { display: block; font-size: 15px; letter-spacing: -.03em; }
.template-window-hero small { color: #786c8f; }
.template-copy { display: grid; gap: 7px; padding: 13px; }
.template-copy strong { font-size: 15px; }
.template-copy p { margin: 0; color: var(--muted); font-size: 12px; }
.template-tags { display: flex; flex-wrap: wrap; gap: 5px; }
.template-tag { padding: 2px 6px; border-radius: 999px; color: var(--brand-ink); background: var(--brand-soft); font-size: 10px; font-weight: 750; }
.login-page { min-height: 100vh; display: grid; place-items: center; padding: 20px; background: radial-gradient(circle at 15% 10%, var(--wash-user), transparent 35%), var(--bg); }
.login-page-admin { background: radial-gradient(circle at 15% 10%, var(--wash-admin), transparent 35%), var(--bg); }
.login-card { width: min(460px, 100%); padding: 32px; background: var(--surface); border: 1px solid var(--line); border-radius: 18px; box-shadow: var(--shadow); }
.login-brand { display: flex; align-items: center; gap: 12px; margin-bottom: 22px; }
.portal-identity { margin-bottom: 24px; padding: 22px; border: 1px solid #c9d3f5; border-radius: 14px; background: linear-gradient(135deg, #f0f4ff, #e9efff); }
.portal-identity-admin { border-color: #d9c9fa; background: linear-gradient(135deg, #f6f1ff, #ede6ff); }
.portal-identity > span { color: var(--muted); font-size: 12px; font-weight: 750; letter-spacing: .1em; text-transform: uppercase; }
.portal-identity h1 { margin: 5px 0 7px; color: #263f99; font-size: clamp(30px, 8vw, 38px); line-height: 1; letter-spacing: -.04em; }
.portal-identity-admin h1 { color: #6335a6; }
.portal-identity p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.5; }
.login-card > h2 { margin: 0 0 18px; font-size: 20px; letter-spacing: -.02em; }
/* Direct child only. This used to match every form inside the card, which
   since the language switcher moved in meant its two buttons were laid out
   as a one-column grid and stacked. */
.login-card > form { display: grid; gap: 15px; }
.portal-switch { display: grid; gap: 5px; margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--line); text-align: center; }
.portal-switch span { color: var(--muted); font-size: 12px; }
.portal-switch a { font-size: 14px; font-weight: 750; }
.alert { padding: 11px 13px; border-radius: 9px; color: var(--danger); background: var(--danger-soft); font-size: 13px; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }

/* Content Studio */
.studio-pane-head { min-height: 65px; padding: 12px 14px; display: flex; align-items: center; justify-content: space-between; gap: 10px; border-bottom: 1px solid var(--line); }
.studio-pane-head h2 { margin: 0; font-size: 15px; }
.studio-pane-head .eyebrow { font-size: 10px; }
.studio-search { padding: 11px 12px 7px; }
.studio-search input { min-height: 36px; padding: 7px 10px; }
/* The filter row: the categories, and the fold control that acts on what
   they leave. It sits here rather than in the pane head because that is
   what it operates on -- and because the head is 190px wide at its
   narrowest and already carries a title and a refresh. The chips scroll
   when they must; the control does not go with them. */
.studio-filterbar { display: flex; align-items: center; gap: 6px; padding: 5px 10px 9px; border-bottom: 1px solid var(--line); }
.studio-categories { flex: 1; min-width: 0; display: flex; gap: 4px; overflow-x: auto; }
.studio-tree-toggle { flex: none; width: 26px; height: 26px; }
.studio-category { padding: 5px 8px; border: 0; border-radius: 7px; color: var(--muted); background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; }
.studio-category:hover, .studio-category.active { color: var(--brand); background: var(--brand-soft); }
/* The file tree.

   One row per file, one per folder, indentation by padding rather than by
   nested boxes -- so the hover and the selection fill the pane's full width
   however deep the row sits, which is what keeps a tree from looking like a
   stack of increasingly narrow lists. Folders carry the chevron and no
   folder icon: in a column the reader can drag down to 150px, a second mark
   on every folder row costs a word of every folder name.

   `--depth` is written per row by studio.js. 13px a level is deliberately
   tighter than a desktop file manager's: the compacted rows
   (`src/content/blog` as one row) mean depth rarely passes three, and the
   indent only has to be readable, not roomy. */
.studio-tree-scroll { flex: 1; min-height: 0; overflow: auto; }
.studio-pinned { padding: 7px 6px 0; }
.studio-group-label { display: block; padding: 4px 8px 3px; color: var(--muted); font-size: 10px; letter-spacing: .07em; text-transform: uppercase; }
.studio-tree { padding: 7px 6px 14px; }
.studio-node { display: flex; align-items: center; gap: 7px; min-height: 26px; padding: 3px 8px 3px calc(8px + var(--depth, 0) * 13px); border-radius: 6px; color: var(--ink); cursor: pointer; font-size: 12.5px; line-height: 1.35; -webkit-user-select: none; user-select: none; }
.studio-node:hover { background: var(--surface-muted); }
.studio-node > svg { flex: none; color: var(--muted); }
.studio-node-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.studio-node-dir { font-weight: 650; }
.studio-node-dir > svg { transition: transform .13s ease; }
.studio-branch[aria-expanded="true"] > .studio-node-dir > svg { transform: rotate(90deg); }
.studio-node-count { margin-left: auto; padding-left: 7px; color: var(--muted); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.studio-node-dir-hint { margin-left: auto; padding-left: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 10.5px; }
.studio-node-file.active { color: var(--brand-strong); background: var(--brand-soft); font-weight: 650; }
.studio-node-file.active > svg { color: var(--brand); }
.studio-node.is-readonly .studio-node-name { color: var(--muted); }
.studio-tree-capped { margin: 10px 8px 0; color: var(--muted); font-size: 11px; }
.studio-node mark { padding: 0 1px; border-radius: 3px; color: var(--mark-ink); background: var(--mark); }
/* The tree moves focus itself (roving tabindex), so the ring belongs on the
   row, not on the branch box that also contains every child of it. */
.studio-branch:focus-visible, .studio-node-file:focus-visible { outline: none; }
.studio-branch:focus-visible > .studio-node, .studio-node-file:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: -2px; }
/* The row menu, and the list it opens.

   Fixed-position at document level: the tree is a scroll container, and a
   menu inside it would scroll away from the row it belongs to and be clipped
   by the pane's own edge. One layer holds either shape -- a menu of actions
   or a list of places -- because they are the same box with different rows,
   and the second is reached from the first. */
.studio-menu-layer { position: fixed; inset: 0; z-index: 60; pointer-events: none; }
.studio-menu { position: absolute; min-width: 208px; max-width: min(340px, 92vw); padding: 5px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); box-shadow: var(--shadow-md); pointer-events: auto; }
.studio-menu hr { height: 1px; margin: 4px 6px; border: 0; background: var(--line); }
.studio-menu-item { display: grid; grid-template-columns: 15px minmax(0, 1fr); gap: 4px 9px; width: 100%; padding: 6px 9px; border: 0; border-radius: 7px; color: var(--ink); background: transparent; cursor: pointer; font-size: 13px; text-align: left; }
.studio-menu-item:hover:not(:disabled), .studio-menu-item:focus-visible { background: var(--surface-muted); outline: none; }
.studio-menu-item:focus-visible { box-shadow: inset 0 0 0 2px var(--brand); }
.studio-menu-item:disabled { color: var(--muted); cursor: default; }
.studio-menu-item > svg { grid-row: 1; color: var(--muted); }
.studio-menu-item.danger { color: var(--danger); }
.studio-menu-item.danger > svg { color: var(--danger); }
.studio-menu-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.studio-menu-hint { grid-column: 2; color: var(--muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The preview menu's own title -- what was clicked -- and the section headings
   that group the questions under the file each one acts on. Both stay on one
   line and carry the full value as a tooltip: the clicked text is often a whole
   paragraph, and a heading that wraps pushes every actual choice out of sight. */
/* The opening line of a menu-started conversation. The reader was there and
   knows what they clicked; the answer to it is the conversation, so this reads
   as a caption and keeps the chat clean. */
.chat-context-line { margin: 2px 0 6px; padding: 0 2px; color: var(--muted); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* An option that says what it means. The note sits under its own label so the
   choice reads as one thing, and the button stops being a bare word the reader
   has to interpret. */
.chat-actions-options .button.has-note { display: block; padding-top: 7px; padding-bottom: 7px; text-align: left; }
.chat-option-note { display: block; margin-top: 2px; color: var(--muted); font-size: 11.5px; font-weight: 400; white-space: normal; }
.chat-actions-other { display: flex; gap: 6px; margin-top: 6px; }
.chat-actions-other .input { flex: 1 1 auto; min-width: 0; }
.studio-menu-header { margin: 0; padding: 5px 9px 6px; border-bottom: 1px solid var(--line); color: var(--muted); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.studio-menu-heading { display: block; margin: 0; padding: 8px 9px 3px; color: var(--label); font-size: 11px; font-weight: 700; letter-spacing: .03em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.studio-menu-heading > .studio-menu-subheading { display: block; margin-top: 1px; color: var(--muted); font-weight: 400; letter-spacing: 0; text-transform: none; overflow: hidden; text-overflow: ellipsis; }
.studio-list { max-height: min(60vh, 460px); overflow: auto; }
.studio-list-title { display: block; padding: 6px 9px 2px; color: var(--label); font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.studio-list-note { margin: 0 9px 6px; color: var(--muted); font-size: 11px; }
.studio-list-row { font: 12.5px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
.studio-list-row.is-missing .studio-menu-label { color: var(--muted); text-decoration: line-through; }
/* The rule between the two groups sits inside a scrolling list, so it needs
   its own breathing room -- the menu's own separator is tuned for rows that
   are one line high, and a list row is two. */
.studio-list hr { margin: 7px 9px; }
/* Three states and a fourth that is deliberately silent: a check that timed
   out means the dev server was busy, not that the page is absent, and a
   cross there would be a claim nobody made. */
.studio-list-status { grid-row: 1; width: 15px; text-align: center; color: var(--muted); font-size: 12px; }
.studio-list-status[data-state="pending"]::before { content: "\00b7\00b7\00b7"; letter-spacing: 1px; }
.studio-list-status[data-state="ok"]::before { content: "\2713"; color: var(--success); }
.studio-list-status[data-state="missing"]::before { content: "\2717"; color: var(--danger); }

/* "Show in preview" for the file that is open, on the line that already
   says what kind of file it is -- the action row above is six controls wide
   and this is a question about the words next to it, not another tool. */
.studio-kind-line { display: flex; align-items: center; gap: 8px; min-width: 0; }
.studio-reveal { display: inline-flex; align-items: center; gap: 4px; padding: 1px 7px 1px 5px; border: 1px solid var(--line); border-radius: 999px; color: var(--muted); background: transparent; cursor: pointer; font-size: 10.5px; font-weight: 700; white-space: nowrap; }
.studio-reveal:hover:not(:disabled) { color: var(--brand-strong); border-color: var(--brand); background: var(--brand-soft); }
.studio-reveal:disabled { opacity: .45; cursor: default; }
.studio-reveal svg { display: block; }

.studio-tree-toggle[data-state="expanded"] [data-tree-mark="expand"] { display: none; }
.studio-tree-toggle[data-state="collapsed"] [data-tree-mark="collapse"] { display: none; }
/* Both actions carry a mark and their word, like every other button in
   this workspace. They share the row and wrap onto a second one rather than
   truncating: at 190px two labelled buttons do not fit side by side, and a
   button reading "Upload as…" is worse than a button on its own line. */
.studio-browser-actions { padding: 10px; display: flex; flex-wrap: wrap; gap: 7px; border-top: 1px solid var(--line); }
.studio-browser-actions .button { flex: 1 1 auto; white-space: nowrap; }
.studio-file-heading { min-width: 0; }
.studio-file-heading .eyebrow { display: block; }
.studio-file-heading h2 { display: inline; margin-right: 2px; overflow-wrap: anywhere; }
/* Renaming is a fact about *this* name, so the control belongs to the name
   rather than to the row of things you can do to the file -- in that row it
   was one grey pencil among five grey marks, and nothing said which of them
   acted on the words right next to it.
   Inline, because the name is inline: a long path wraps to two or three
   lines and the pencil stays glued to its last character. A flex row would
   have centred the button against the whole wrapped block instead.
   It sits *before* the unsaved badge: that badge comes and goes with the
   first keystroke, and a button that slides sideways when it appears is one
   the reader has to re-aim at. Quiet until pointed at -- it is a second
   thing on a heading line, not a call to action. */
.studio-name-edit { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px;
  padding: 0; vertical-align: -5px; border: 0; border-radius: 7px; color: var(--muted); background: transparent; cursor: pointer; }
.studio-name-edit:hover:not(:disabled) { color: var(--ink); background: var(--surface-muted); }
.studio-name-edit:disabled { opacity: .4; cursor: default; }
.studio-name-edit svg { display: block; }
.studio-dirty { margin-left: 6px; padding: 2px 6px; border-radius: 999px; color: var(--warning); background: var(--warning-soft); font-size: 10px; font-weight: 750; }
.studio-welcome { flex: 1; display: grid; place-items: center; padding: 30px; text-align: center; color: var(--muted); }
.studio-welcome strong { display: block; color: var(--ink); font-size: 18px; }
.studio-welcome p { margin: 5px 0 0; }
.studio-document { flex: 1; min-height: 0; overflow: auto; }
.studio-form { padding: 18px; }
.studio-form .form-grid { grid-template-columns: 1fr; }
.studio-form .field-checkbox { display: flex; align-items: center; gap: 9px; }
.studio-form .field-checkbox input { width: auto; min-height: auto; }
.studio-body-field { margin-top: 17px; }
.studio-body-field textarea { min-height: 260px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.studio-code { position: relative; height: 100%; min-height: 410px; display: flex; flex-direction: column; }
.studio-code > textarea, #studio-source > textarea { width: 100%; height: 100%; border: 0; border-radius: 0; resize: none; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
/* A stacking context of its own, and a low one. CodeMirror numbers its own
   layers up to 6 -- the line-number gutter is 4, the fake scrollbars are 6 --
   and none of that is inside a context of its own, so the numbers shone
   through the metadata panel, which was also 4 and came earlier in the DOM.
   Raising the panel would fix today's collision and lose the next one to
   whichever addon picks a higher number; trapping CodeMirror's whole scale
   under one z-index ends the argument. */
.studio-code #studio-source { position: relative; z-index: 0; flex: 1; min-height: 0; }
/* CodeMirror on a dark ground.

   The vendored stylesheet ships one theme (`cm-s-default`) and it is a light
   one: black on white with blue keywords. On the mid and dark schemes that
   leaves a bright rectangle in the middle of a dark panel -- the largest
   single thing on the Studio screen, and the one a reader spends minutes at
   a time inside. Bright keeps CodeMirror's own colours untouched, because
   "so wie jetzt" was the brief for that scheme.

   The syntax colours are literals rather than tokens on purpose: they are
   one palette shared by both dark schemes, they appear nowhere else, and a
   token nothing else reads is a token that drifts. Their contrast floor is
   enforced against BOTH grounds in tests/test_web_theming.py -- mid's editor
   ground is far lighter than dark's, so a colour that reads on one can go
   flat on the other. */
[data-theme="mid"] .CodeMirror,
[data-theme="dark"] .CodeMirror { color: var(--ink); background: var(--field); }
[data-theme="mid"] .CodeMirror-gutters,
[data-theme="dark"] .CodeMirror-gutters { border-right-color: var(--line); background: var(--surface-muted); }
[data-theme="mid"] .CodeMirror-linenumber,
[data-theme="dark"] .CodeMirror-linenumber { color: var(--muted); }
[data-theme="mid"] .CodeMirror-cursor,
[data-theme="dark"] .CodeMirror-cursor { border-left-color: var(--ink); }
/* The selection is painted on a layer *behind* the text, so it has to stay
   dim: a bright fill hides the very characters it is marking. */
[data-theme="mid"] .CodeMirror-selected,
[data-theme="dark"] .CodeMirror-selected { background: var(--brand-soft); }
[data-theme="mid"] .CodeMirror-focused .CodeMirror-selected,
[data-theme="dark"] .CodeMirror-focused .CodeMirror-selected { background: var(--brand-line); }
[data-theme="mid"] .CodeMirror-activeline-background,
[data-theme="dark"] .CodeMirror-activeline-background { background: var(--surface-muted); }
[data-theme="mid"] .CodeMirror-matchingbracket,
[data-theme="dark"] .CodeMirror-matchingbracket { color: var(--ink); outline: 1px solid var(--brand); }
[data-theme="mid"] .cm-s-default .cm-comment,
[data-theme="dark"] .cm-s-default .cm-comment { color: #a2acbd; }
[data-theme="mid"] .cm-s-default .cm-keyword,
[data-theme="dark"] .cm-s-default .cm-keyword { color: #cd9cef; }
[data-theme="mid"] .cm-s-default .cm-atom,
[data-theme="mid"] .cm-s-default .cm-number,
[data-theme="mid"] .cm-s-default .cm-string-2,
[data-theme="dark"] .cm-s-default .cm-atom,
[data-theme="dark"] .cm-s-default .cm-number,
[data-theme="dark"] .cm-s-default .cm-string-2 { color: #f78c6c; }
[data-theme="mid"] .cm-s-default .cm-def,
[data-theme="mid"] .cm-s-default .cm-variable-2,
[data-theme="dark"] .cm-s-default .cm-def,
[data-theme="dark"] .cm-s-default .cm-variable-2 { color: #82aaff; }
[data-theme="mid"] .cm-s-default .cm-variable-3,
[data-theme="mid"] .cm-s-default .cm-type,
[data-theme="dark"] .cm-s-default .cm-variable-3,
[data-theme="dark"] .cm-s-default .cm-type { color: #7fdbca; }
[data-theme="mid"] .cm-s-default .cm-string,
[data-theme="mid"] .cm-s-default .cm-quote,
[data-theme="dark"] .cm-s-default .cm-string,
[data-theme="dark"] .cm-s-default .cm-quote { color: #c3e88d; }
[data-theme="mid"] .cm-s-default .cm-meta,
[data-theme="mid"] .cm-s-default .cm-qualifier,
[data-theme="dark"] .cm-s-default .cm-meta,
[data-theme="dark"] .cm-s-default .cm-qualifier { color: #b9c0cb; }
[data-theme="mid"] .cm-s-default .cm-builtin,
[data-theme="mid"] .cm-s-default .cm-attribute,
[data-theme="dark"] .cm-s-default .cm-builtin,
[data-theme="dark"] .cm-s-default .cm-attribute { color: #ffcb6b; }
[data-theme="mid"] .cm-s-default .cm-tag,
[data-theme="mid"] .cm-s-default .cm-error,
[data-theme="dark"] .cm-s-default .cm-tag,
[data-theme="dark"] .cm-s-default .cm-error { color: #ff9ba1; }
[data-theme="mid"] .cm-s-default .cm-header,
[data-theme="mid"] .cm-s-default .cm-link,
[data-theme="dark"] .cm-s-default .cm-header,
[data-theme="dark"] .cm-s-default .cm-link { color: #8fa8ff; }

.studio-code .CodeMirror { height: 100%; min-height: 340px; font: 13px/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* The metadata flap.

   A tab hanging off the top edge of the editor that pulls the file's YAML
   head down over it as a form. It overlays rather than pushes: the editor
   keeps its scroll position and its cursor, so opening the metadata to
   check a date is not an interruption of writing.

   The tab is the only control that is always there -- the panel's own head
   holds the way out ("edit as text"), and the badge says which of the two
   states the metadata is in: a field count when the form owns it, a word
   when the text does. */
.studio-meta { z-index: 4; flex: none; height: 28px; border-bottom: 1px solid var(--line); background: var(--surface); }
.studio-meta-tab { display: inline-flex; align-items: center; gap: 7px; height: 27px; margin: 0 0 -1px 12px; padding: 0 10px; border: 1px solid var(--line); border-top: 0; border-radius: 0 0 10px 10px; color: var(--muted); background: var(--surface-muted); cursor: pointer; font-size: 12px; font-weight: 700; }
.studio-meta-tab:hover { color: var(--ink); background: var(--surface-sunken); }
.studio-meta[data-open="1"] .studio-meta-tab { color: var(--brand-strong); border-color: var(--brand); background: var(--brand-soft); }
.studio-meta-chevron { display: grid; place-items: center; }
.studio-meta-chevron svg { display: block; transition: transform .14s ease; }
.studio-meta[data-open="1"] .studio-meta-chevron svg { transform: rotate(180deg); }
.studio-meta-badge { min-width: 18px; padding: 1px 6px; border-radius: 999px; color: var(--muted); background: var(--line); font-size: 10px; text-align: center; }
.studio-meta[data-text="1"] .studio-meta-badge { color: var(--warning); background: var(--warning-soft); }
/* Measured against the editor, not the viewport: in the stacked layout the
   editor is 300px tall and a `58vh` panel would be clipped by the card's own
   overflow, hiding fields with nothing to scroll. The containing block is
   .studio-code, so `100%` is exactly the room there is. */
.studio-meta-panel { container-type: inline-size; position: absolute; z-index: 4; left: 0; right: 0; top: 28px; max-height: calc(100% - 28px); overflow: auto; border-bottom: 1px solid var(--line-strong); background: var(--surface); box-shadow: var(--shadow-md); animation: studio-meta-slide .16s ease; }
@keyframes studio-meta-slide { from { opacity: 0; transform: translateY(-10px); } }
.studio-meta-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 14px 0; }
.studio-meta-hint { margin: 0; color: var(--muted); font-size: 12px; }

/* One grid for the whole panel, and every simple field dissolves into it.

   A field used to be its own little box of label-above-control, laid out in
   an auto-fit grid: with five keys of different name lengths that reads as
   five unrelated forms, and it costs two rows of height per value. Here the
   label sits in one column and the control in the next -- `display:
   contents` on the label is what puts them there, rather than nesting them
   in a box of their own -- so every label in the panel lines up and every
   input starts at the same x, whatever the keys are called.

   Only a value that genuinely needs several lines breaks the pattern, and
   it breaks it completely: full width, label above, no half-width textarea
   wedged into a column. `fit-content(150px)` lets the label column follow
   the longest key it holds and stop there, so one `heroBackgroundImageAlt`
   cannot push every input off the right-hand side.

   The second pair of columns appears when the panel is wide enough for two
   fields to sit side by side -- a container query, not a media query,
   because the editor's width is the side panel's, not the screen's. */
.studio-meta-fields { display: grid; grid-template-columns: fit-content(150px) minmax(130px, 1fr); gap: 7px 12px; align-items: center; padding: 11px 14px 14px; }
@container (min-width: 640px) {
  .studio-meta-fields { grid-template-columns: fit-content(150px) minmax(130px, 1fr) fit-content(150px) minmax(130px, 1fr); }
}
.studio-meta-field { display: contents; }
.studio-meta-fields > .wide { grid-column: 1 / -1; }
.studio-meta-field.wide { display: grid; gap: 4px; margin-top: 3px; }
.studio-meta-key { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--label); font-size: 11.5px; font-weight: 700; }
.studio-meta-fields input, .studio-meta-fields textarea { min-width: 0; min-height: 30px; padding: 4px 9px; border-radius: 7px; font-size: 13px; }
.studio-meta-fields textarea { min-height: 58px; }
.studio-meta-fields input[type="checkbox"] { width: 16px; height: 16px; min-height: 0; margin: 0; justify-self: start; }
.studio-meta-note { color: var(--muted); font-size: 10.5px; }
.studio-meta-locked { display: grid; gap: 3px; padding: 7px 10px; border: 1px dashed var(--line-strong); border-radius: 8px; background: var(--surface-muted); cursor: pointer; text-align: left; }
.studio-meta-locked:hover { border-color: var(--brand); background: var(--brand-soft); }
.studio-meta-preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font: 11.5px ui-monospace, SFMono-Regular, Menlo, monospace; }
@media (prefers-reduced-motion: reduce) {
  .studio-meta-panel { animation: none; }
  .studio-meta-chevron svg, .studio-node-dir > svg { transition: none; }
}
.studio-asset { padding: 20px; display: grid; gap: 18px; }
.studio-asset-stage { min-height: 260px; display: grid; place-items: center; overflow: hidden; border: 1px dashed var(--line-strong); border-radius: 11px; background: linear-gradient(45deg, var(--surface-sunken) 25%, transparent 25%), linear-gradient(-45deg, var(--surface-sunken) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--surface-sunken) 75%), linear-gradient(-45deg, transparent 75%, var(--surface-sunken) 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0; }
.studio-asset-stage img, .studio-asset-stage video { display: block; max-width: 100%; max-height: 430px; }
.studio-status { min-height: 37px; padding: 8px 12px; display: flex; justify-content: space-between; gap: 12px; border-top: 1px solid var(--line); color: var(--muted); font-size: 11px; }
.studio-shortcut { white-space: nowrap; }
.preview-sizes { display: flex; }
.preview-sizes .icon-button { width: 30px; height: 30px; display: grid; place-items: center; }
.preview-sizes .icon-button svg { display: block; }
.preview-sizes .active { color: var(--brand); background: var(--brand-soft); }
@keyframes toast-in { from { opacity: 0; transform: translateY(-8px); } }
@media (max-width: 900px) {
  .app-shell { grid-template-columns: 1fr; }
  .sidebar { position: fixed; left: 0; transform: translateX(-102%); width: min(var(--sidebar), 85vw); box-shadow: var(--shadow-lg); transition: transform .2s ease; }
  body.sidebar-open .sidebar { transform: translateX(0); }
  .mobile-menu { display: inline-grid; place-items: center; }
  .topbar { justify-content: flex-start; }
  .topbar-actions { margin-left: auto; }
  .split, .grid.two, .grid.three { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
  .content { padding-top: 24px; }
  .page-head { display: grid; }
  .page-head .actions .button { flex: 1; }
  .toolbar { align-items: stretch; flex-direction: column; }
  .search { max-width: none; }
  .form-grid { grid-template-columns: 1fr; }
  .card-head, .card-body, .site-card { padding-left: 16px; padding-right: 16px; }
  .topbar .button .optional-label { display: none; }
  .definition-list { grid-template-columns: 1fr; gap: 2px; }
  .definition-list dd { margin-bottom: 9px; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* --- chat ---------------------------------------------------------------- */
.chat-log { flex: 1; min-height: 0; overflow-y: auto; padding: 12px; display: flex;
  flex-direction: column; gap: 10px; background: var(--surface-muted); }
.chat-intro { color: var(--muted); font-size: 12px; }
.chat-intro p { margin: 0 0 6px; }
.chat-msg { display: flex; }
.chat-msg-user { justify-content: flex-end; }
.chat-msg-body { max-width: 82%; padding: 8px 12px; border-radius: 12px; font-size: 12.5px;
  line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.chat-msg-user .chat-msg-body { background: var(--brand); color: var(--on-brand); border-bottom-right-radius: 3px; }
.chat-msg-assistant .chat-msg-body { background: var(--surface); border: 1px solid var(--line); border-bottom-left-radius: 3px; }
.chat-system { font-size: 11px; color: var(--muted); padding-left: 2px; }
.chat-system-warn { color: var(--warning); }
.chat-system-error { color: var(--danger); }
.chat-system-diag { color: var(--muted); font-family: ui-monospace, monospace; font-size: 10.5px; }
/* Distinguishes "the model is generating right now" from the static,
   already-finished tool-call lines above it -- without this, a long silent
   stretch (thinking mode can run minutes) looks identical to a frozen last
   action ("searching the website...") that actually finished in milliseconds. */
.chat-system-thinking { animation: chat-thinking-pulse 1.6s ease-in-out infinite; }
@keyframes chat-thinking-pulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
.chat-reasoning { font-size: 11px; border: 1px dashed var(--line); border-radius: 8px; background: var(--surface); }
.chat-reasoning summary { cursor: pointer; padding: 5px 9px; color: var(--muted); }
.chat-reasoning-body { margin: 0; padding: 0 9px 8px; white-space: pre-wrap;
  font-family: ui-monospace, monospace; font-size: 10.5px; color: var(--muted); max-height: 220px; overflow: auto; }
.chat-actions { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; padding: 9px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface); }
.chat-actions-summary { flex-basis: 100%; margin: 0; font-size: 11.5px; color: var(--muted);
  overflow-wrap: anywhere; }
.chat-actions-done { font-size: 11px; color: var(--muted); }
/* Click-to-answer question options: stacked, full width and left-aligned so
   they read as a choice list, not a row of equal-weight command buttons. */
.chat-actions-options { display: flex; flex-direction: column; gap: 6px; flex-basis: 100%; }
.chat-actions-options .button { justify-content: flex-start; text-align: left; }
/* A proposal's own differences, inline in the transcript. Full width of
   the action card (which already wraps for its summary line), bounded in
   height, and one heading per file because a proposal may touch several. */
.chat-diff { flex-basis: 100%; max-height: 320px; overflow: auto; margin-top: 2px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--surface); font-size: 11px; }
.chat-diff-file { position: sticky; top: 0; z-index: 1; margin: 0; padding: 5px 9px;
  border-bottom: 1px solid var(--line); background: var(--surface-muted);
  color: var(--muted); font-size: 11px; font-weight: 650; overflow-wrap: anywhere; }
.chat-diff .diff { font-size: 11px; }
.chat-diff .review-note { padding: 8px 9px; font-size: 11.5px; }
.chat-stream { margin: 0; max-height: 260px; overflow: auto; background: var(--terminal); color: var(--terminal-ink);
  border-radius: 8px; padding: 9px; font-size: 10.5px; white-space: pre-wrap; }
.chat-context { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 10px 0; }
.chat-chip { display: inline-flex; align-items: center; gap: 6px; background: var(--brand-soft);
  border: 1px solid var(--brand-line); border-radius: 999px; padding: 3px 6px 3px 10px; font-size: 11px; max-width: 100%; }
.chat-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px; }
.chat-chip-drop { border: 0; background: transparent; cursor: pointer; color: var(--muted); font-size: 13px; line-height: 1; }
.chat-form { position: relative; display: flex; flex-direction: column; gap: 7px; padding: 10px;
  border-top: 1px solid var(--line); }

/* The command palette.
   Anchored to the form and opening upward, because the form sits at the
   bottom of the pane. A sheet rather than a list: a sticky filter field, a
   scrolling body of labelled groups, and a footer-less bottom edge -- so a
   long list reads as one object with a top, and not as a dropdown that ran
   out of room. */
.chat-commands-menu {
  position: absolute; z-index: 20; left: 10px; right: 10px; bottom: 100%;
  margin-bottom: 8px; overflow: hidden;
  display: flex; flex-direction: column; max-height: 62vh;
  border: 1px solid var(--line-strong); border-radius: 12px;
  background: var(--surface); box-shadow: var(--shadow-md);
}
.chat-commands-search { display: flex; align-items: center; gap: 7px;
  flex: 0 0 auto; padding: 9px 11px; border-bottom: 1px solid var(--line);
  color: var(--muted); background: var(--surface-muted); }
.chat-commands-search input { flex: 1; min-width: 0; border: 0; padding: 0;
  background: none; font: inherit; font-size: 12.5px; color: var(--ink); }
.chat-commands-search input:focus { outline: none; }
.chat-commands-search input::placeholder { color: var(--muted); }
/* Chrome draws its own clear button on a search field, which lands on top of
   the frame's own padding and reads as a second control. */
.chat-commands-search input::-webkit-search-cancel-button { display: none; }
.chat-commands-list { flex: 1 1 auto; overflow: auto; padding: 5px; }
.chat-commands-empty { margin: 0; padding: 12px 13px 15px; color: var(--muted); font-size: 12px; }

/* One group per kind of thing, because the flat list put a setting that
   changes how the agent thinks directly above a command that uploads a
   release. The heading is small and quiet: it orients, it is not read. */
/* The rule sits on every group and is taken off the first VISIBLE one from
   JavaScript -- `.group + .group` still matches across a hidden sibling, so
   filtering down to one group drew a divider above the top of the list. */
.chat-command-group { margin-top: 3px; padding-top: 4px; border-top: 1px solid var(--line); }
.chat-command-group-first { margin-top: 0; padding-top: 0; border-top: 0; }
.chat-command-group-title { margin: 0; padding: 5px 9px 3px;
  font-size: 9.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); }
.chat-command {
  width: 100%; display: flex; align-items: baseline; gap: 10px;
  padding: 5px 9px 6px; border: 0; border-radius: 8px; background: none;
  font: inherit; text-align: left; cursor: pointer; color: var(--ink);
}
.chat-command:hover, .chat-command:focus-visible { background: var(--brand-soft); outline: none; }
.chat-command-text { flex: 1; min-width: 0; display: flex; flex-direction: column;
  gap: 1px; }
.chat-command-label { font-size: 12.5px; font-weight: 550; color: var(--ink); }
/* The row's own sentence, not a tooltip: what the command does has to be
   readable without hovering, or the palette is a list of guesses again. */
.chat-command-hint { font-size: 10.5px; line-height: 1.4; color: var(--muted); }
/* The typed spelling, kept as a reminder on the right rather than as the
   label -- it teaches the shortcut without making the menu look like a
   terminal, which is what the old monospace-first list did. */
.chat-command-key { flex: 0 0 auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10.5px; color: var(--muted); }
.chat-command:hover .chat-command-key, .chat-command:focus-visible .chat-command-key {
  color: var(--brand-strong); }
/* The reference group, where the spelling IS the label. */
.chat-command-typed .chat-command-label { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px; font-weight: 500; color: var(--brand-strong); }

/* A switch inside the palette: it changes how the agent works rather than
   doing something once, so it reads as a setting and not as an action. */
.chat-switch { align-items: center; }
.chat-switch-knob { flex: 0 0 auto; position: relative; width: 30px; height: 17px;
  border-radius: 9px; background: var(--line-strong); transition: background .16s; }
.chat-switch-knob::after { content: ""; position: absolute; top: 2px; left: 2px;
  width: 13px; height: 13px; border-radius: 50%; background: var(--surface);
  transition: transform .16s; }
.chat-switch[aria-checked="true"] .chat-switch-knob { background: var(--brand); }
.chat-switch[aria-checked="true"] .chat-switch-knob::after { transform: translateX(13px); }
.chat-form textarea { width: 100%; resize: vertical; min-height: 82px; max-height: 240px; font: inherit;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 9px; }
.chat-form-actions { display: flex; gap: 6px; align-items: center; }
.chat-form-spacer { flex: 1; }
.chat-icon-button { width: 30px; height: 30px; display: grid; place-items: center;
  padding: 0; border: 1px solid var(--line); border-radius: 8px; cursor: pointer;
  color: var(--muted); background: var(--surface); }
.chat-icon-button:hover { color: var(--ink); border-color: var(--line-strong); background: var(--surface-muted); }
.chat-icon-button svg { display: block; }
.chat-icon-button[aria-pressed="true"] { color: var(--brand); border-color: var(--brand); background: var(--brand-soft); }
.chat-icon-button[aria-pressed="true"] svg { fill: currentColor; }
.chat-icon-button.chat-send { color: var(--on-brand); border-color: transparent; background: var(--brand); }
.chat-icon-button.chat-send:hover { background: var(--brand-strong); }
.chat-icon-button:disabled { opacity: .45; cursor: default; }
.chat-icon-button.chat-send:disabled:hover { background: var(--brand); }
.chat-notice { margin: 10px; }
.chat-egress { margin: 0; padding: 0 10px 9px; font-size: 10px; color: var(--muted); }

/* Budget gauge, in the button row beside the slash menu. A ring rather than
   a bar because it sits among icons and has to read at 17px; the numbers
   live in its `title`, which needs no positioning and reaches a screen
   reader. Hidden until a request runs -- an idle composer stays uncluttered. */
.chat-meter { position: relative; display: inline-flex; align-items: center;
  justify-content: center; width: 28px; height: 28px; cursor: help; }
/* Its own element, not a `title`. A native tooltip is torn down and rebuilt
   on every attribute write, and this one is rewritten ~25 times per
   completion -- which flickered badly while hovered. This node's text is
   replaced in place, so an open tooltip simply updates. */
.chat-meter-tip { position: absolute; bottom: calc(100% + 6px); right: 0; z-index: 30;
  padding: 6px 9px; border-radius: 8px; white-space: pre; font-size: 11px;
  line-height: 1.45; font-variant-numeric: tabular-nums; color: var(--tooltip-ink);
  background: var(--tooltip); box-shadow: var(--shadow); pointer-events: none; }
.chat-meter-track { stroke: var(--line); }
.chat-meter-ring { stroke: var(--brand); transition: stroke-dashoffset .3s ease; }
.chat-meter[data-level="warn"] .chat-meter-ring { stroke: var(--warning); }
.chat-meter[data-level="full"] .chat-meter-ring { stroke: var(--danger); }
.chat-meter[data-running="1"] .chat-meter-ring { animation: chat-thinking-pulse 1.6s ease-in-out infinite; }

.chat-help { display: grid; gap: 4px; padding: 8px 10px; border: 1px solid var(--line);
  border-radius: 9px; background: var(--surface); font-size: 11.5px; }
.chat-help-row { display: flex; gap: 10px; align-items: baseline; }
.chat-help-cmd { font-family: ui-monospace, monospace; color: var(--brand); min-width: 118px; }
.chat-help-text { color: var(--muted); }
.chat-system-hint { color: var(--muted); }

/* --- chat: attachments, history ------------------------------------------ */
.chat-attachments { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 10px 0; }
.chat-file { display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 3px 5px 3px 4px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface);
  font-size: 11px; }
.chat-file-thumb { width: 26px; height: 26px; object-fit: cover; border-radius: 5px; display: block; }
.chat-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 150px; }
.chat-file-progress { color: var(--muted); font-variant-numeric: tabular-nums; }
.chat-file-error { color: var(--danger); }
.chat-file-uploading { border-style: dashed; }
.chat-file-error { border-color: var(--danger); }
.chat-file-drop { border: 0; background: transparent; cursor: pointer; color: var(--muted);
  font-size: 13px; line-height: 1; padding: 0 2px; }
.chat-file-drop:disabled { opacity: .35; cursor: default; }
.chat-dropping { outline: 2px dashed var(--brand); outline-offset: -6px; }
.chat-dropping .chat-log { background: var(--brand-soft); }

.chat-threads { display: grid; gap: 5px; max-height: min(60vh, 460px); overflow-y: auto; }
.chat-thread { display: grid; gap: 2px; text-align: left; padding: 8px 10px; cursor: pointer;
  border: 1px solid var(--line); border-radius: 9px; background: var(--surface); font: inherit; }
.chat-thread:hover { border-color: var(--line-strong); background: var(--surface-muted); }
.chat-thread.active { border-color: var(--brand); background: var(--brand-soft); }
.chat-thread-title { font-weight: 650; font-size: 12.5px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.chat-thread-meta { color: var(--muted); font-size: 11px; }


/* Profile page.

   The language is a radio group here, not the segmented `DE | EN` control:
   it is saved together with the name and the email on submit, so it has to
   look like the other fields on the form rather than like a switch that
   acts on click. The extra "follow my browser" option is a real setting --
   it clears the stored preference and the cookie with it. */
/* Two columns while there is room for them: the identity fields and the
   language are independent groups, and stacking them left a very narrow
   column of form on a very wide page. `.grid.two` folds to one column
   under 900px on its own, so nothing extra is needed for small screens.
   `align-items: start` rather than the default stretch -- the language
   card is the shorter of the two and a card padded out with empty space
   to match its neighbour reads as something failing to load. */
.profile-page { max-width: 1040px; }
.profile-page .grid.two { align-items: start; }
.profile-page .card-head { display: block; }
.profile-page .card-head .help { margin: 4px 0 0; }
.lang-choice { border: 0; padding: 0; margin: 0; }
.lang-choice legend { padding: 0; font-size: 13px; font-weight: 700; color: var(--label); }
.lang-choice-option { display: flex; align-items: center; gap: 9px; padding: 9px 11px; border: 1px solid var(--line-strong); border-radius: 8px; cursor: pointer; }
.lang-choice-option:hover { background: var(--surface-muted); }
.lang-choice-option input { width: auto; min-height: 0; margin: 0; }
.lang-choice-option:has(input:checked) { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); }
.profile-error-detail { margin: 8px 0 0; white-space: pre-wrap; font-size: 12px; }
.profile-page .grid.two + .grid.two { margin-top: 18px; }

/* Which connection a site uses: its own, or a person's profile default.
   Boxed rather than a bare radio pair, because the choice decides what the
   fields beneath it mean -- under "profile default" they are read-only and
   describe somebody else's stored connection. */
.source-choice { display: grid; gap: 8px; padding: 12px 13px; margin: 0; border: 1px solid var(--line-strong); border-radius: 10px; background: var(--surface-muted); }
.source-choice legend { padding: 0 5px; font-size: 13px; font-weight: 700; color: var(--label); }
.source-choice .checkbox-line { display: flex; align-items: center; gap: 9px; cursor: pointer; }
.source-choice .help { margin: 0; }
[data-connection-fields] input:disabled { color: var(--muted); background: var(--surface-muted); cursor: not-allowed; }

/* Build progress (static/build-progress.js).
   The chain of six steps, a bar and a clock. Lives in the preview stage
   (.job-overlay in workspace.css), which is why it may spread out: a build
   and a preview are never both worth watching at the same moment. */
.build-progress { display: grid; gap: 14px; }
.build-steps { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 0; margin: 0; padding: 0; list-style: none; }
/* Each step is its own three-row stack (marker, name, time) so the labels
   line up across steps no matter how long any one of them is. */
.build-step { position: relative; display: grid; justify-items: center; gap: 5px; padding: 0 4px; text-align: center; }
/* The connector is drawn by each step except the first, from its own left
   edge to its marker -- so it always reaches, whatever the column width. */
.build-step + .build-step::before {
  content: ""; position: absolute; top: 13px; right: 50%; left: -50%;
  height: 2px; background: var(--line); z-index: 0;
}
.build-step[data-state="done"]::before, .build-step[data-state="active"]::before { background: var(--brand-soft); }
.build-step-dot {
  position: relative; z-index: 1; display: grid; place-items: center;
  width: 26px; height: 26px; border-radius: 50%;
  border: 2px solid var(--line); background: var(--surface);
  color: var(--muted); font-size: 12px; font-weight: 700;
}
.build-step-label { color: var(--muted); font-size: 12px; line-height: 1.25; }
.build-step-time { color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; opacity: .75; }
.build-step[data-state="done"] .build-step-dot { border-color: var(--success); background: var(--success-soft); color: var(--success); }
.build-step[data-state="done"] .build-step-label { color: var(--ink); }
.build-step[data-state="active"] .build-step-dot { border-color: var(--brand); background: var(--brand); color: var(--on-brand); }
.build-step[data-state="active"] .build-step-label { color: var(--brand-strong); font-weight: 650; }
.build-step[data-state="active"] .build-step-time { color: var(--brand-strong); opacity: 1; }
.build-step[data-state="failed"] .build-step-dot { border-color: var(--danger); background: var(--danger-soft); color: var(--danger); }
.build-step[data-state="failed"] .build-step-label { color: var(--danger); font-weight: 650; }
.build-headline { margin: 0; text-align: center; font-size: 15px; font-weight: 650; color: var(--ink); }
.build-track { height: 6px; border-radius: 999px; background: var(--line); overflow: hidden; }
.build-track span { display: block; height: 100%; border-radius: inherit; background: var(--brand); transition: width .4s ease; }
.build-footnote { margin: 0; text-align: center; color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }

@media (max-width: 760px) {
  /* Under a certain width six columns stop being readable at all; one
     column per step, stacked, keeps every label whole. */
  .build-steps { grid-auto-flow: row; grid-auto-columns: auto; gap: 8px; }
  .build-step { grid-template-columns: 26px minmax(0, 1fr) auto; justify-items: start; align-items: center; text-align: left; }
  .build-step + .build-step::before { top: -5px; right: auto; left: 12px; width: 2px; height: 10px; }
}
