/* Work Sans, self-hosted. Deliberately NOT loaded from fonts.googleapis.com:
   AI:U is sold as a sovereign, on-prem gateway, and a Google-hosted font would
   send every visitor's IP to a third country on every page load. The files are
   the variable font (weight 100-900 in one file), so the 750 weight used below
   renders properly instead of being synthesised. SIL OFL 1.1, see fonts/OFL.txt.
   font-display:swap keeps text visible while the file loads. */
@font-face{
  font-family:"Work Sans";
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url("/static/fonts/work-sans-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:"Work Sans";
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url("/static/fonts/work-sans-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
/* Type scale (design direction, 2026-08-21): headings 700, body 400, with 500
   and 600 for intermediate emphasis. 700 is the ceiling — nothing outruns a
   page title. The old 800 and 750 weights were flattened into 700 to make that
   hold. If 700 reads too heavy for headings, drop .page-title/h2/h3 to 600. */
:root{
  --font:"Work Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  /* LIGHT is the default, regardless of OS (owner requirement: readable by day). */
  --bg:#f6f6f7; --panel:#ffffff; --panel-2:#eef0f2; --border:#d8dade;
  --text:#18181b; --muted:#4b4d52; --red:#e10600; --red-dim:#b00500;
  --topbar-bg:#ffffff; --th-bg:#f0f0f3;
  --code-bg:#000; --code-fg:#e6e6e6;   /* code/terminal blocks stay dark in both themes */
  /* Sidebar rail — dark in both themes (brand black/red/white) */
  --side-w:248px;
  --side-bg:#111114; --side-text:#e7e7ea; --side-muted:#8b8b93;
  --side-border:#232327; --side-hover-bg:rgba(255,255,255,.06);
  --side-active-bg:rgba(225,6,0,.16);
  --shadow-sm:0 1px 2px rgba(16,17,20,.05),0 1px 3px rgba(16,17,20,.07);
  --shadow-md:0 6px 16px rgba(16,17,20,.10),0 2px 6px rgba(16,17,20,.06);
  --radius:10px; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
}
:root[data-theme="dark"]{
  --bg:#0a0a0b; --panel:#141417; --panel-2:#1c1c21; --border:#2a2a30;
  --text:#f4f4f5; --muted:#a6a6ae;
  --topbar-bg:#000; --th-bg:#000;
  --side-bg:#000; --side-text:#f4f4f5; --side-muted:#9b9ba3;
  --side-border:#1c1c21; --side-hover-bg:rgba(255,255,255,.05);
  --side-active-bg:rgba(225,6,0,.20);
  --shadow-sm:0 1px 2px rgba(0,0,0,.45);
  --shadow-md:0 10px 28px rgba(0,0,0,.6);
}
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{
  background:var(--bg);color:var(--text);
  font-family:var(--font);
  font-size:15px;line-height:1.55;letter-spacing:-.006em;
  -webkit-font-smoothing:antialiased;
}
a{color:inherit;text-decoration:none}

/* Custom scrollbars — thin, subtle, theme-aware (every overflow container) */
*{scrollbar-width:thin;scrollbar-color:var(--border) transparent}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--border);border-radius:6px;border:2px solid transparent;background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background:var(--muted);background-clip:padding-box}
.side-nav{scrollbar-color:var(--side-border) transparent}
.side-nav::-webkit-scrollbar-thumb{background:var(--side-border);background-clip:padding-box}
.side-nav::-webkit-scrollbar-thumb:hover{background:var(--side-muted);background-clip:padding-box}

/* Topbar */
.topbar{
  display:flex;align-items:center;gap:24px;padding:12px 24px;
  background:var(--topbar-bg);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:10;
}
.brand{display:flex;align-items:center;gap:10px}
.brand-logo{height:26px;width:auto;display:block}
.brand-mark{font-weight:700;font-size:20px;letter-spacing:.5px}
.accent{color:var(--red)}
.nav{display:flex;gap:18px;flex:1;align-items:center}
.nav a{color:var(--muted);font-weight:500;padding:6px 0;border-bottom:2px solid transparent;display:inline-flex;align-items:center;gap:7px}
.nav a:hover{color:var(--text)}
.nav-admin{color:var(--red)!important}
/* Vendored inline SVG icons (sprite in /static/img/icons.svg). */
.ico{width:17px;height:17px;flex:0 0 auto;stroke:currentColor;display:inline-block;vertical-align:-3px}
.nav .ico{opacity:.85}
.nav a:hover .ico,.navdrop[open]>summary .ico{opacity:1}
/* "Use It" nav dropdown (built on <details>) */
.navdrop{position:relative}
.navdrop>summary{list-style:none;cursor:pointer;color:var(--muted);font-weight:500;padding:6px 0;border-bottom:2px solid transparent;display:flex;align-items:center;gap:7px;user-select:none}
.navdrop>summary::-webkit-details-marker{display:none}
.navdrop>summary:hover,.navdrop[open]>summary{color:var(--text)}
.navdrop>summary:focus-visible{outline:2px solid var(--red);outline-offset:3px;border-radius:4px}
.navdrop-caret{width:14px;height:14px;transition:transform .15s;opacity:.85}
.navdrop[open] .navdrop-caret{transform:rotate(180deg)}
/* Panel sits directly below the trigger (top:100%) with NO margin gap, so the
   hover target is continuous. The visual 8px gap is recreated with a
   transparent top border-spacing via the wrapper padding below. */
.navdrop-panel{position:absolute;top:100%;left:0;margin-top:0;padding-top:14px;min-width:210px;display:flex;flex-direction:column;gap:2px;z-index:20}
/* Inner surface (the visible card); the 8px offset lives in the wrapper's
   padding-top so the bridge area between trigger and card is still hoverable. */
.navdrop-panel-inner{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);padding:6px;display:flex;flex-direction:column;gap:2px;box-shadow:0 10px 30px rgba(0,0,0,.5)}
.navdrop-panel a{display:flex;align-items:center;gap:9px;color:var(--muted);font-weight:500;padding:8px 12px;border:none;border-radius:7px;white-space:nowrap}
.navdrop-panel a:hover,.navdrop-panel a:focus-visible{color:var(--text);background:var(--panel-2);outline:none}
.navdrop-panel a:hover .ico{opacity:1}
.navdrop-group{display:flex;flex-direction:column;gap:2px;border-top:1px solid var(--border);margin-top:4px;padding-top:4px}
.navdrop-head{display:flex;align-items:center;gap:7px;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--red);font-weight:700;padding:6px 12px 2px}
.navdrop-head .ico{width:14px;height:14px}
.navdrop-group a{padding-left:22px}
@media(max-width:760px){
  .navdrop-panel{position:static;padding-top:0;margin-top:4px;min-width:0}
  .navdrop-panel-inner{box-shadow:none;background:var(--panel-2)}
}
.user-box{display:flex;align-items:center;gap:12px}
.user-name{font-weight:600}
.tenant-chip{background:var(--panel-2);border:1px solid var(--border);border-radius:999px;padding:2px 10px;font-size:12px;color:var(--muted)}
/* ---- Left sidebar rail (replaces the old top nav; old .topbar/.nav/.navdrop
   rules above are now unused but harmless) ---- */
.sidebar{
  position:fixed;top:0;left:0;z-index:30;
  width:var(--side-w);height:100vh;
  display:flex;flex-direction:column;
  background:var(--side-bg);color:var(--side-text);
  border-right:1px solid var(--side-border);
}
body.has-side{padding-left:var(--side-w)}
.side-brand{display:flex;align-items:center;gap:10px;padding:22px 20px 14px;color:var(--side-text)}
/* The wordmark carries the name itself, so the adjacent "AI:U" text was dropped
   and the logo grew to fill the rail. The rail is dark in BOTH themes, so this
   one is always the white variant. */
.side-brand .brand-logo{height:30px;width:auto;display:block}
.side-brand .brand-mark{font-weight:700;font-size:20px;letter-spacing:-.01em}
.side-nav{flex:1;min-height:0;overflow-y:auto;padding:6px 12px 14px;display:flex;flex-direction:column;gap:1px}
.side-group{margin-top:16px}
.side-group-head{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.7px;color:var(--side-muted);padding:4px 12px 6px}
.side-link{position:relative;display:flex;align-items:center;gap:11px;padding:9px 12px;border-radius:8px;color:var(--side-muted);font-weight:500;font-size:14px;line-height:1.2;transition:background .12s,color .12s}
.side-link .ico{width:18px;height:18px;flex:0 0 auto;opacity:.9;stroke:currentColor;display:inline-block}
.side-link:hover{background:var(--side-hover-bg);color:var(--side-text)}
.side-link:focus-visible{outline:2px solid var(--red);outline-offset:2px}
.side-link.active{background:var(--side-active-bg);color:#fff;font-weight:600}
.side-link.active .ico{opacity:1;stroke:var(--red)}
.side-link.active::before{content:"";position:absolute;left:0;top:7px;bottom:7px;width:3px;border-radius:0 3px 3px 0;background:var(--red)}
.side-admin{color:#ff8480}
.side-admin:hover{color:#ffb3b0}
.side-admin.active{color:#fff}
.side-foot{border-top:1px solid var(--side-border);padding:12px;display:flex;flex-direction:column;gap:10px}
.side-tools{display:flex;align-items:center;justify-content:space-between;gap:8px}
.side-user{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:0 4px}
.side-user .user-name{font-weight:600;color:var(--side-text);font-size:13px}
.side-foot .tenant-chip{background:rgba(255,255,255,.08);border:1px solid var(--side-border);color:var(--side-muted);border-radius:999px;padding:2px 10px;font-size:12px}
.side-actions{display:flex;gap:8px}
.side-actions .btn{flex:1;justify-content:center;padding:7px 10px;font-size:13px;background:transparent;border-color:var(--side-border);color:var(--side-text)}
.side-actions .btn:hover{border-color:var(--red)}
.theme-toggle{display:inline-flex;align-items:center;justify-content:center;padding:6px 10px;color:var(--side-text)}
/* Beat the base .btn color (var(--text), dark) so the icon is visible on the dark rail. */
.side-tools .theme-toggle{color:var(--side-text);border-color:var(--side-border)}
.side-tools .theme-toggle:hover{border-color:var(--red);color:#fff}
.theme-toggle .ico{width:18px;height:18px}
.theme-toggle .ico-sun{display:none}
:root[data-theme="dark"] .theme-toggle .ico-moon{display:none}
:root[data-theme="dark"] .theme-toggle .ico-sun{display:inline-block}
.side-toggle{display:none}
.side-backdrop{display:none}
@media(max-width:900px){
  body.has-side{padding-left:0}
  .sidebar{transform:translateX(-100%);transition:transform .2s ease;box-shadow:var(--shadow-md)}
  body.nav-open .sidebar{transform:none}
  .side-backdrop{display:block;position:fixed;inset:0;z-index:29;background:rgba(0,0,0,.5);opacity:0;pointer-events:none;transition:opacity .2s}
  body.nav-open .side-backdrop{opacity:1;pointer-events:auto}
  .side-toggle{display:inline-flex;align-items:center;justify-content:center;position:fixed;top:12px;left:12px;z-index:31;width:44px;height:44px;border-radius:10px;background:var(--side-bg);color:var(--side-text);border:1px solid var(--side-border);box-shadow:var(--shadow-md);cursor:pointer}
  .side-toggle .ico{width:20px;height:20px;stroke:currentColor}
  .content{padding-top:68px}
}
@media(prefers-reduced-motion:reduce){
  .sidebar,.side-backdrop,.side-link,.tile{transition:none}
}

/* Layout */
.content{max-width:1400px;margin:0 auto;padding:40px 40px}
/* Reporting pages opt out of the prose measure. 1100px is right for reading and
   wrong for a ten-column table: on a 1600px screen it left ~250px of the window
   empty while the widest tables scrolled inside themselves. Still a max, so
   narrow windows are unaffected. */
.content.wide{max-width:1720px}
.page-title{font-size:32px;font-weight:700;letter-spacing:-.021em;line-height:1.15;margin:0 0 6px}
.muted{color:var(--muted)}
h2{font-size:19px;font-weight:700;letter-spacing:-.011em;margin:34px 0 10px}
h3{margin-top:26px;border-left:4px solid var(--red);padding-left:12px;font-size:15px;font-weight:700;line-height:1.3}

/* Buttons */
/* Buttons do not inherit font-family from body the way other elements do, so
   without this they keep the browser's system font while the rest of the page
   is Work Sans. */
button{font-family:inherit}
.btn{cursor:pointer;border:1px solid var(--border);background:var(--panel-2);color:var(--text);
  padding:8px 16px;border-radius:8px;font-weight:600;font-size:14px;transition:.12s}
.btn:hover{border-color:var(--red)}
.btn-primary{background:var(--red);border-color:var(--red);color:#fff}
.btn-primary:hover{background:var(--red-dim)}
.btn-danger{background:transparent;border-color:var(--red);color:var(--red);padding:5px 12px}
.btn-danger:hover{background:var(--red);color:#fff}
.btn-ghost{background:transparent}
.btn-block{width:100%}

/* Cards / tiles */
.card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:18px;margin-top:24px}
.tile{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);padding:22px;box-shadow:var(--shadow-sm);transition:transform .15s,box-shadow .15s,border-color .15s}
.tile:hover{border-color:var(--red);transform:translateY(-3px);box-shadow:var(--shadow-md)}
.tile:hover h3{color:var(--red)}
.tile h3{border:0;padding:0;margin:0 0 6px;font-size:18px}
.tile p{margin:0;color:var(--muted);font-size:14px}
.tile-admin{border-color:var(--red-dim)}

/* Tables */
.data-table{width:100%;border-collapse:collapse;margin-top:12px;background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow-sm)}
.data-table th{text-align:left;padding:10px 14px;background:var(--th-bg);color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.4px}
.data-table td{padding:10px 14px;border-top:1px solid var(--border)}
.mono{font-family:var(--mono);font-size:13px}

/* Forms */
.inline-form{display:flex;gap:10px;margin:18px 0}
.inline-form input{flex:1;max-width:360px}
/* A form that must not break the row it sits in. `class="inline"` was used in
   markup on /admin/plans and defined NOWHERE, so the form stayed a block: the
   Edit link and the Archive button ended up on different lines at different
   heights. That is the misalignment. */
.inline{display:inline}
/* Row actions on one line, same height, same baseline, whatever mix of <a> and
   <button> and <form> they are made of. */
.row-actions{display:flex;align-items:center;gap:8px;flex-wrap:nowrap;justify-content:flex-end}
.row-actions form{display:inline-flex;margin:0}
.row-actions .btn{white-space:nowrap}
/* A numeric column: right-aligned, and its input sized to the number rather
   than stretching to the cell. */
td.num,th.num{text-align:right}
td.num .input-sm{width:80px;text-align:right;display:inline-block}
/* One tier of a package's volume pricing, shown inline on the allocation table. */
.tier-chip{display:inline-block;padding:1px 7px;margin:0 4px 2px 0;border-radius:10px;
  background:var(--panel-2);border:1px solid var(--border);font-size:11px;white-space:nowrap}
/* Several tiers in one cell: right-aligned so the prices line up down the column,
   and allowed to wrap to a second line rather than widening the table. */
.alloc-table td:last-child{text-align:right;max-width:340px}
/* The tier editor: rows of "from N seats -> price", added and removed freely. */
.tier-row{display:flex;align-items:center;gap:8px;margin-bottom:8px}
.tier-row input{width:110px}
.tier-row .tier-lbl{font-size:12px;color:var(--muted);min-width:74px}
.alloc-table td{vertical-align:middle}
/* An actions column sizes to its buttons instead of being squeezed by the prose
   columns beside it. Without this the last button clipped at the table edge. */
.col-actions{width:1%;white-space:nowrap}
/* The package table carries one long prose column ("what happens when the
   allowance runs out") which otherwise takes the width the action buttons need,
   clipping the last button. Cap the prose and let it wrap instead. */
.pack-table td:nth-child(4){max-width:280px;white-space:normal}
.pack-table .col-actions{padding-left:14px}
.tenant-budget-form{align-items:flex-end}
.tenant-budget-form .field-lbl{flex:0 1 260px}
.tenant-budget-form .form-actions{margin-top:0;flex:0 0 auto}
.stack-form{display:flex;flex-direction:column;gap:10px}
input,select,textarea{
  background:var(--panel-2);border:1px solid var(--border);color:var(--text);
  padding:9px 12px;border-radius:8px;font-size:14px;font-family:inherit;
}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--red)}
.check{display:flex;align-items:center;gap:8px;color:var(--muted)}

/* Alerts */
.alert{background:rgba(225,6,0,.12);border:1px solid var(--red);color:#ffb3b0;padding:12px 14px;border-radius:8px;margin:14px 0}
.secret-box{background:var(--panel);border:1px dashed var(--red);border-radius:var(--radius);padding:16px;margin:14px 0;display:flex;flex-direction:column;gap:8px}
.secret-box code{font-family:var(--mono);background:var(--code-bg);color:var(--code-fg);padding:10px;border-radius:6px;word-break:break-all}

/* Stats / bars */
.stat-row{display:flex;gap:16px;margin:20px 0;flex-wrap:wrap}
.stat{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);padding:16px 22px;min-width:160px;box-shadow:var(--shadow-sm)}
.stat-label{display:block;color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.4px}
.stat-value{font-size:26px;font-weight:700}
.bars{display:flex;flex-direction:column;gap:8px;margin-top:12px}
.bar-row{display:grid;grid-template-columns:220px 1fr 90px;align-items:center;gap:12px}
.bar-label{color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bar-track{background:var(--panel-2);border-radius:6px;height:18px;overflow:hidden}
.bar-fill{display:block;height:100%;background:linear-gradient(90deg,var(--red-dim),var(--red))}
.bar-val{text-align:right;font-family:var(--mono);font-size:13px}
.mini-budget{display:flex;align-items:center;gap:8px}
.mini-budget .bar-track{flex:1;min-width:80px}
/* A second line inside a seat cell: the euros under the meter bar, the window
   under the allowance, the "as of" under the reset date. Quieter than the value
   it explains, and on its own line so the column stays scannable. Declared after
   .mono so a .seat-figure.mono keeps the monospace face at this smaller size. */
.seat-figure{display:block;font-size:12px;color:var(--muted);margin-top:2px}
.bar-track.mini{height:10px}
/* Cost-management bars carry a longer "spend / budget (pct)" value. */
.bar-row.wide{grid-template-columns:120px 1fr 220px}
.input-sm{width:140px;padding:4px 8px}
.check-inline{display:inline-flex;align-items:center;gap:8px;cursor:pointer}
.data-table.compact td{padding:6px 10px}
/* A model's individual endpoints, listed under the model they belong to. */
.health-endpoint td:first-child{padding-left:34px}

/* Admin */
.admin-cols{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:18px}
.panel{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);padding:18px}
.panel h3{margin-top:0}

/* Admin models page (redesigned) */
.models-add .field-row{margin:0}
.models-add .field-row input,.models-add .field-row .check{flex:1;min-width:160px}
.models-head{display:flex;align-items:center;justify-content:space-between;margin-top:30px}
.model-group{margin:14px 0 26px}
.model-group-head{display:flex;align-items:center;gap:10px;margin:0 0 12px}
.model-group-title{font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--red)}
.model-group-count{background:var(--panel-2);border:1px solid var(--border);border-radius:999px;padding:1px 9px;font-size:12px;color:var(--muted);font-weight:600}
.model-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:14px}
.model-card{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);padding:16px;display:flex;flex-direction:column;gap:12px}
.model-card:hover{border-color:#3a3a42}
.model-card-top{display:flex;align-items:center;justify-content:space-between;gap:10px}
.model-name{font-size:15px;font-weight:700;color:var(--text);word-break:normal;overflow-wrap:anywhere}
.model-meta{margin:0;display:flex;flex-direction:column;gap:8px}
.model-meta>div{display:grid;grid-template-columns:84px 1fr;gap:8px;align-items:baseline}
.model-meta dt{margin:0;font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:var(--muted)}
.model-meta dd{margin:0;font-size:13px;word-break:normal;overflow-wrap:anywhere}
.model-apibase{color:var(--text)}
.price-pill{display:inline-block;background:var(--panel-2);border:1px solid var(--border);border-radius:6px;padding:2px 8px;font-size:12px;margin:0 4px 4px 0}
.model-card-actions{display:flex;gap:8px;align-items:flex-start;flex-wrap:wrap;border-top:1px solid var(--border);padding-top:12px;margin-top:auto}
.model-editor{flex:1 1 100%}
.model-editor>summary{display:inline-flex;width:auto;list-style:none}
.model-editor>summary::-webkit-details-marker{display:none}
.model-editor[open]>summary{margin-bottom:10px}
.model-editor .field-lbl{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--muted)}
.model-editor .field-lbl input{width:100%}
.model-editor .field-row{margin:0;gap:10px}
.model-editor .field-row .field-lbl{flex:1;min-width:120px}
/* Endpoints of one model (LiteLLM model group). Only rendered when there is
   more than one, so a single-backend model keeps the card it always had. */
.model-instances{width:100%;border-collapse:collapse;font-size:12px;
  background:var(--panel-2);border:1px solid var(--border);border-radius:8px}
.model-instances th{text-align:left;font-size:11px;text-transform:uppercase;
  letter-spacing:.4px;color:var(--muted);font-weight:600;padding:7px 10px;
  border-bottom:1px solid var(--border)}
.model-instances td{padding:7px 10px;border-bottom:1px solid var(--border);vertical-align:middle}
.model-instances tr:last-child td{border-bottom:0}
.model-instances td:first-child{word-break:normal;overflow-wrap:anywhere}
.model-instances .row-form{gap:6px;flex-wrap:nowrap}
/* The row's fields sit in their own cells and reach the row's form by id: a
   <form> cannot span table cells, and a save button per field would let the
   backend model string be stored without the cap that depends on it. */
.model-instances td > input{width:76px}
.model-instances td > input[name='backend_model']{width:180px}
.model-instances td:last-child{text-align:right}
/* Seven columns on a 320px card grid track: the table scrolls, the card does
   not. */
.model-instances-wrap{overflow-x:auto}
.tag-lock{background:rgba(155,155,163,.15);color:var(--muted);border:1px solid var(--border)}
.tag-paused{background:rgba(245,158,11,.15);color:#fbbf24;border:1px solid rgba(245,158,11,.4)}
.tag-neutral{background:rgba(155,155,163,.12);color:var(--muted);border:1px solid var(--border)}

/* Admin sub-nav */
.subnav{display:flex;gap:8px;margin:6px 0 22px;flex-wrap:wrap;border-bottom:1px solid var(--border)}
.subnav a{flex:0 0 auto;white-space:nowrap}
.subnav a{color:var(--muted);font-weight:600;padding:8px 14px;border-bottom:2px solid transparent;margin-bottom:-1px}
.subnav a:hover{color:var(--text)}
.subnav a.active{color:var(--text);border-bottom-color:var(--red)}

/* Inline action buttons */
.actions{display:flex;gap:6px;flex-wrap:wrap}
.actions form{display:inline}
.btn-xs{padding:4px 10px;font-size:12px;white-space:nowrap}
.tag{display:inline-block;border-radius:999px;padding:2px 9px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.4px}
.tag-ok{background:rgba(40,180,90,.15);color:#5fd08a;border:1px solid #2f7a4d}
.tag-off{background:rgba(225,6,0,.15);color:#ff9b98;border:1px solid var(--red-dim)}
.tag-admin{background:rgba(225,6,0,.18);color:#ffb3b0;border:1px solid var(--red)}
/* Light theme: the pastel tag text (tuned for dark) is unreadable on white -> darken */
:root:not([data-theme="dark"]) .tag-ok{color:#1a7f43}
:root:not([data-theme="dark"]) .tag-off{color:#c11c17}
:root:not([data-theme="dark"]) .tag-admin{color:#b00500}
:root:not([data-theme="dark"]) .tag-paused{color:#8a5a00}
:root:not([data-theme="dark"]) .tag-creating{color:#c11c17}
/* Model-name chips (getting-started, catalog) — legible in both themes, real model IDs (no uppercase) */
.model-chips{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0 0;padding:0}
.model-chip{display:inline-flex;align-items:center;gap:7px;background:var(--panel);border:1px solid var(--border);color:var(--text);border-radius:7px;padding:4px 10px;font-family:var(--mono);font-size:12.5px;font-weight:500;line-height:1.4;text-transform:none;letter-spacing:0;white-space:nowrap;box-shadow:var(--shadow-sm)}
.model-chip::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--red);flex:0 0 auto}
.model-chip:hover{border-color:var(--red)}

/* Charts */
.chart{width:100%;height:auto;display:block;background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);padding:8px}
.chart-grid{display:grid;grid-template-columns:1.4fr 1fr;gap:16px;align-items:start;margin-top:12px}
@media(max-width:820px){.chart-grid{grid-template-columns:1fr}.admin-cols{grid-template-columns:1fr}}

/* Notice banners */
.notice{background:var(--panel);border:1px solid var(--red-dim);border-left:3px solid var(--red);border-radius:8px;padding:12px 14px;margin:14px 0}
.notice strong{color:#ffb3b0}
.ok-msg{background:rgba(40,180,90,.1);border:1px solid #2f7a4d;color:#9fe3b8;padding:10px 14px;border-radius:8px;margin:14px 0}

/* edit row (details/summary) */
details.editor{margin-top:6px}
/* Add-endpoint extras. Folded away, because the common case is a second
   identical box and that needs one api_base. */
.instance-advanced{margin:10px 0}
.instance-advanced .check-inline{margin:8px 0 4px;font-size:13px}
.instance-advanced input:disabled{opacity:.5}
details.editor>summary{cursor:pointer;color:var(--muted);font-size:13px;list-style:none}
/* a <summary> styled as a button must shrink to its text + center it (it would
   otherwise inherit display:list-item → full-width box, left-aligned label) */
summary.btn{display:inline-block;text-align:center;list-style:none}
summary.btn::-webkit-details-marker{display:none}
summary.btn::marker{content:""}
details.editor>summary:hover{color:var(--text)}
details.editor .stack-form{margin-top:10px;background:var(--panel-2);padding:12px;border-radius:8px}

/* Table row actions — tidy, right-aligned, uniform sizing */
.data-table th:last-child,.data-table td:last-child{text-align:right}
/* A cell that spans the table is a message about the whole table ("nothing here
   yet"), not a value in the last column. Without this it inherits the rule above
   and every empty state in the portal sits hard against the right edge, reading
   like a number somebody forgot to label. */
.data-table td[colspan],.data-table th[colspan]{text-align:left}
.data-table .actions{justify-content:flex-end;align-items:center;gap:6px;flex-wrap:nowrap}
.data-table .actions .btn-xs{white-space:nowrap}
@media (max-width:720px){.data-table .actions{flex-wrap:wrap}}

/* A heading with a right-aligned action next to it (e.g. a section CSV link) */
.section-head{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;margin-top:26px}
.section-head h3{margin:0}

/* Per-day usage table: one <tbody> per day, the day total first and its
   breakdown lines indented underneath. Numbers right-align (this table is all
   figures; the generic .data-table only right-aligns the last column). */
.daily-table th+th,.daily-table td+td{text-align:right}
.daily-table tbody{border-top:1px solid var(--border)}
.daily-table tbody+tbody .day-row td{border-top:1px solid var(--border)}
.daily-table .day-row td{background:var(--panel-2);font-weight:600}
.daily-table .day-row td:first-child{font-variant-numeric:tabular-nums}
.daily-table .day-sub td{border-top:0;color:var(--muted);font-size:13px;padding-top:5px;padding-bottom:5px}
.daily-table .day-sub td:first-child{padding-left:34px}
.daily-table .day-sub:last-child td{padding-bottom:9px}

/* Modal (native <dialog>) */
dialog.modal{border:1px solid var(--border);background:var(--panel);color:var(--text);
  border-radius:var(--radius);padding:0;max-width:520px;width:calc(100% - 32px);
  box-shadow:0 24px 60px rgba(0,0,0,.5)}
dialog.modal::backdrop{background:rgba(0,0,0,.55);backdrop-filter:blur(2px)}
.modal-head{display:flex;align-items:center;justify-content:space-between;
  padding:16px 20px;border-bottom:1px solid var(--border)}
.modal-head h3{margin:0;border:0;padding:0;font-size:17px}
.modal-close{background:transparent;border:0;color:var(--muted);font-size:24px;line-height:1;
  cursor:pointer;padding:0 4px}
.modal-close:hover{color:var(--text)}
.modal-body{padding:20px}
.modal-body .form-actions{margin-top:18px;display:flex;gap:8px;justify-content:flex-end}
.row-form{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.row-form input{width:120px}

/* Admin labeled create/edit forms (models add, cloud images/templates) */
.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px}
.form-grid .field-lbl{min-width:0}
.form-grid .field-lbl input,.form-grid .field-lbl select{width:100%}
.field-lbl .field-help{font-weight:400;color:var(--muted);font-size:11px}
.field-lbl .check{margin-top:6px}
/* Compact scrollable model multi-select (allowed_models). */
.checkbox-list{display:flex;flex-direction:column;gap:4px;max-height:160px;overflow-y:auto;
  border:1px solid var(--border);border-radius:8px;padding:8px 10px;background:var(--panel-2,rgba(0,0,0,.02))}
.checkbox-list .check{display:flex;align-items:center;gap:8px;margin:0;font-weight:400;
  color:var(--text);font-size:13px;cursor:pointer}
.checkbox-list .check input{width:auto;margin:0}
.checkbox-list .check.all{border-bottom:1px solid var(--border);padding-bottom:6px;margin-bottom:2px;font-weight:600}
.checkbox-list .check.empty{color:var(--muted);cursor:default}
.form-actions{margin-top:14px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.form-span-2{grid-column:1 / -1}
.account-card{max-width:460px}

/* Auth */
.auth-wrap{min-height:100vh;display:flex;align-items:center;justify-content:center}
.auth-card{background:var(--panel);border:1px solid var(--border);border-radius:14px;padding:40px;width:360px;text-align:center}
/* The auth card drops the "AI:U" heading, so the wordmark is the whole identity
   and takes its size. Two files rather than one: the card is light by default
   and dark under the theme toggle, and the dark-grey wordmark is close to
   invisible on the dark panel. Swapped by CSS, since <picture> media queries
   follow the OS and this portal follows its own data-theme attribute. */
.auth-logo{height:56px;margin-bottom:18px}
.auth-logo.logo-inverse{display:none}
:root[data-theme="dark"] .auth-logo.logo-ink{display:none}
:root[data-theme="dark"] .auth-logo.logo-inverse{display:inline-block}
.auth-mark{font-size:32px;font-weight:700;margin:6px 0 0}
.auth-sub{color:var(--muted);margin:4px 0 20px}
.auth-form{display:flex;flex-direction:column;gap:14px;text-align:left}
.auth-form label{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)}
.auth-lang{margin-top:16px}

/* Billing */
.small{font-size:12px}
.billing-bar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin:16px 0}
.period-picker{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.period-picker label{display:flex;align-items:center;gap:6px;color:var(--muted);font-size:13px}
.period-picker select,.period-picker input{padding:6px 10px;font-size:13px}
.period-sep{color:var(--muted);font-size:12px}
.data-table tfoot th{padding:10px 14px;border-top:2px solid var(--red-dim);background:var(--th-bg);color:var(--text);text-align:left;font-size:13px}

/* Invoice-style statement */
.statement{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);padding:28px;margin-top:14px}
.statement-head{display:flex;justify-content:space-between;align-items:flex-start;gap:24px;border-bottom:1px solid var(--border);padding-bottom:18px;margin-bottom:18px;flex-wrap:wrap}
.statement-head .brand-mark{font-size:28px}
.statement-meta{font-size:13px;line-height:1.9;text-align:right}
.statement-meta .muted{display:inline-block;min-width:90px}

/* Code blocks (quickstart) */
.code-block{position:relative;margin:12px 0}
.code-block pre{background:var(--code-bg);border:1px solid var(--border);border-radius:8px;padding:16px;overflow-x:auto;margin:0}
.code-block code{font-family:var(--mono);font-size:13px;color:var(--code-fg)}
.copy-btn{position:absolute;top:8px;right:8px;cursor:pointer;border:1px solid var(--border);background:var(--panel-2);color:var(--text);padding:4px 10px;border-radius:6px;font-size:12px;font-weight:600}
.copy-btn:hover{border-color:var(--red)}

/* Print: statement only */
@media print{
  .sidebar,.side-toggle,.side-backdrop,.subnav,.no-print,.page-title{display:none!important}
  body{background:#fff;color:#000}
  .statement{border:0;padding:0}
  .data-table{border-color:#bbb}
  .data-table th{background:#eee;color:#000}
  .tag-off{color:#000;border-color:#000;background:#eee}
}

/* Chat */
.chat-content{max-width:900px;display:flex;flex-direction:column;height:calc(100vh - 80px)}
.chat-shell{display:flex;flex-direction:column;flex:1;min-height:0}
.chat-head{display:flex;align-items:center;justify-content:space-between}
.chat-controls{display:flex;gap:10px}
.messages{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:14px;padding:16px 0}
.msg{max-width:80%;padding:12px 16px;border-radius:12px;border:1px solid var(--border)}
.msg-user{align-self:flex-end;background:var(--red);border-color:var(--red);color:#fff}
.msg-assistant{align-self:flex-start;background:var(--panel)}
.msg-body p{margin:.4em 0}
.msg-body pre{background:var(--code-bg);color:var(--code-fg);padding:10px;border-radius:8px;overflow-x:auto}
.msg-body code{font-family:var(--mono);font-size:13px}
.composer{display:flex;flex-direction:column;gap:10px;padding:14px 0;border-top:1px solid var(--border)}
.composer-row{display:flex;gap:10px}
.composer textarea{flex:1;resize:none;max-height:160px}
.attach-zone{background:var(--panel-2);border:1px solid var(--border);border-radius:10px;padding:8px 10px}
.attach-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.attach-hint{color:var(--muted);font-size:12px}
.attach-list{display:flex;flex-wrap:wrap;gap:6px}
.attach-list:not(:empty){margin-top:8px}
.attach-chip{display:inline-flex;align-items:center;gap:6px;background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:3px 8px;font-size:12px;color:var(--text);max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.attach-x{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0}
.attach-x:hover{color:var(--red)}
.attach-err{outline:2px solid var(--red);outline-offset:1px}
.composer.drag{outline:2px dashed var(--red);outline-offset:3px;border-radius:8px}
.msg-attach-note{margin-top:6px;font-size:12px;opacity:.85}
/* Upload-truncation note above an assistant answer (e.g. long PDF capped) */
.chat-notice{margin:0 0 8px;font-size:12px;color:var(--muted);background:var(--panel-2);border:1px solid var(--border);border-left:3px solid var(--red);border-radius:8px;padding:6px 10px}
.chat-notice::before{content:"ℹ ";color:var(--red)}
/* Chat toggles (show / disable thinking) */
.chat-toggle{display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--muted);cursor:pointer;white-space:nowrap}
.chat-toggle input{accent-color:var(--red);cursor:pointer}
/* Collapsible reasoning block above an assistant answer */
.thinking{margin:0 0 8px;border:1px solid var(--red-dim);border-left:3px solid var(--red);border-radius:8px;background:rgba(0,0,0,.28);width:fit-content;max-width:100%}
.thinking[open]{width:auto}
.thinking>summary{cursor:pointer;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.03em;color:var(--red);padding:4px 10px;list-style:none}
.thinking>summary::-webkit-details-marker{display:none}
.thinking>summary::before{content:"▸ ";display:inline-block}
.thinking[open]>summary::before{content:"▾ "}
.thinking-body{padding:0 12px 10px;font-size:13px;color:var(--muted);white-space:pre-wrap;word-break:break-word;max-height:320px;overflow-y:auto}
/* Animated typing / loading indicator while awaiting the answer */
.typing{display:inline-flex;gap:5px;align-items:center;padding:4px 2px}
.typing .dot{width:7px;height:7px;border-radius:50%;background:var(--red);opacity:.35;animation:typing-blink 1.2s infinite ease-in-out}
.typing .dot:nth-child(2){animation-delay:.2s}
.typing .dot:nth-child(3){animation-delay:.4s}
@keyframes typing-blink{0%,80%,100%{opacity:.25;transform:translateY(0)}40%{opacity:1;transform:translateY(-3px)}}

/* Onboarding step lists */
.steps{margin:12px 0 12px 20px;line-height:1.7}
.steps li{margin:8px 0}

/* Chat replay + tool-call visualization */
.replay{display:flex;flex-direction:column;gap:14px;margin-top:18px}
.replay .msg{max-width:100%}
.msg-role{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin-bottom:6px}
.replay-text{white-space:pre-wrap;word-break:break-word;margin:0;font-family:inherit;font-size:14px}
.tool-calls,.tool-result{margin-top:10px}
.tool-call,.tool-result{border:1px solid var(--red);border-radius:8px;padding:10px 12px;margin-top:8px;background:rgba(0,0,0,.25)}
.tool-call-head{font-size:12px;color:var(--red);font-weight:600;margin-bottom:6px}
.tool-call-args{background:var(--code-bg);border-radius:6px;padding:10px;overflow-x:auto;margin:0;font-size:12px;color:var(--code-fg);white-space:pre-wrap;word-break:break-word}

/* Language switcher */
.lang-switch{display:inline-flex;margin:0}
.lang-switch select{font-size:13px;padding:4px 6px}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}

/* Admin help page */
.help-modes{margin:10px 0 14px 20px;line-height:1.7}
.help-modes li{margin:6px 0}

/* Modality pages (images / voice / transcribe / video) */
.modality-form{display:flex;flex-direction:column;gap:14px;max-width:760px;margin:16px 0}
.modality-form .field{display:flex;flex-direction:column;gap:6px}
.modality-form .field>span{font-size:13px;color:var(--muted)}
.modality-form textarea,.modality-form input[type=text],.modality-form input,.modality-form select{
  background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);
  color:var(--text);padding:9px 11px;font:inherit}
.modality-form textarea{resize:vertical}
.field-row{display:flex;gap:14px;flex-wrap:wrap}
.field-row .field{flex:1;min-width:160px}
.modality-form button{align-self:flex-start}
.mode-hint{align-self:flex-start;font-size:13px;background:var(--panel-2);
  border:1px solid var(--border);border-radius:var(--radius);padding:7px 11px;margin:0}
/* Spinner + generation-progress feedback (images sync POST, music/video polling) */
.spinner{display:inline-block;width:15px;height:15px;border:2px solid var(--border);
  border-top-color:var(--red);border-radius:50%;animation:spin .7s linear infinite;
  vertical-align:-2px;flex:0 0 auto}
@keyframes spin{to{transform:rotate(360deg)}}
.gen-progress{display:flex;align-items:center;gap:10px;margin:16px 0;font-size:14px;
  color:var(--muted);background:var(--panel-2);border:1px solid var(--border);
  border-radius:var(--radius);padding:12px 14px;max-width:760px}
.btn:disabled,.btn.is-loading{opacity:.6;cursor:progress;pointer-events:none}
.btn.is-loading .spinner{width:13px;height:13px;margin-right:6px;border-top-color:#fff}
.notice{border:1px solid var(--red);background:var(--red-dim);color:var(--text);
  padding:10px 14px;border-radius:var(--radius);margin:10px 0}
.cost-line{margin:12px 0}
.image-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:14px;margin-top:14px}
.image-card{margin:0;display:flex;flex-direction:column;gap:8px;background:var(--panel);
  border:1px solid var(--border);border-radius:var(--radius);padding:10px}
.image-card img{width:100%;border-radius:8px;display:block}
.audio-result{display:flex;align-items:center;gap:12px;margin:16px 0;flex-wrap:wrap}
.audio-result audio{flex:1;min-width:240px}
.custom-voice{margin-top:24px;border-top:1px solid var(--border);padding-top:14px}
.custom-voice summary{cursor:pointer;font-weight:600;color:var(--red)}
.transcript{margin-top:18px;background:var(--panel);border:1px solid var(--border);
  border-radius:var(--radius);padding:14px}
.transcript p{white-space:pre-wrap;word-break:break-word;margin:.4em 0 0}
.video-job{margin-top:18px}
.video-job video{border-radius:var(--radius);margin-top:8px}
.modality-group{margin:14px 0;padding:12px 0;border-bottom:1px solid var(--border)}
.modality-group h3{margin:0 0 6px;display:flex;align-items:center;gap:10px}

/* ------------------------------------------------------------------ */
/* GPU Cloud (Pods) — overview cards, launch wizard, keys, empty states */
/* ------------------------------------------------------------------ */
.cloud-head{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;margin:18px 0 6px}
.cloud-lead{max-width:640px;margin:6px 0 0}
.cloud-head .btn{display:inline-flex;align-items:center;gap:8px}

/* My-pods card grid */
.pod-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:16px;margin-top:18px}
.pod-card{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);padding:18px;display:flex;flex-direction:column;gap:14px;position:relative;overflow:hidden}
.pod-card::before{content:"";position:absolute;top:0;left:0;width:3px;height:100%;background:var(--border)}
.pod-card.pod-running::before{background:linear-gradient(180deg,#5fd08a,#2f7a4d)}
.pod-card.pod-creating::before{background:linear-gradient(180deg,var(--red),var(--red-dim))}
.pod-card.pod-stopped::before{background:#54545c}
.pod-card.pod-error::before{background:var(--red)}
.pod-card:hover{border-color:#3a3a42}
.pod-card-head{display:flex;align-items:center;justify-content:space-between;gap:10px}
.pod-id{display:flex;flex-direction:column;gap:2px;min-width:0}
.pod-id-label{font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)}
.pod-id code{font-size:13px}
.pod-template{font-size:17px;font-weight:700}
.pod-meta{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;margin:0}
.pod-meta>div{display:flex;flex-direction:column;gap:2px;min-width:0}
.pod-meta dt{font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)}
.pod-meta dd{margin:0;font-size:14px;word-break:break-all}
.pod-cost{display:flex;gap:10px;background:var(--th-bg);border:1px solid var(--border);border-radius:8px;padding:12px 14px}
.pod-cost-item{flex:1;display:flex;flex-direction:column;gap:3px}
.pod-cost-label{font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)}
.pod-uptime,.pod-livecost{font-size:18px;font-weight:700}
.pod-livecost{color:#5fd08a}
.pod-connect{display:flex;align-items:center;gap:8px;background:var(--panel-2);border:1px solid var(--border);border-radius:8px;padding:8px 10px}
.pod-connect code{flex:1;word-break:break-all}
.pod-connect-pending{color:var(--muted)}
.pod-actions{display:flex;gap:8px;flex-wrap:wrap;border-top:1px solid var(--border);padding-top:12px;margin-top:auto}
.pod-actions form{display:inline}
.tag-creating{background:rgba(225,6,0,.12);color:#ff9b98;border:1px solid var(--red-dim)}

/* Empty states */
.empty-state{display:flex;flex-direction:column;align-items:center;gap:10px;text-align:center;background:var(--panel);border:1px dashed var(--border);border-radius:var(--radius);padding:48px 24px;margin-top:18px}
.empty-state-sm{padding:32px 20px}
.empty-state h3{border:0;padding:0;margin:0;font-size:18px}
.empty-state p{margin:0;max-width:380px}
.empty-state .btn{margin-top:6px}
.empty-ico{width:42px;height:42px;stroke:var(--red);opacity:.7}

/* "Add a key first" call-to-action (replaces the old dead launch button) */
.cta-panel{display:flex;align-items:center;gap:18px;background:var(--panel);border:1px solid var(--red-dim);border-left:3px solid var(--red);border-radius:var(--radius);padding:20px 22px;margin-top:18px;flex-wrap:wrap}
.cta-ico{width:34px;height:34px;flex:0 0 auto;stroke:var(--red)}
.cta-text{flex:1;min-width:200px}
.cta-text strong{display:block;font-size:16px;margin-bottom:2px}
.cta-text p{margin:0}
.cta-panel .btn{display:inline-flex;align-items:center;gap:8px}

/* Launch wizard */
.launch-wizard{margin-top:8px}
.spec-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px;margin-top:6px}
.spec-card{display:block;cursor:pointer}
.spec-card input{position:absolute;opacity:0;pointer-events:none}
.spec-card-inner{display:flex;flex-direction:column;gap:12px;background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);padding:16px;transition:.12s}
.spec-card:hover .spec-card-inner{border-color:#3a3a42}
.spec-card input:checked+.spec-card-inner{border-color:var(--red);box-shadow:0 0 0 1px var(--red),0 8px 24px rgba(225,6,0,.18)}
.spec-card input:focus-visible+.spec-card-inner{outline:2px solid var(--red);outline-offset:2px}
.spec-card-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px}
.spec-name{font-size:16px;font-weight:700}
.spec-price{font-size:18px;font-weight:700;color:var(--red);white-space:nowrap}
.spec-price-suffix{font-size:12px;font-weight:600;color:var(--muted)}
.spec-rows{display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--border);padding-top:10px}
.spec-row{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.spec-k{font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:var(--muted)}
.spec-v{font-family:var(--mono);font-size:13px}
.spec-avail{display:flex;align-items:center;flex-wrap:wrap;gap:8px;border-top:1px solid var(--border);padding-top:10px}
.spec-host{font-size:11px}
.spec-card-disabled{cursor:not-allowed}
.spec-card-disabled .spec-card-inner{opacity:.5;filter:grayscale(.4)}
.spec-card-disabled:hover .spec-card-inner{border-color:var(--border)}
.launch-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;margin-top:6px}
.field-lbl{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)}
.field-lbl input,.field-lbl select,.field-lbl textarea{width:100%}
.btn-launch{display:inline-flex;align-items:center;gap:8px;margin-top:20px}

/* SSH keys page */
.keys-layout{display:grid;grid-template-columns:340px 1fr;gap:20px;margin-top:18px;align-items:start}
.keys-layout .panel h3{margin-top:0}
@media(max-width:760px){.keys-layout{grid-template-columns:1fr}.pod-meta{grid-template-columns:1fr 1fr}}

/* Themed scrollbars (black/red/white). WebKit + Firefox; vendored, no CDN. */
*{scrollbar-width:thin;scrollbar-color:var(--red) var(--panel)}
::-webkit-scrollbar{width:11px;height:11px}
::-webkit-scrollbar-track{background:var(--panel)}
::-webkit-scrollbar-thumb{background:var(--red-dim);border-radius:6px;border:2px solid var(--panel)}
::-webkit-scrollbar-thumb:hover{background:var(--red)}
::-webkit-scrollbar-corner{background:var(--panel)}

/* ----- Guardrails (tenant content policy + live tester) ----- */
.pill{display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:700;border:1px solid var(--border);white-space:nowrap}
.pill-on{background:rgba(16,185,129,.15);color:#34d399;border-color:rgba(16,185,129,.4)}
.pill-off{background:var(--panel-2);color:var(--muted)}
.gswitch{display:inline-flex;align-items:center;gap:10px;cursor:pointer;font-weight:600;color:var(--text)}
.gswitch input{position:absolute;opacity:0;width:0;height:0}
.gswitch-track{position:relative;flex:0 0 auto;width:38px;height:22px;background:var(--panel-2);border:1px solid var(--border);border-radius:11px;transition:background .15s}
.gswitch-track::after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--muted);transition:transform .15s,background .15s}
.gswitch input:checked + .gswitch-track{background:var(--red);border-color:var(--red)}
.gswitch input:checked + .gswitch-track::after{transform:translateX(16px);background:#fff}
.gswitch input:focus-visible + .gswitch-track{outline:2px solid var(--red);outline-offset:2px}
.guard-master{margin-bottom:16px}
.guard-master-row{display:flex;align-items:center;justify-content:space-between;gap:12px}
.guard-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin-bottom:18px}
.guard-card{display:flex;flex-direction:column;gap:10px}
.guard-card-wide{grid-column:1 / -1}
.guard-card-head .gswitch-lbl{font-size:15px;font-weight:700}
.guard-card .field-help{margin:0}
.guard-controls{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end}
.field-lbl.sm{font-size:12px;flex:0 0 auto;gap:4px}
.field-lbl.sm select{width:auto;min-width:150px}
.guard-entities{display:flex;flex-wrap:wrap;gap:8px 16px}
.guard-two-col{display:grid;grid-template-columns:1fr 1fr;gap:12px}
textarea.mono{font-family:var(--mono);font-size:12px}
.guard-tester{margin-top:18px;border-color:var(--red-dim)}
.guard-tester-head{margin-bottom:8px}
.guard-tester textarea{width:100%;font-family:var(--mono);font-size:13px}
.gt-result{margin-top:14px;padding:14px;border-radius:var(--radius);background:var(--panel-2);border:1px solid var(--border)}
.gt-verdict{display:inline-block;font-weight:700;font-size:12px;letter-spacing:.04em;text-transform:uppercase;padding:4px 12px;border-radius:999px;margin-bottom:10px}
.gt-allow{background:rgba(16,185,129,.18);color:#34d399}
.gt-flag{background:rgba(245,158,11,.18);color:#fbbf24}
.gt-redact{background:rgba(59,130,246,.18);color:#60a5fa}
.gt-block{background:rgba(225,6,0,.2);color:#ff6b66}
.gt-blockmsg{color:var(--text);margin:6px 0}
.gt-badges{margin:6px 0;font-size:12px;color:var(--muted);line-height:2.1}
.gt-badges-lbl{margin-right:4px}
.gt-badge{display:inline-block;padding:2px 8px;border-radius:6px;background:var(--panel);border:1px solid var(--border);font-size:11px;color:var(--text)}
.gt-badge-block{border-color:var(--red);color:#ff6b66}
.gt-badge-redact{border-color:#3b82f6;color:#60a5fa}
.gt-badge-flag{border-color:#f59e0b;color:#fbbf24}
.gt-diff{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:10px}
.gt-diff-lbl{display:block;font-size:11px;color:var(--muted);margin-bottom:4px;text-transform:uppercase;letter-spacing:.04em}
.gt-diff pre{white-space:pre-wrap;word-break:break-word;background:var(--panel);border:1px solid var(--border);border-radius:8px;padding:10px;font-family:var(--mono);font-size:12px;margin:0}
.gt-after{border-color:var(--red-dim)}
.rule-rows{display:flex;flex-direction:column;gap:6px;margin:4px 0}
.rule-row{display:flex;gap:8px;align-items:center}
.rule-val{flex:1;min-width:0;width:100%}
.rule-act{flex:0 0 auto;width:auto;min-width:108px}
.rule-x{flex:0 0 auto;background:none;border:1px solid var(--border);border-radius:6px;color:var(--muted);cursor:pointer;width:28px;height:28px;line-height:1;font-size:16px}
.rule-x:hover{color:var(--red);border-color:var(--red)}
.add-rule{align-self:flex-start}
.entity-grid{display:flex;flex-direction:column;gap:6px;margin-top:2px}
.entity-row{display:flex;align-items:center;justify-content:space-between;gap:10px}
.entity-name{font-size:13px;color:var(--text)}
.entity-act{width:auto;min-width:120px;flex:0 0 auto}
.guard-logsnip{margin-top:10px}
.viol-snippet{margin-top:6px;font-size:12px;color:var(--muted);background:var(--panel-2);border:1px solid var(--border);border-radius:6px;padding:6px 8px;white-space:pre-wrap;word-break:break-word}
@media(max-width:760px){.guard-grid{grid-template-columns:1fr}}
@media(max-width:560px){.guard-two-col,.gt-diff{grid-template-columns:1fr}}

/* ── Reporting: sortable tables, filter bar, metric tiles, sparkline ────────
   All colours come from the existing tokens so light and dark both follow
   without a second definition. */
.th-sort{cursor:pointer;user-select:none;position:relative;white-space:nowrap}
.th-sort:hover{color:var(--text)}
.th-sort:focus-visible{outline:2px solid var(--red);outline-offset:-2px}
.th-sort::after{content:"";opacity:0;margin-left:6px;font-size:10px}
.th-sort.sorted{color:var(--text)}
.th-sort.sorted[data-dir="asc"]::after{content:"▲";opacity:.75}
.th-sort.sorted[data-dir="desc"]::after{content:"▼";opacity:.75}

.report-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:14px 0 4px}
.report-bar .table-filter{min-width:230px;flex:0 1 320px}
.filter-count{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}
.report-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:26px}
.report-head h3{margin:0}
.report-hint{font-size:12px;color:var(--muted)}

/* Numeric columns read as columns of figures, not ragged text. */
.data-table.report td.n,.data-table.report th.n{text-align:right;font-variant-numeric:tabular-nums}
.data-table.report td.n{white-space:nowrap}

/* Every table is exactly the width of the content column. A report table with
   ten nowrap numeric columns has a minimum content width wider than the page,
   and without this it simply grew past its container: the model table rendered
   1194px inside a 1020px column while the narrower tables sat at 1020, so the
   page looked ragged and picked up a horizontal scrollbar. The wrapper keeps
   the outer width identical for every table and scrolls the wide ones inside
   themselves. */
.table-wrap{width:100%;max-width:100%;overflow-x:auto;overflow-y:hidden}
.table-wrap>.data-table{margin-top:12px}
.table-wrap+.table-wrap{margin-top:0}

/* Compact metric tiles: denser than .stat, for a row of six or seven. */
.metric-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin:18px 0}
.metric{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);
  padding:12px 14px;box-shadow:var(--shadow-sm);min-width:0}
.metric-label{display:block;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.4px}
.metric-value{display:block;font-size:21px;font-weight:700;margin-top:3px;
  font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
.metric-sub{display:block;font-size:11px;color:var(--muted);margin-top:2px}
.metric.good .metric-value{color:#16a34a}
.metric.warn .metric-value{color:#b45309}
.metric.bad .metric-value{color:var(--red)}
:root[data-theme="dark"] .metric.good .metric-value{color:#34d399}
:root[data-theme="dark"] .metric.warn .metric-value{color:#fbbf24}

/* Health pills for error rate / cache rate inside tables. */
.pill{display:inline-block;padding:1px 8px;border-radius:999px;font-size:11px;
  font-weight:600;border:1px solid var(--border);background:var(--panel-2);color:var(--muted);
  font-variant-numeric:tabular-nums}
.pill-good{border-color:rgba(22,163,74,.45);background:rgba(22,163,74,.12);color:#15803d}
.pill-warn{border-color:rgba(180,83,9,.45);background:rgba(245,158,11,.14);color:#b45309}
.pill-bad{border-color:var(--red);background:rgba(225,6,0,.12);color:var(--red)}
:root[data-theme="dark"] .pill-good{color:#34d399}
:root[data-theme="dark"] .pill-warn{color:#fbbf24}
:root[data-theme="dark"] .pill-bad{color:#ff6b66}

/* Day sparkline: one column per day, cost-proportional height. */
.spark{display:flex;align-items:flex-end;gap:2px;height:96px;margin:14px 0 4px;
  padding:8px 10px;background:var(--panel);border:1px solid var(--border);
  border-radius:var(--radius);box-shadow:var(--shadow-sm);overflow-x:auto}
.spark-col{flex:1 0 8px;min-width:6px;display:flex;flex-direction:column;justify-content:flex-end;height:100%}
.spark-bar{display:block;width:100%;background:linear-gradient(180deg,var(--red),var(--red-dim));
  border-radius:2px 2px 0 0;min-height:1px}
.spark-col:hover .spark-bar{background:var(--text)}
.spark-axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin-top:-2px}

@media(max-width:640px){
  .report-bar .table-filter{flex:1 1 100%}
}

/* ── Load heatmap: weekday x hour ──────────────────────────────────────────
   Intensity is passed in as a 0-1 alpha so a single accent colour carries the
   scale in both themes without a second palette. */
.heatmap{margin:14px 0 4px;padding:12px 14px;background:var(--panel);
  border:1px solid var(--border);border-radius:var(--radius);
  box-shadow:var(--shadow-sm);overflow-x:auto}
.heat-grid{display:grid;grid-template-columns:auto repeat(24,1fr);gap:2px;min-width:640px}
.heat-corner{}
.heat-hour{font-size:10px;color:var(--muted);text-align:center;line-height:1.4}
.heat-day{font-size:11px;color:var(--muted);padding-right:8px;white-space:nowrap;
  display:flex;align-items:center}
.heat-cell{aspect-ratio:1/1;border-radius:2px;background:var(--panel-2);
  min-height:14px;border:1px solid transparent}
.heat-cell[data-on="1"]{border-color:rgba(225,6,0,.25)}
.heat-legend{display:flex;align-items:center;gap:6px;margin-top:10px;
  font-size:11px;color:var(--muted)}
.heat-swatch{width:14px;height:14px;border-radius:2px;background:var(--panel-2)}

/* ── Top movers: what changed against the comparable previous stretch ────── */
.movers{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
  gap:10px;margin:14px 0}
.mover{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);
  padding:10px 13px;box-shadow:var(--shadow-sm);display:flex;
  justify-content:space-between;align-items:baseline;gap:10px;min-width:0}
.mover-label{font-family:var(--mono);font-size:12px;overflow-wrap:anywhere;min-width:0}
.mover-kind{display:block;font-size:10px;color:var(--muted);text-transform:uppercase;
  letter-spacing:.4px}
.mover-delta{font-weight:700;white-space:nowrap;font-variant-numeric:tabular-nums}
.mover-up .mover-delta{color:var(--red)}
.mover-down .mover-delta{color:#16a34a}
:root[data-theme="dark"] .mover-down .mover-delta{color:#34d399}
.mover-sub{display:block;font-size:11px;color:var(--muted);font-weight:400;text-align:right}

/* Delta column inside the report tables. */
.delta-up{color:var(--red)}
.delta-down{color:#16a34a}
:root[data-theme="dark"] .delta-down{color:#34d399}
.delta-new{color:var(--muted);font-style:italic}

/* ── Model pressure: who is driving each model ─────────────────────────────
   A stacked share bar plus named segments. Four fixed hues rather than a
   generated palette, because the same rank should look the same on every row. */
.share-bar{display:flex;height:8px;border-radius:4px;overflow:hidden;
  background:var(--panel-2);min-width:140px;margin-bottom:5px}
.share-seg{display:block;height:100%}
.share-seg.s1,.dot.s1{background:var(--red)}
.share-seg.s2,.dot.s2{background:#f59e0b}
.share-seg.s3,.dot.s3{background:#3b82f6}
.share-seg.s4,.dot.s4{background:#8b5cf6}
.share-names{display:flex;flex-wrap:wrap;gap:4px 10px;font-size:11px;color:var(--muted)}
.share-name{display:inline-flex;align-items:center;gap:4px;white-space:nowrap}
.dot{width:7px;height:7px;border-radius:50%;display:inline-block;flex:0 0 auto}

/* ── Lyric structure markers (music page) ──────────────────────────────────
   One-click inserts for [verse], [chorus] and friends. The model keeps only a
   leading tag on a line and discards the rest of it, so the buttons always
   place a marker on its own line; these are affordances, not decoration. */
.tag-chips{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 8px}
.chip{background:var(--panel);border:1px solid var(--border);color:var(--text);
  border-radius:999px;padding:3px 11px;font-family:var(--mono);font-size:12px;
  line-height:1.5;cursor:pointer}
.chip:hover{border-color:var(--red);color:var(--red)}
.chip:active{transform:translateY(1px)}

/* Model attribution. The MiniMax-Music3 licence requires the name to be shown
   in the UI of a commercial service, so this line is a licence term, not a
   credit that can be trimmed for tidiness. */
.attribution{font-size:12px;margin-top:14px}

/* ── Music studio ──────────────────────────────────────────────────────────
   One page, three bands: a listening deck on top, the recipe underneath, a
   library down the left. Two earlier versions of this page were a wizard and
   they read as a form; there are deliberately no steps, no rail and no
   "continue" button anywhere in here.

   What makes it read as a studio rather than a settings screen is that the
   things you look at are the things you hear: waveforms, a playhead, blocks
   whose width is the actual length of that part of the song. Nothing in this
   file draws a control that has no audible consequence. */

:root{
  --wave:#8b8b96; --wave-dim:#c9c9d1;
  --deck-bg:#101013; --deck-line:#232329;
}
:root[data-theme="dark"]{
  --wave:#9a9aa6; --wave-dim:#2e2e36;
  --deck-bg:#000; --deck-line:#1c1c22;
}

.studio2{max-width:1240px;margin:0 auto}

/* head row */
.st-head{display:flex;align-items:center;gap:10px;margin:0 0 12px}
.st-title{flex:1 1 auto;min-width:0;font-size:20px;font-weight:600;
  background:transparent;border:1px solid transparent;border-radius:8px;
  padding:6px 10px;color:var(--text)}
.st-title:hover{border-color:var(--border)}
.st-title:focus{border-color:var(--red);background:var(--panel);outline:none}
.st-spacer{flex:1 1 auto}
.seed-chip{display:inline-flex;align-items:center;gap:6px;background:var(--panel);
  border:1px solid var(--border);border-radius:999px;padding:4px 12px;
  font-family:var(--mono);font-size:12px;color:var(--muted);cursor:pointer}
.seed-chip.pinned{border-color:var(--red);color:var(--text)}
.seed-chip:hover{border-color:var(--red)}

/* ── the deck ─────────────────────────────────────────────────────────── */
.deck{background:var(--deck-bg);border:1px solid var(--deck-line);border-radius:12px;
  padding:10px 12px 8px;margin:0 0 18px;box-shadow:var(--shadow-md)}
.lanes{display:flex;flex-direction:column;gap:6px}
.lane{display:grid;grid-template-columns:132px 1fr;gap:10px;align-items:stretch;
  border:1px solid transparent;border-radius:8px;padding:4px;transition:border-color .12s}
.lane.active{border-color:rgba(225,6,0,.55);background:rgba(225,6,0,.05)}
.lane-side{display:flex;flex-direction:column;justify-content:center;gap:2px;
  padding:0 4px;min-width:0}
.lane-id{font-family:var(--mono);font-size:15px;font-weight:700;color:#e7e7ea;line-height:1}
.lane-kind{font-size:11px;color:#9a9aa6;text-transform:uppercase;letter-spacing:.6px}
.lane-meta{font-family:var(--mono);font-size:11px;color:#7a7a86;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lane-wave{position:relative;height:88px;min-width:0}
.lane-wave canvas{width:100%;height:100%;display:block;cursor:crosshair}
.lane-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:12px;color:#5a5a64;pointer-events:none}

/* the section strip under the lanes: colour tells you where you are */
.deck-strip{display:flex;gap:2px;height:20px;margin:6px 0 0}
.deck-strip span{flex:1 1 auto;border-radius:2px;
  background:hsl(var(--h,260) 40% 40% / .55);font-size:9px;color:#c9c9d1;
  display:flex;align-items:center;justify-content:center;overflow:hidden}

.transport{display:flex;align-items:center;gap:12px;margin-top:8px;
  padding-top:8px;border-top:1px solid var(--deck-line);flex-wrap:wrap}
.tr-play{width:34px;height:34px;border-radius:50%;border:1px solid #3a3a44;
  background:#1c1c21;color:#f4f4f5;font-size:13px;cursor:pointer;flex:0 0 auto}
.tr-play:hover{border-color:var(--red);color:var(--red)}
.tr-time{font-family:var(--mono);font-size:12px;color:#9a9aa6;min-width:88px}
.ab-rocker{display:inline-flex;align-items:center;gap:2px;background:#1c1c21;
  border:1px solid #33333c;border-radius:8px;padding:2px}
.ab-rocker button{min-width:34px;padding:4px 10px;border:none;border-radius:6px;
  background:transparent;color:#9a9aa6;font-weight:700;font-size:12px;cursor:pointer}
.ab-rocker button.on{background:var(--red);color:#fff}
.ab-sep{color:#4a4a54;font-size:11px;padding:0 1px}
.transport .btn.on{border-color:var(--red);color:var(--red)}
.tr-note{font-size:11px;color:#7a7a86}
.tr-note.good{color:#34d399}
.tr-note.warn{color:#fbbf24}
.btn-probe{background:#1c1c21;border:1px solid #3a3a44;color:#f4f4f5}
.btn-probe:hover:not(:disabled){border-color:var(--red);color:var(--red)}
.btn-probe small{font-family:var(--mono);font-size:11px;color:#7a7a86;margin-left:6px}
.honesty{margin:8px 0 0;font-size:11px;color:#6a6a74;line-height:1.4}

/* ── work area ────────────────────────────────────────────────────────── */
.work{display:grid;grid-template-columns:250px 1fr;gap:18px;align-items:start}

.ablage{position:sticky;top:12px;border:1px solid var(--border);border-radius:10px;
  background:var(--panel);overflow:hidden}
.ablage-tabs{display:flex;border-bottom:1px solid var(--border)}
.atab{flex:1 1 0;padding:8px 6px;border:none;background:transparent;cursor:pointer;
  font-size:12px;color:var(--muted);border-bottom:2px solid transparent}
.atab.on{color:var(--text);font-weight:600;border-bottom-color:var(--red);
  background:var(--panel-2)}
.atab-n{font-family:var(--mono);font-size:11px}
.ablage-body{max-height:60vh;overflow-y:auto;padding:8px}
.take{display:flex;align-items:center;gap:7px;padding:6px 7px;border-radius:7px;
  font-size:12px;border:1px solid transparent}
.take:hover{background:var(--panel-2);border-color:var(--border)}
.take-k{font-size:11px;color:var(--muted);flex:0 0 auto}
.take-t{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.take-m{font-family:var(--mono);font-size:10px;color:var(--red)}
.take-s{font-size:10px;color:var(--muted);flex:0 0 auto}
.take.running .take-s{color:var(--red)}
.take-btns{display:flex;gap:2px;flex:0 0 auto}
.take-btns button{border:1px solid var(--border);background:var(--panel);border-radius:5px;
  font-size:10px;padding:1px 6px;cursor:pointer;color:var(--text)}
.take-btns button:hover{border-color:var(--red);color:var(--red)}

.bench{min-width:0}
.bench-h{margin:20px 0 10px;font-size:12px;text-transform:uppercase;letter-spacing:.9px;
  color:var(--muted);font-weight:700;display:flex;align-items:baseline;gap:10px}
.bench-h:first-child{margin-top:0}
.bench-sub{font-size:11px;letter-spacing:0;text-transform:none;font-weight:400}

.pitch-card{display:flex;align-items:flex-end;gap:8px;
  border:1px solid var(--border);border-radius:10px;background:var(--panel-2);
  padding:10px 12px}
.pitch-card .field{flex:1 1 auto;margin:0}
.pitch-card input{width:100%}

/* ── the rack ─────────────────────────────────────────────────────────── */
/* Explicit column counts, not auto-fill: there are NINE modules, and 3 divides
   them into clean rows while 4 or 5 leaves an orphan that then has to be
   special-cased. Six modules (the instrumental rack) also divide by 3. */
.rack{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}
@media (max-width:1180px){.rack{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:720px){.rack{grid-template-columns:1fr}}
.mod{border:1px solid var(--border);border-radius:10px;background:var(--panel);
  padding:10px 12px 12px;min-width:0}
.mod-h{display:flex;align-items:center;gap:7px;margin:0 0 9px;
  padding-bottom:6px;border-bottom:1px solid var(--border)}
.mod-n{font-size:12px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;
  color:var(--text);flex:1 1 auto;min-width:0}
.mod-state{font-size:11px;flex:0 0 auto}
.mod-state.stale{position:relative}
.mod-state.stale::after{content:"";position:absolute;top:-2px;right:-5px;
  width:6px;height:6px;border-radius:50%;background:#f59e0b}
.mod-probe{border:1px solid var(--border);background:var(--panel-2);border-radius:6px;
  color:var(--muted);font-size:12px;line-height:1;padding:2px 7px;cursor:pointer;flex:0 0 auto}
.mod-probe:hover{border-color:var(--red);color:var(--red)}
.mod-b{display:flex;flex-direction:column;gap:10px}
.dial{min-width:0}
.dial-n{display:block;font-size:11px;color:var(--muted);margin:0 0 5px}

/* segmented: equal widths, no floating */
.seg{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:0;
  border:1px solid var(--border);border-radius:7px;overflow:hidden}
.seg button{border:none;background:var(--panel-2);color:var(--muted);cursor:pointer;
  font-size:12px;padding:5px 4px;border-right:1px solid var(--border);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.seg button:last-child{border-right:none}
.seg button:hover{color:var(--text)}
.seg button.on{background:var(--red);color:#fff;font-weight:600}
.seg.small button{font-size:11px;padding:4px 2px}

/* chip rows: wrap, never scroll horizontally */
.chipline{display:flex;flex-wrap:wrap;gap:5px}
.chipline button{border:1px solid var(--border);background:var(--panel-2);
  border-radius:999px;padding:3px 11px;font-size:12px;color:var(--text);cursor:pointer}
.chipline button:hover{border-color:var(--red)}
.chipline button.on{background:var(--red);border-color:var(--red);color:#fff;font-weight:600}
.chip.on{background:var(--red);border-color:var(--red);color:#fff}

/* the voice cards: the one field that silently ruins a render */
.vcards{display:grid;grid-template-columns:repeat(auto-fit,minmax(104px,1fr));gap:6px}
.vcards button{border:1px solid var(--border);background:var(--panel-2);border-radius:8px;
  padding:8px 9px;font-size:12px;color:var(--text);cursor:pointer;text-align:left}
.vcards button:hover{border-color:var(--red)}
.vcards button.on{border-color:var(--red);background:var(--panel);font-weight:600;
  box-shadow:inset 3px 0 0 var(--red)}

/* twelve pitches, laid out like a keyboard row rather than a dropdown */
.keyring{display:grid;grid-template-columns:repeat(6,1fr);gap:4px}
.keyring button{border:1px solid var(--border);background:var(--panel-2);border-radius:6px;
  padding:5px 2px;font-family:var(--mono);font-size:12px;color:var(--text);cursor:pointer}
.keyring button:hover{border-color:var(--red)}
.keyring button.on{background:var(--red);border-color:var(--red);color:#fff;font-weight:700}

.stops input[type="range"]{width:100%;margin:0}
.stop-labels{display:flex;justify-content:space-between;gap:2px;margin-top:2px}
.stop-labels span{font-size:10px;color:var(--muted);flex:1 1 0;text-align:center;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.stop-labels span.on{color:var(--red);font-weight:700}

.bpm{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.bpm input[type="range"]{flex:1 1 110px;min-width:90px;margin:0}
.bpm-n{width:64px;flex:0 0 auto}
.bpm-u{font-size:11px;color:var(--muted)}
.bpm-tap{border:1px solid var(--border);background:var(--panel-2);border-radius:6px;
  font-size:11px;padding:4px 9px;cursor:pointer;color:var(--text)}
.bpm-tap:hover{border-color:var(--red);color:var(--red)}

.combo input{width:100%}

/* band: chosen instruments as tokens, the palette folded away by family */
.band{display:flex;flex-direction:column;gap:8px}
.inst-picked{display:flex;flex-wrap:wrap;gap:5px;min-height:26px}
.inst-tok{display:inline-flex;align-items:center;gap:5px;border:1px solid var(--red);
  background:var(--panel-2);border-radius:999px;padding:2px 4px 2px 11px;font-size:12px}
.inst-tok.sec{border-color:var(--border);color:var(--muted)}
.tok-tier,.tok-x{border:none;background:transparent;cursor:pointer;font-size:11px;
  color:var(--muted);padding:0 3px;line-height:1}
.tok-tier{font-family:var(--mono);border:1px solid var(--border);border-radius:4px}
.tok-x:hover{color:var(--red)}
.inst-fams{display:flex;flex-direction:column;gap:3px}
.inst-fam summary{cursor:pointer;font-size:11px;color:var(--muted);padding:3px 0}
.inst-list{display:flex;flex-wrap:wrap;gap:4px;padding:4px 0 6px}
.inst-add{border:1px solid var(--border);background:var(--panel-2);border-radius:6px;
  font-size:11px;padding:3px 8px;cursor:pointer;color:var(--text)}
.inst-add:hover{border-color:var(--red)}
.inst-add.on{border-color:var(--red);color:var(--red);font-weight:600}

/* ── arrangement ──────────────────────────────────────────────────────── */
/* Block width IS the length of that part. That is the whole point: the picture
   and the duration cannot disagree, because they are the same number. */
.arrange{display:grid;gap:3px;position:relative;margin:0 0 6px;min-height:62px;
  touch-action:pan-y}
.blk{position:relative;height:62px;border-radius:6px;cursor:move;overflow:hidden;
  border:1px solid hsl(var(--h) 45% 45% / .5);
  background:hsl(var(--h) 45% 50% / .16);
  display:flex;flex-direction:column;justify-content:flex-end;padding:4px 6px;
  min-width:0;user-select:none}
.blk:hover{border-color:hsl(var(--h) 55% 55% / .9)}
.blk:focus-visible{outline:2px solid var(--red);outline-offset:1px}
.blk.dragging{opacity:.4;cursor:move}
.blk-energy{position:absolute;left:0;right:0;bottom:0;
  background:hsl(var(--h) 55% 50% / .3);border-top:2px solid hsl(var(--h) 60% 55% / .95);
  cursor:ns-resize;pointer-events:auto}
.blk-l{position:relative;font-size:10px;font-weight:700;text-transform:uppercase;
  letter-spacing:.4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  color:var(--text);pointer-events:none}
.blk-t{position:relative;font-family:var(--mono);font-size:9px;color:var(--muted);
  pointer-events:none}
.drop-ind{position:absolute;top:0;bottom:0;width:3px;background:var(--red);
  border-radius:2px;pointer-events:none;left:0}
.plan-note{margin:0 0 14px;font-size:11px;color:var(--muted)}

/* ── lyrics ───────────────────────────────────────────────────────────── */
.partitur{display:flex;flex-direction:column;gap:9px;margin:0 0 10px}
.pcard{border:1px solid var(--border);border-radius:9px;background:var(--panel);
  padding:8px 10px}
.pc-h{display:flex;align-items:center;gap:8px;margin-bottom:7px;
  padding-bottom:6px;border-bottom:1px solid var(--border)}
.pc-grip{cursor:move;color:var(--muted);font-size:13px;line-height:1;
  opacity:.55;padding:2px 3px;border-radius:4px}
.pc-grip:hover{opacity:1;color:var(--red)}
.pc-grip:focus-visible{outline:2px solid var(--red);outline-offset:1px;opacity:1}
.pc-type{width:150px;font-size:12px;font-weight:600;text-transform:uppercase;
  letter-spacing:.4px}
.pc-lines{font-family:var(--mono);font-size:10px;color:var(--muted)}
.pc-rw{border:1px solid var(--border);background:var(--panel-2);border-radius:6px;
  font-size:11px;padding:2px 8px;cursor:pointer;color:var(--text)}
.pc-rw:hover:not(:disabled){border-color:var(--red);color:var(--red)}
.pc-rw:disabled{opacity:.45;cursor:not-allowed}
.pc-x{border:none;background:transparent;color:var(--muted);cursor:pointer;
  font-size:15px;line-height:1;padding:0 3px}
.pc-x:hover{color:var(--red)}
.pc-t{width:100%;box-sizing:border-box;font-size:13px;line-height:1.6;resize:vertical}
.pc-instr{margin:0;font-size:12px;font-style:italic}

/* ── bake ─────────────────────────────────────────────────────────────── */
.bake{border:1px solid var(--border);border-radius:10px;background:var(--panel-2);
  padding:12px 14px}
.bake-row{display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap;margin-bottom:10px}
.bake-len{margin:0;flex:0 0 160px}
.bake-len input{width:100%;font-family:var(--mono)}
.quality{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;flex:1 1 260px;
  border:1px solid var(--border);border-radius:7px;overflow:hidden}
.quality button{border:none;border-right:1px solid var(--border);background:var(--panel);
  cursor:pointer;padding:6px 4px;font-size:12px;color:var(--muted);
  display:flex;flex-direction:column;align-items:center;gap:1px}
.quality button:last-child{border-right:none}
.quality button.on{background:var(--red);color:#fff;font-weight:600}
.quality small{font-family:var(--mono);font-size:10px;opacity:.8}
.bake-go{display:flex;align-items:center;gap:12px}
.blockers{margin:0 0 8px}
.blocker{margin:0 0 4px;font-size:12px;color:var(--red-dim);
  border-left:3px solid var(--red);padding-left:8px}
.caption-readout{margin-top:12px;border-top:1px solid var(--border);padding-top:8px}
.caption-readout summary{cursor:pointer;font-size:12px;color:var(--muted)}
.caption-pre{margin:8px 0 0;max-height:280px;overflow:auto;white-space:pre-wrap;
  font-family:var(--mono);font-size:11px;line-height:1.5;color:var(--text);
  background:var(--panel);border:1px solid var(--border);border-radius:8px;padding:10px}

.undo-toast{position:fixed;left:50%;bottom:22px;transform:translateX(-50%);
  display:flex;align-items:center;gap:12px;background:var(--panel);
  border:1px solid var(--border);border-radius:10px;padding:8px 14px;
  box-shadow:var(--shadow-md);z-index:40;font-size:13px}

/* ── responsive ───────────────────────────────────────────────────────── */
@media (max-width:1040px){
  .work{grid-template-columns:1fr}
  .ablage{position:static;order:2}
  .bench{order:1}
}
@media (max-width:720px){
  .lane{grid-template-columns:96px 1fr}
  .lane-wave{height:64px}
  .rack{grid-template-columns:1fr}
  .transport{gap:8px}
  .tr-note{width:100%;order:9}
  .keyring{grid-template-columns:repeat(4,1fr)}
}
@media (prefers-reduced-motion:reduce){
  .lane{transition:none}
}

/* Small shared pieces the studio references. These were used by markup but
   never actually defined, which is the same class of bug as a chip that
   toggles a class with no rule behind it: the code looks right and the screen
   does not change. */
.btn-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:8px 0 0}
.empty-hint{margin:0;padding:14px 0;font-size:12px;text-align:center}
.alert-warn{border:1px solid #f59e0b;border-left-width:3px;border-radius:8px;
  background:rgba(245,158,11,.08);padding:8px 12px;margin:0 0 12px;font-size:13px}
.mod-state.owned{color:var(--red)}
.mod-state.drafted{color:#8b5cf6}

/* Example loader. Opt-in starting points with audio that already exists, which
   is why they are buttons rather than a placeholder: a concrete placeholder
   gets read as prefilled content and then as work someone else did. */
/* The example list lives in the narrow left column now, so the entries stack
   full width instead of flowing as a row of pills across the main area. */
.ex-hint{margin:0 0 8px;font-size:11px;color:var(--muted);line-height:1.45}
.ex-list{display:flex;flex-direction:column;gap:5px}
.ex{display:flex;flex-direction:column;align-items:flex-start;gap:1px;width:100%;
  border:1px solid var(--border);background:var(--panel);border-radius:8px;
  padding:7px 10px;cursor:pointer;text-align:left;color:var(--text)}
.ex:hover{border-color:var(--red)}
.ex-n{font-size:12px;font-weight:600}
.ex-note{font-size:10px;color:var(--muted)}
.arr-legend{margin:0 0 8px;font-size:11px;color:var(--muted)}

/* ── Studio corrections after a review pass ────────────────────────────────
   Each of these was confirmed by measuring the live page, not by reading the
   markup and assuming. */

/* The `hidden` ATTRIBUTE loses to any display rule, so a component that styles
   itself with display:flex is permanently visible no matter what the markup
   says. The undo toast sat over the voice module on every page load. */
.undo-toast[hidden]{display:none}

/* Nine modules in a two-column grid leaves the ninth alone in its row. Span it
   only when it would actually be orphaned, so the six-module instrumental
   layout is unaffected. */
@media (min-width:721px) and (max-width:1180px){
  .rack .mod:last-child:nth-child(odd){grid-column:1 / -1}
}

/* Red was doing two incompatible jobs: "this is selected" on ~13 controls at
   once, and "this is the action". With everything red, nothing reads as the
   action. Selection now shows as a lit control with a red edge; solid red is
   reserved for the transport and the two buttons that spend GPU time. */
.seg button.on{background:var(--panel);color:var(--text);font-weight:600;
  box-shadow:inset 0 -2px 0 var(--red)}
.chipline button.on{background:var(--panel);border-color:var(--red);
  color:var(--text);font-weight:600}
.keyring button.on{background:var(--panel);border-color:var(--red);
  color:var(--text);font-weight:700;box-shadow:inset 0 -2px 0 var(--red)}
.quality button.on{background:var(--panel);color:var(--text);font-weight:600;
  box-shadow:inset 0 -2px 0 var(--red)}

/* "2:34  (aus den Teilen)" did not fit its box. The qualifier is not part of
   the value, so it stops being crammed into the input. */
.bake-len{flex:0 0 132px}
.bake-len input{font-family:var(--mono);text-align:left}
.len-note{font-size:10px;color:var(--muted);display:block;margin-top:2px}

/* A block narrower than its own label rendered as "INT…". Below 60px the hue
   and the tooltip carry it; an ellipsis carries nothing. */
.blk.narrow .blk-l,.blk.narrow .blk-t{display:none}
.blk{height:68px}

/* Standby action bar. Only rendered while the real Bake card is off screen, so
   the page never shows two render buttons at once. */
.sticky-go{position:sticky;bottom:0;z-index:30;display:flex;align-items:center;gap:12px;
  margin:0 0 -6px;padding:9px 14px;background:var(--panel);
  border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-md)}
.sticky-go[hidden]{display:none}
.sg-t{flex:1 1 auto;min-width:0;font-size:13px;font-weight:600;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* The deck is the argument the page is making, so give it room to make it. */
.lane-wave{height:104px}
@media (max-width:720px){.lane-wave{height:72px}}

/* Placeholders must be unmistakably placeholders. The title sits at 20px bold
   at the top of the page, so its grey hint read as an actual song title; the
   same doubt then applies to every other empty field. Italic plus a lower
   contrast says "nothing here yet" without a label having to say it. */
.studio2 ::placeholder{font-style:italic;opacity:.75}
.st-title::placeholder{font-weight:400;font-style:italic}
/* An empty title also needs to look like a field you can type in. */
.st-title:placeholder-shown{border-bottom:1px dashed var(--border)}

/* When content came from an example it is REAL text, not a hint, so it is
   labelled as borrowed and can be cleared in one click. */
.ex-active{display:flex;align-items:center;gap:10px;margin:10px 0 0;padding:7px 12px;
  border:1px solid var(--red);border-left-width:3px;border-radius:8px;
  background:var(--panel-2);font-size:12px}
.ex-active[hidden]{display:none}
.ex-active .st-spacer{flex:1 1 auto}

/* ── Second review pass, all four confirmed by measurement ─────────────────*/

/* THE SAME BUG AS THE UNDO TOAST, in a second place: `.lane-empty` styles
   itself `display:flex`, which beats the `hidden` attribute, so the "nothing
   loaded yet" caption stayed on screen ON TOP of a loaded waveform. Rather than
   patch the third instance later, make the attribute win everywhere in here. */
.studio2 [hidden],.sticky-go[hidden],.undo-toast[hidden]{display:none}

/* The standby action bar was `position:sticky` on an element that sits at the
   very end of the document, so it rendered across the middle of the rack and
   put a SECOND red render button on the page. It is an overlay; it should have
   been fixed positioning from the start. */
.sticky-go{position:fixed;left:50%;transform:translateX(-50%);bottom:16px;
  width:min(660px,calc(100vw - 48px));margin:0}

/* A studio should use the screen it is given. 1240 px centred in a 1970 px
   window left ~350 px of dead black on either side and made the page read as a
   centred article rather than a workspace. */
.studio2{max-width:1600px}
/* Scoped to the studio page only: the 1100px site container is correct for
   every other page and this is the one screen that wants the width. */
.content.content-studio{max-width:1640px}

/* Three tabs in a 250 px column: shrink the type rather than let the labels
   ellipsise into "Beis…". */
.ablage-tabs .atab{font-size:11px;padding:8px 4px;letter-spacing:-.01em}

/* ── Duplicated parts and the rewrite panel ────────────────────────────────*/

/* A repeat is the same words twice. The hatch says "you are looking at the same
   thing again", so a user editing one is not surprised when the other changes. */
.blk.linked{background-image:repeating-linear-gradient(135deg,
  hsl(var(--h) 45% 50% / .16) 0 6px, hsl(var(--h) 45% 50% / .30) 6px 12px)}
.pc-link,.pc-dup{border:1px solid var(--border);background:var(--panel-2);
  border-radius:6px;font-size:11px;line-height:1;padding:3px 7px;cursor:pointer;
  color:var(--text)}
.pc-link:hover,.pc-dup:hover{border-color:var(--red);color:var(--red)}
.pc-link{border-color:var(--red)}

/* The rewrite panel. The user's own words stay in the textarea above it the
   whole time; nothing here replaces anything until a suggestion is chosen. */
.rw-panel{margin-top:9px;padding:9px 10px;border:1px solid var(--border);
  border-left:3px solid var(--red);border-radius:8px;background:var(--panel-2)}
.rw-lead{margin:0 0 7px;font-size:11px;line-height:1.45}
.rw-angles{margin-bottom:8px}
.rw-angles button{font-size:11px;padding:2px 9px}
.rw-row{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.rw-instr{flex:1 1 200px;min-width:0;font-size:12px}
.rw-wait{margin:8px 0 0;font-size:12px}
.rw-vars{display:flex;flex-direction:column;gap:7px;margin-top:9px}
.rw-var{border:1px solid var(--border);border-radius:7px;background:var(--panel);
  padding:7px 9px}
.rw-head{display:flex;align-items:center;gap:8px;margin-bottom:5px}
.rw-angle{flex:1 1 auto;font-size:10px;text-transform:uppercase;letter-spacing:.5px;
  color:var(--muted)}
.rw-text{font-size:13px;line-height:1.55;white-space:pre-wrap}

/* The toast is now a real feedback channel, not just an undo affordance. */
.undo-toast #undo-btn[hidden]{display:none}

/* The pitch line is the main way into the studio, so it gets the primary
   button and a real progress readout: the model takes 20-40 seconds and a bare
   spinner for that long reads as a hang. */
.pitch-card .btn-primary{flex:0 0 auto;display:inline-flex;align-items:center;gap:8px}
.spin{width:12px;height:12px;border-radius:50%;flex:0 0 auto;
  border:2px solid rgba(255,255,255,.35);border-top-color:#fff;
  animation:spin .8s linear infinite}
.spin[hidden]{display:none}
@keyframes spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.spin{animation-duration:2.4s}}
.draft-status{margin:8px 0 0;padding:7px 11px;border:1px solid var(--border);
  border-left:3px solid var(--red);border-radius:8px;background:var(--panel-2);
  font-size:12px;line-height:1.5}
.draft-status[hidden]{display:none}

/* Download. A finished song you cannot save off the page is not finished. */
.lane-dl{font-size:11px;color:#9a9aa6;text-decoration:none;
  border:1px solid #33333c;border-radius:6px;padding:2px 7px;margin-top:3px;
  display:inline-block;width:fit-content}
.lane-dl:hover{border-color:var(--red);color:var(--red)}
.lane-dl[hidden]{display:none}
.take-dl{display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--border);background:var(--panel);border-radius:5px;
  font-size:11px;padding:1px 6px;cursor:pointer;color:var(--text);
  text-decoration:none;line-height:1.5}
.take-dl:hover{border-color:var(--red);color:var(--red)}

/* The intensity FILL is decoration and must never intercept a pointer: it
   covers up to 98% of a block, so making it interactive meant the blocks with
   the most energy were the ones you could not drag. Only the grip at the top
   edge of the fill takes the pointer. */
.blk-energy{pointer-events:none}
.blk-grip{position:absolute;left:0;right:0;top:-5px;height:11px;cursor:ns-resize;
  pointer-events:auto;display:block}
.blk-grip::after{content:"";position:absolute;left:0;right:0;top:5px;height:2px;
  background:hsl(var(--h) 65% 60% / .95)}
.blk:hover .blk-grip::after{height:3px;top:4px}

/* Real selects. The datalist version showed nothing until you typed, so a list
   of 34 genres was invisible unless you already knew what was in it. */
.combo{display:flex;flex-direction:column;gap:5px}
.combo-sel{width:100%}
.combo-own{width:100%}
.combo-own[hidden]{display:none}

/* Right-edge resize. Every part can be given an explicit length; an
   instrumental or a solo has no lyrics to derive one from, so without this it
   was pinned to a constant and could not be made longer or shorter at all. */
.blk-resize{position:absolute;top:0;right:0;bottom:0;width:9px;cursor:ew-resize;
  pointer-events:auto}
.blk-resize::after{content:"";position:absolute;top:30%;bottom:30%;right:3px;width:2px;
  border-radius:1px;background:hsl(var(--h) 55% 60% / .55)}
.blk:hover .blk-resize::after{background:var(--red)}
.blk.resizing{outline:2px solid var(--red);outline-offset:-2px}

/* The part a probe will render. */
.blk.focused{outline:2px solid var(--red);outline-offset:-2px}
.blk.focused .blk-l{color:var(--red)}
#probe-what{font-weight:700}

/* Drop indicator for the lyric cards, which reorder vertically. */
.partitur{position:relative}
.card-drop-ind{position:absolute;left:0;right:0;height:3px;background:var(--red);
  border-radius:2px;pointer-events:none;top:0}
.pcard.dragging{opacity:.4}

/* Audit trail --------------------------------------------------------------
   The filter is a grid rather than a row of inline fields: seven controls in a
   line wrap into a ragged stack on a narrow window, which is the shape the
   guardrail page already gets wrong. */
.audit-filter{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px;align-items:end;margin-top:12px}
.audit-filter .field-lbl{display:flex;flex-direction:column;gap:4px;margin:0}
/* A text input and a select do not agree on their own height, so a row mixing
   the two sits 2px out of line. Stating the height once settles it. */
.audit-filter input,.audit-filter select{width:100%;height:38px;box-sizing:border-box}
.audit-filter__wide{grid-column:1/-1}
.audit-filter__actions{grid-column:1/-1;display:flex;gap:8px;align-items:center}
.audit-resulthead{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}
.audit-resulthead h3{margin:0}
/* The generated recap is prose, so it gets a reading measure rather than the
   full panel width, and a rule down the side to say it was written, not logged. */
.audit-recap{max-width:72ch;line-height:1.55;border-left:3px solid var(--red);padding:2px 0 2px 14px;margin:10px 0}
.audit-recap p{margin:.5em 0}.audit-recap h3,.audit-recap h4,.audit-recap h5{margin:.8em 0 .3em}.audit-recap ul{margin:.4em 0;padding-left:20px}.audit-recap code{font-family:var(--mono,monospace);font-size:.92em}
/* audit: collapsible person groups on the day page, collapsible tool calls in a session */
.audit-person{border:1px solid var(--border,#2a2f3a);border-radius:8px;margin:8px 0;overflow:hidden}
.audit-person>summary{cursor:pointer;padding:9px 12px;list-style:none;user-select:none}
.audit-person>summary::-webkit-details-marker{display:none}
.audit-person>summary::before{content:"▸";display:inline-block;width:1em;color:var(--muted,#8b94a3)}
.audit-person[open]>summary::before{content:"▾"}
.audit-person>summary:hover{background:var(--panel-2,rgba(127,138,153,.08))}
.audit-person .data-table{margin:0}
.audit-tool{margin:2px 0}
.audit-tool>summary{cursor:pointer;font-family:var(--mono,monospace);font-size:.9em;color:var(--muted,#8b94a3);padding:2px 0;list-style:none}
.audit-tool>summary::-webkit-details-marker{display:none}
.audit-tool>summary::before{content:"▸ ";color:var(--muted,#8b94a3)}
.audit-tool[open]>summary::before{content:"▾ "}
.audit-tool[open]>summary{color:var(--fg,inherit)}
.audit-highlights{margin:6px 0 10px;padding-left:18px}
.audit-highlights li{margin:4px 0}
/* One mail subscription: the switch and its address list belong on one line, and the line has to
   survive a narrow window without the addresses ending up under the switch label. */
.audit-sub{display:grid;grid-template-columns:minmax(220px,auto) 1fr;gap:10px 18px;align-items:center;padding:10px 0;border-bottom:1px solid var(--border)}
.audit-sub:last-of-type{border-bottom:none}
.audit-sub__to{display:flex;flex-direction:column;gap:4px;margin:0}
.audit-sub__to input{width:100%;height:38px;box-sizing:border-box}
.audit-sub p{grid-column:1/-1;margin:0}
@media (max-width:720px){.audit-sub{grid-template-columns:1fr}}

/* The session transcript -----------------------------------------------------
   Four kinds of row, and they must be tellable apart at a glance: what a person asked, what the
   assistant answered, what was actually run, and what somebody allowed. */
.talk{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:12px}
.talk__row{border-left:3px solid var(--border);padding:2px 0 2px 12px}
.talk__row--user{border-left-color:var(--red)}
.talk__row--assistant{border-left-color:#8a8d94}
.talk__row--tool{border-left-color:#2f6f4f}
.talk__row--decision{border-left-color:#9a6a00}
.talk__meta{display:flex;gap:10px;align-items:baseline;font-size:12px;margin-bottom:4px}
.talk__who{font-weight:600}
.talk__text{white-space:pre-wrap;line-height:1.5;max-width:80ch}
.talk__decision{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
/* A terminal, drawn as one. Black on light grey in both themes, the same as everywhere else in
   the product, so a command and its output read as machine text rather than as prose. */
.term{background:var(--codebg,#e6e6e6);color:var(--code,#000);border-radius:3px;overflow:hidden;font-family:ui-monospace,"JetBrains Mono",Consolas,monospace;font-size:12.5px}
.term__cmd{padding:8px 10px;border-bottom:1px solid rgba(0,0,0,.12);font-weight:600;white-space:pre-wrap;word-break:break-word}
.term__prompt{color:#b00500;margin-right:6px}
.term__out{margin:0;padding:8px 10px;white-space:pre;overflow-x:auto;line-height:1.45;max-height:420px}
.term__cut{padding:4px 10px;border-top:1px solid rgba(0,0,0,.12);font-size:11px;opacity:.75}
.replay-controls{display:flex;gap:8px;align-items:center}
.replay-status{font-variant-numeric:tabular-nums}
.talk__row--hidden{display:none}
.talk__row--current{background:rgba(225,6,0,.05)}
/* Tool calls and permission decisions dominate an agentic session, so the transcript hides them by
   default and a toggle in the head brings them back. What a person asked and what the assistant
   answered stay; the technical rows are one click away, and sit a touch smaller once revealed. */
.talk--collapsed .talk__row--tool,.talk--collapsed .talk__row--decision{display:none}
.talk__row--tool{font-size:.92em}

/* ------------------------------------------------------------------ *
 * Managing one person
 *
 * Editing a member used to be five stacked forms inside the last cell of the
 * member table, which is right-aligned and about as wide as a button. These are
 * the pieces of the page that replaced it.
 * ------------------------------------------------------------------ */

/* Two panels side by side that stay readable when one is much shorter. */
.two-cols{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start;margin-top:16px}
@media (max-width:900px){.two-cols{grid-template-columns:1fr}}

/* A row of radio choices, stacked, each with room to breathe. The vision review
   caught these running together: "Wie die Organisation" ended flush against the
   next radio button, so the labels read as one sentence. */
.choice-list{display:flex;flex-direction:column;gap:10px;margin:12px 0 14px}
.choice{display:flex;align-items:flex-start;gap:10px;cursor:pointer;line-height:1.4}
.choice input[type="radio"]{margin:2px 0 0;flex:0 0 auto}
.choice>span{display:block}
.choice .muted{margin-left:6px}

/* One line of state next to the button that changes it. */
.row-form{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:12px}
.row-form .state-line{display:flex;align-items:center;gap:8px}
.ok-inline{color:#2f7a4d;font-weight:600}
:root[data-theme="dark"] .ok-inline{color:#5fd08a}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .ok-inline{color:#5fd08a}}

/* The panel that deletes things sits apart from the ones that do not. */
.panel-danger{border-color:var(--red-dim)}
.panel-danger h3{color:var(--red)}

/* Second-level navigation, under the main row rather than beside it. */
.subnav-2{margin:-14px 0 20px;border-bottom:none;gap:4px}
.subnav-2 a{font-size:13px;font-weight:600;padding:6px 12px;border-bottom-width:2px}

/* ------------------------------------------------------------------ *
 * Table legibility
 *
 * All from the vision pass over the admin pages: a role badge that broke across
 * two lines while its neighbours stayed on one, a column header that wrapped,
 * and a model list long enough to triple the height of its row.
 * ------------------------------------------------------------------ */
.tag{white-space:nowrap}
.data-table th{white-space:nowrap}
/* A long list of model names is reference material, not the point of the row.
   It gets a ceiling and wraps inside it instead of setting the row's height. */
.data-table td.models{max-width:280px;font-size:12px;line-height:1.35}
.data-table td.who{white-space:nowrap}
.data-table td.who .muted{display:block;font-size:12px}

/* A text field and a dropdown side by side must be the same height.
 *
 * They were 36px and 38px, because a select sizes itself from the font metrics of
 * its options while an input sizes itself from its padding. Two pixels is enough
 * to read as a misaligned row, and every create-user form has both in one line.
 * Measured, not guessed: tests/ui_measure.mjs reports the heights per form row. */
input,select,textarea{line-height:1.35;box-sizing:border-box}
.form-grid input,.form-grid select,.form-grid textarea{height:38px;width:100%}
.form-grid textarea{height:auto;min-height:76px}
.form-grid input[type="checkbox"],.form-grid input[type="radio"]{height:auto;width:auto}

/* A model list is reference material. It gets two lines and a tooltip for the
 * rest, rather than setting the height of everything beside it: one member with
 * five models made their row five lines tall and the table look broken. */
.data-table td.models{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}

/* A row of tabs must never scroll sideways.
 *
 * It used to be `flex-wrap:nowrap; overflow-x:auto`, so once a section had seven
 * tabs the navigation grew its own horizontal scrollbar inside a content column
 * that was itself only 1100px wide on a 2560px screen: a scrollbar under the
 * menu with empty space on both sides of it. Tabs wrap to a second line now, and
 * the content column is wider, so on any normal screen they stay on one. */
.subnav{overflow:visible}
.subnav a{flex:0 0 auto}

/* A table wider than the screen scrolls itself, instead of scrolling the page.
 *
 * The nine-column user tables pushed the whole document sideways below about
 * 1300px, which is what put a horizontal scrollbar under the navigation on a
 * laptop. The table keeps its own bar and the page stays put. */
@media (max-width:1320px){
  .data-table{display:block;overflow-x:auto;max-width:100%}
  .data-table thead,.data-table tbody{display:table;width:100%}
}

/* Panels never sit flush against each other.
 *
 * `.panel` carried padding but no outside spacing, so two sections in a row met
 * at 0px and read as one box with a line through it. Every /team page with more
 * than one panel had it. A grid or flex parent supplies its own gap, so the
 * margin is only applied where the panel is a plain block in the flow.
 * tests/ui_measure.mjs fails on any stacked pair closer than 8px. */
.panel{margin-bottom:18px}
.panel:last-child{margin-bottom:0}
.two-cols>.panel,.admin-cols>.panel,.chart-grid>.panel{margin-bottom:0}
/* A table followed by a panel, and a heading after either, need the same air. */
.data-table+.panel,.data-table+section,table+.panel{margin-top:18px}
.panel+h2,.panel+h3,.data-table+h2,.data-table+h3{margin-top:26px}

/* The rule for what is removed from a session transcript.
 *
 * Presets first, in plain sentences, because the people who set this are
 * administrators and works councils, not prompt engineers. The text itself sits
 * behind a disclosure so it is there when somebody needs it and not in the way
 * when they do not. */
.choice .choice-help{display:block;color:var(--muted);font-size:12px;margin-top:2px;max-width:62ch}
.prompt-text{margin:8px 0 14px}
.prompt-text>summary{cursor:pointer;color:var(--muted);font-size:13px;padding:6px 0;list-style:none}
.prompt-text>summary::-webkit-details-marker{display:none}
.prompt-text>summary::before{content:"▸ ";display:inline-block;transition:transform .12s}
.prompt-text[open]>summary::before{content:"▾ "}
.prompt-text>summary:hover{color:var(--text)}
.prompt-text textarea{width:100%;font-size:12px;line-height:1.5;min-height:280px}
.note-floor{font-size:12px;border-left:3px solid var(--border);padding-left:10px}

/* A form's buttons, when the form's fields live in panels and the buttons do not.
 *
 * On the sharing, features and guardrails pages the switches sit in cards with
 * 18px of padding and the Save button sat directly on the page background,
 * flush to the left edge, looking like it belonged to nothing. It is the footer
 * of the form above it, so it gets the same frame and the same inset. Buttons
 * already inside a panel, a table or a dialog are untouched. */
.form-actions:not(.panel .form-actions):not(.modal-body .form-actions):not(.tenant-budget-form .form-actions){
  background:var(--panel);
  border:1px solid var(--border);
  border-radius:var(--radius);
  padding:14px 18px;
  margin-top:0;
  box-shadow:var(--shadow-sm);
}
/* The panel above it already carries the gap, so the footer must not add a second one. */
.panel + .form-actions{margin-top:-2px}


/* ------------------------------------------------------------------ *
 * Subscription seats
 *
 * The allowance meter has three states and only one of them is a problem.
 * Under the default plan nothing happens at 100 percent: work continues and
 * usage beyond the allowance is covered. So "used up" is a calm slate, not
 * red -- red here would announce a fault that does not exist and generate
 * exactly the support call the plan is designed to avoid. Amber is the one
 * that means "look at this", at eighty percent, while there is still time to
 * act.
 *
 * Defined as tokens on :root so both themes get a deliberate value rather than
 * one theme's colour showing through on the other's panel.
 * ------------------------------------------------------------------ */
:root{
  --seat-warn:#b45309;
  --seat-over:#64748b;
}
:root[data-theme="dark"]{
  --seat-warn:#f59e0b;
  --seat-over:#94a3b8;
}
.bar-fill--warn{background:var(--seat-warn)}
.bar-fill--over{background:var(--seat-over)}

/* The meter inside a table cell. `.mini-budget` is a flex row; without a width
 * the track collapses to nothing next to a short percentage and the bar reads
 * as empty at every value. */
.data-table .mini-budget{min-width:150px}
.data-table .mini-budget .bar-track{min-width:90px}

/* Seat tier cards on the admin page: a grid supplies the gap, so the panels
 * inside it drop their own bottom margin the same way .two-cols does. */
.tier-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px;margin-top:14px}
.tier-grid>.panel{margin-bottom:0}
.tier-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:6px}
.tier-head h4{margin:0;font-size:15px;font-weight:700}
.tier-price{font-family:var(--mono);font-size:13px;white-space:nowrap}

/* A row of facts under a heading: label above value, wrapping rather than
 * scrolling, because these sit inside a card that is already narrow. */
.seat-facts{display:flex;flex-wrap:wrap;gap:14px 26px;margin:10px 0 0}
.seat-facts>div{min-width:110px}
.seat-facts dt{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.4px;margin:0}
.seat-facts dd{margin:2px 0 0;font-family:var(--mono);font-size:14px}

/* A form that lives inside a table cell.
 *
 * `.inline-form` carries 18px above and below, which is right on a page and wrong in a row: on the
 * seat roster it made every line 95px tall, so seven people needed a scroll and the dropdowns
 * floated in dead space. tests/ui_measure.mjs reports these as cells four lines high. */
td > .inline-form,
td > form > .inline-form {
  margin: 0;
}

/* A checkbox in a form grid.
 *
 * `.form-grid input` forces 38px, which a checkbox must not take, so the box measured 13px against
 * 38px inputs beside it and the row read as misaligned. The label keeps the row height instead, and
 * the box keeps its own size. */
.form-grid .check-inline input[type='checkbox'],
.form-grid .check input[type='checkbox'] {
  width: auto;
  height: auto;
}
.form-grid > .field-lbl > .check-inline {
  min-height: 38px;
  align-items: center;
}

/* Seat-type cards: the button sits at the foot of the card, not wherever the text above it ended.
 * Three cards with different amounts of detail put three buttons at three heights. */
.tier-grid > .panel {
  display: flex;
  flex-direction: column;
}
.tier-grid > .panel > .editor {
  margin-top: auto;
  padding-top: 10px;
}

/* The package form.
   Two grouped questions that are not a plain grid: "how much usage, how often" is
   three controls on one line, and "what happens at the limit" is two radios with a
   number that belongs to the second of them. Everything else on the page uses
   .form-grid and .field-lbl unchanged. */
.pack-included,
.pack-limit {
  margin: 16px 0 0;
  padding: 12px 14px 14px;
  border: 1px solid var(--border);
  border-radius: 3px;
}
.pack-included legend,
.pack-limit legend {
  padding: 0 6px;
  font-size: 13px;
  color: var(--text);
}
.pack-included__row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: flex-end;
}
.pack-included__row .field-lbl { flex: 1 1 140px; min-width: 0; }
.pack-included .field-help,
.pack-limit .field-help { margin: 8px 0 0; }
#pack-estimate:not(:empty) { margin-top: 8px; }
.pack-limit .check { display: flex; align-items: center; gap: 8px; margin: 6px 0; }
.pack-limit__extra { margin-top: 10px; max-width: 260px; }

/* The person's own allowance, on the dashboard and on the usage page.
   Kept to the tokens the rest of the stylesheet already defines: this card is a
   panel with a meter in it, not a new visual language. */
.plan-card .section-head { align-items: baseline; }
.plan-meter { display: flex; align-items: center; gap: 10px; margin: 4px 0 8px; }
.plan-meter .bar-track { flex: 1 1 auto; }
.plan-meter .bar-val { min-width: 3.5em; text-align: right; }
.plan-line { margin: 0 0 4px; }

/* An allowance whose monthly value exceeds the seat price. Not an error: a
   deliberate loss-leader is a real choice. It just has to be a seen one. */
.pack-over { color: var(--red); font-weight: 600; }

/* Per-system rules on the team features page. One row per rule; the inputs fill their column so a
   long domain or a range is readable without the row growing a scrollbar of its own. */
/* Second setting in the same panel: it needs the separation a panel edge would have given it. */
.hostrules__head{margin:22px 0 2px;padding-top:18px;border-top:1px solid var(--border);font-size:15px}
.hostrules td{vertical-align:middle}
/* The systems are what the rule is about, so that column gets the room; the rest are fixed. */
.hostrules th:first-child,.hostrules td:first-child{width:130px}
.hostrules th:nth-child(3),.hostrules td:nth-child(3){width:270px}
.hostrules th:nth-child(4),.hostrules td:nth-child(4){width:190px}
.hostrules td input[type=text],.hostrules td select{width:100%;min-width:0}
.hostrules th:nth-child(4),.hostrules td:nth-child(4){text-align:left}
/* The remove button's column. Narrow and last, so it never competes with the fields for room. */
.hostrules th.hostrules__x,.hostrules td.hostrules__x{width:34px;text-align:center;padding-left:0;padding-right:4px}
/* The rule in words, under the row it belongs to. A select reading "Sperren" beside a ticked box
   labelled "Verbinden" looks like a contradiction and is not one: the select is the verb, the boxes
   are its scope. Rather than explain that in help text nobody reads at the moment they need it,
   every row says what it does. */
.hostrules__saidrow td{border-top:0;padding-top:0;padding-bottom:8px}
.hostrules__said{font-size:12px;color:var(--muted)}
.hostrules__said--allow{color:var(--ok,#1a7f4b)}
.hostrules__said--deny{color:var(--danger,#b4232a)}
/* A row that can never fire is worth more than a muted note: it looks like a rule and is not one. */
.hostrules__said--warn{color:var(--warn,#a25b00)}
.hostrules__said--quiet{opacity:.65}
.hostrules__addwrap{margin:6px 0 0}
/* The one line that answers "which wins, this or the setting above". */
.hostrules__precedence{margin-top:4px}
.hostrules__acts{white-space:nowrap}
.hostrules__acts .check{display:inline-flex;align-items:center;gap:4px;margin-right:10px;font-size:12px;color:var(--muted)}
.hostrules__acts .check input{margin:0}
/* Checking a rule is a question, not a setting, so it does not look like the fields above it: its
   own bordered box, and the answer lands inside it rather than as a flash message at the top of a
   page the fields are a long way down. */
.trybox{margin-top:22px;padding:16px;border:1px solid var(--border);border-radius:10px;background:var(--panel-2)}
.trybox__head{display:flex;flex-direction:column;gap:2px;margin-bottom:12px}
.trybox__head strong{font-size:14px}
.trybox__row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:12px}
.trybox__row .field-lbl{flex:1 1 190px;min-width:0;margin:0}
.trybox__row .field-lbl:has(select){flex:0 0 190px}
.trybox__row .field-lbl input,.trybox__row .field-lbl select{width:100%}
.trybox__row button{flex:0 0 auto}
.trybox__answer{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:14px;padding-top:12px;border-top:1px solid var(--border)}
.trybox__who{font-size:13px;font-weight:600}
.trybox__answer .field-help{margin:0;flex:1 1 100%}

/* ── Live dashboard ───────────────────────────────────────────────────────
   Two sizes from one markup. The admin view is ordinary; `.live.tv` swaps in a
   type scale derived from viewing distance rather than taste.

   ANSI/AVIXA V202.01 DISCAS: a glyph must subtend ~17 arcminutes to read
   comfortably. Pixels per arcminute for a 55" 1080p panel at 4 m is 1.835, so
   the floor is 17.25 * 1.835 / 0.72 = 44px and the hero numbers are 140px.
   That also caps the design: 1080px / (56px * 1.35) is fourteen rows on the
   WHOLE screen, which is why every table here shows five and why there are
   seven panels rather than twenty. */
.live{display:flex;flex-direction:column;gap:18px}
.live-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
.live-title{margin:0;font-size:22px;font-weight:700;letter-spacing:-.01em}
.live-sub{color:var(--muted);font-size:12px;margin-top:2px}
.live-sub.stale{color:#d29922}
.live-head-right{display:flex;align-items:center;gap:12px}
.live-clock{font-family:var(--mono);color:var(--muted);font-size:13px}
.kpi-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
.kpi{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:16px 18px}
.kpi-label{color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.06em}
.kpi-value{font-size:34px;font-weight:700;line-height:1.1;margin-top:4px;
  font-variant-numeric:tabular-nums}
.kpi-sub{color:var(--muted);font-size:12px;margin-top:2px;font-variant-numeric:tabular-nums}
.kpi-sub.bad{color:#f85149}

.live-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}
/* A class selector beats the browser's own `[hidden]{display:none}`, so the
   rule above quietly un-hides every dashboard the script has hidden: all six
   were laid out at once, sharing the height, and each chart ended up 79px tall
   with nothing drawable in it. Restore the meaning of the attribute. */
.live-grid[hidden]{display:none}
.live-panel{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:16px 18px}
.live-panel h3{margin:0 0 10px;font-size:13px;font-weight:600;color:var(--muted);
  text-transform:uppercase;letter-spacing:.06em}
.live-span2{grid-column:span 2}
/* A panel heading with a control on the right. The heading keeps its own
   margin-less form so the row height is set by the button, not by an h3
   margin fighting a flex baseline. */
.live-panel-head{display:flex;align-items:baseline;justify-content:space-between;
  gap:12px;margin:0 0 10px}
.live-panel-head h3{margin:0}
.live-more{appearance:none;background:none;border:1px solid var(--border);
  color:var(--muted);font:inherit;font-size:11px;font-weight:600;
  padding:4px 10px;border-radius:999px;cursor:pointer;line-height:1;
  white-space:nowrap;transition:color .15s,border-color .15s,background .15s}
.live-more:hover{color:var(--text);border-color:var(--muted)}
.live-more.is-on{color:#fff;background:var(--red);border-color:var(--red)}
/* `.chart` elsewhere in the portal carries its own panel frame. Inside a
   live panel that draws a second border inside the first one, so it is undone
   here rather than renamed, which would touch every other page that uses it. */
.live .chart{width:100%;height:220px;background:none;border:0;padding:0;border-radius:0}
/* Ten people in 220px gave each row 19px, thinner than the label beside it, so
   the bars read as a texture rather than as ten quantities. The height is set
   from the row count in live.js; this is the pre-paint default so the panel
   does not visibly jump on first draw. */
.live .chart-people{height:340px}
/* The bubbles carry names as labels, so they need vertical room the same way
   the bars do: at 220px a clustered team and its outlier shared thirteen
   pixels. */
.live .chart-context{height:300px}
.live .chart-map{height:340px}

.live-table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums}
.live-table td{padding:6px 8px;border-bottom:1px solid var(--border);font-size:13px}
.live-table tr:last-child td{border-bottom:none}
/* A model name is long ("deepseek-v4-flash-vision-exp") and must never wrap:
   on the wall one wrapped name grew to two rows and pushed the other five
   models out of a panel that clips its overflow, so the table showed a single
   model and looked like there was only one. It truncates instead, and the
   numbers beside it keep their room. */
/* `max-width:0` plus a percentage width is the standard way to make a table
   cell truncate. It only works for ONE flexible column though: the feed has a
   model column AND a person column, both asking for 99%, and the second
   collapsed to a single character. So the first text column takes the lion's
   share and any further one gets the rest. */
.live-table td.n{color:var(--text);max-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.live-table td.n:first-of-type{width:62%}
.live-table td.n:first-of-type ~ td.n{width:38%}
.live-table td.v{white-space:nowrap}
.live-table td.v{text-align:right;color:var(--muted);font-family:var(--mono);font-size:12px}
.live-table .ok{color:#3fb950}
.live-table .bad{color:#f85149}
.live-empty{color:var(--muted);font-style:italic}
.live-foot{display:flex;justify-content:space-between;color:var(--muted);font-size:11px}
.live-attr a{color:var(--muted)}

/* ── TV mode ─────────────────────────────────────────────────────────────── */
body.tv{background:var(--bg);margin:0;overflow:hidden}
.live.tv{
  /* 3% inset for TV overscan; without it a panel in its default picture mode
     eats the outer rows entirely. */
  padding:24px 40px;box-sizing:border-box;gap:16px;
  transition:transform 2000ms linear;will-change:transform}
/* Everything else about the wall scale lives in ONE block further down. There
   used to be two, and the older one carried a 118px hero and a `td.v` rule
   that outranked the newer `td` by specificity, so half of a resize landed in
   the copy that did not win. */
/* Tabs. At a desk they are the control; on the wall they are a progress
   strip telling the room which of the six dashboards is currently up. */
.live-tabs{display:flex;gap:4px;flex-wrap:wrap;justify-content:center;flex:1}
.live-tab{appearance:none;background:none;border:0;border-bottom:2px solid transparent;
  color:var(--muted);font:inherit;font-size:13px;font-weight:600;
  padding:6px 12px;cursor:pointer;white-space:nowrap;border-radius:6px 6px 0 0}
.live-tab:hover{color:var(--text);background:var(--panel-2)}
.live-tab.is-on{color:var(--text);border-bottom-color:var(--red)}
.live-brand{min-width:150px}
.live-head-right{display:flex;align-items:center;gap:12px;min-width:150px;
  justify-content:flex-end}

/* The wide detail tables carry a header row; the mini ones on the overview
   do not, because at a glance the columns are obvious from the numbers. */
.live-table thead th{font-size:11px;font-weight:600;color:var(--muted);
  text-transform:uppercase;letter-spacing:.05em;text-align:left;
  padding:0 8px 6px;border-bottom:1px solid var(--border);white-space:nowrap}
.live-table thead th.v{text-align:right}
.live-scroll{overflow:auto;max-height:100%}
.live-tall .chart-map{height:460px}

.live-warn{list-style:none;margin:0;padding:0}
.live-warn li{display:flex;gap:10px;align-items:baseline;padding:7px 0;
  border-bottom:1px solid var(--border)}
.live-warn li:last-child{border-bottom:none}
.live-warn li::before{content:"";width:8px;height:8px;border-radius:50%;flex:none}
.live-warn b{font-weight:600;min-width:170px;color:var(--text)}
.live-warn span{color:var(--muted);font-size:13px}
.live-warn .warn-warn::before{background:var(--red)}
.live-warn .warn-info::before{background:#d29922}
.live-warn .warn-none{color:var(--muted);font-style:italic}
.live-warn .warn-none::before{background:#3fb950}

/* The carousel control. On a wall the dashboards advance by themselves; at a
   desk this starts and stops that, and the bar under the tabs shows how long
   the current one has left so the next switch is expected rather than sudden. */
.live-play{appearance:none;background:none;border:1px solid var(--border);
  color:var(--muted);font:inherit;font-size:12px;font-weight:600;
  padding:5px 11px 5px 8px;margin-left:8px;border-radius:999px;cursor:pointer;
  display:inline-flex;align-items:center;gap:7px;line-height:1;
  transition:color .15s,border-color .15s,background .15s}
.live-play:hover{color:var(--text);border-color:var(--muted)}
.live-play.is-on{color:#fff;background:var(--red);border-color:var(--red)}
.play-ico{width:0;height:0;border-style:solid;border-width:5px 0 5px 8px;
  border-color:transparent transparent transparent currentColor;flex:none}
/* The same element becomes a pause bar when the carousel is running, so the
   button always shows what pressing it will DO next. */
.live-play.is-on .play-ico{border:0;width:8px;height:10px;
  border-left:3px solid currentColor;border-right:3px solid currentColor;
  background:none}
.rotbar-track{display:block;width:100%;height:2px;background:var(--border);
  border-radius:2px;overflow:hidden;margin-top:6px;opacity:0;
  transition:opacity .2s}
.rotbar-track i{display:block;height:100%;width:0;background:var(--red);
  border-radius:2px}

/* Switching dashboards fades and lifts the new one in. 260ms is long enough to
   read as a transition and short enough that nobody waits for it. */
@keyframes live-in{from{opacity:0;transform:translateY(10px)}
                   to{opacity:1;transform:none}}
.live-grid:not([hidden]){animation:live-in .26s cubic-bezier(.22,.8,.3,1) both}

/* The dot breathes while data is arriving and goes solid amber when it stops,
   so "is this thing still live" is answerable from across the room. */
@keyframes live-pulse{0%,100%{opacity:1;transform:scale(1)}
                      50%{opacity:.45;transform:scale(.82)}}
.live-dot{display:inline-block;width:10px;height:10px;border-radius:50%;
  background:var(--red);margin-left:10px;vertical-align:middle;
  animation:live-pulse 2s ease-in-out infinite}
.live-dot.stale{background:#d29922;animation:none}
.live-sub.stale{color:#d29922}

@media (prefers-reduced-motion: reduce){
  .live-grid:not([hidden]){animation:none}
  .live-dot{animation:none}
}

/* A TV NEVER SCROLLS, and the height budget decides the design.

   1080 lines, minus 48 of page padding, ~96 for the header and tab strip, 150
   for the KPI row, 28 for the footer and 48 of gaps, leaves roughly 700 for the
   panels. That is two rows. Which is why each dashboard holds at most two rows
   and why there are six dashboards rather than one long page: anything below
   the fold on a wall does not exist, because nobody can scroll it.

   The hide rule sits AFTER `.live.tv .live-panel` on purpose. Both selectors
   have the same specificity, so the later one wins, and the panel rule sets
   `display:flex`. Written the other way round the panels never disappear. */
.live.tv{height:100vh;display:flex;flex-direction:column;overflow:hidden}
.live.tv .live-panel{min-height:0;display:flex;flex-direction:column;overflow:hidden}
.live.tv .tv-hide{display:none}

/* The KPI row squeezes everything below it, so it is pinned rather than left to
   size itself: one label wrapping to two lines steals 60px from the panels. */
.live.tv .kpi{padding:10px 16px}
.live.tv .kpi-label{font-size:17px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.live.tv .kpi-value{font-size:42px;margin-top:0;line-height:1.05}
.live.tv .kpi-sub{font-size:15px;margin-top:0}
.live.tv .live-title{font-size:24px}
.live.tv .live-sub{font-size:13px;margin-top:0}
.live.tv .live-clock{font-size:20px}
.live.tv .live-foot{font-size:13px}
.live.tv .live-tabs{gap:2px}
.live.tv .live-tab{font-size:14px;padding:4px 11px;border-bottom-width:2px}
.live.tv .live-play{display:none}
.live.tv .rotbar-track{height:3px;margin-top:6px}
.live.tv .live-dot{width:11px;height:11px;margin-left:10px}
.live.tv .live-brand,.live.tv .live-head-right{min-width:0}

/* `grid-auto-rows:minmax(0,1fr)` is load-bearing, not tidiness. The panels
   hold charts sized `flex:1;height:auto`, and a chart can only resolve that
   against a parent with a DEFINITE height. Left on the default `auto` the row
   sizes to its content, the content sizes to the row, and the circle resolves
   at zero: every canvas came out 1748x0 and ECharts threw reading a null
   coordinate system. Dividing the free space into equal rows breaks the loop. */
.live.tv .live-grid{flex:1;min-height:0;grid-auto-rows:minmax(0,1fr)}
/* On the three leaderboard dashboards the wall shows the TABLE ONLY. Sharing
   the height with the bar chart beside it left the chart 92px tall, which
   cannot carry eight labelled bars, and the table three rows of seven. The
   table already ranks by requests and prints the numbers, so at four metres it
   is the better of the two; the chart keeps its place at a desk, where the page
   can scroll. Those tables also run to eight columns, hence the denser row. */
/* The ranking chart is the headline of its dashboard and needs room for one
   labelled bar per model; the two measures under it are the detail. */
.live.tv .live-grid[data-panel="models"]{grid-template-rows:1.3fr 1fr}
/* A scatter needs vertical room in a way a bar list does not: the bars read
   fine squashed, the bubbles and their axis do not. */
.live.tv .live-grid[data-panel="people"]{grid-template-rows:0.85fr 1.15fr}
/* A world map is 2:1. Stacked full-width on a wall it gets a 1748x250 slot,
   fits itself to the height and leaves the continents a stamp in the middle of
   an empty letterbox. Side by side it gets roughly 870x570, which the map fills
   properly, and the country bars read fine in a narrow column. */
.live.tv .live-grid[data-panel="geo"]{grid-template-rows:1fr}
.live.tv .live-grid[data-panel="geo"] .live-span2{grid-column:auto}
/* The traffic graph likewise carries the overview, and the two rings under it
   read fine at two thirds of its height. */
.live.tv .live-grid[data-panel="overview"]{grid-template-rows:1fr 1.15fr}
.live.tv .live-wide td{font-size:15px;padding:2px 8px}
.live.tv .live-wide thead th{font-size:11px}
.live.tv .chart,.live.tv .chart-map{flex:1;height:auto;min-height:0}
.live.tv .live-table{flex:1}
.live.tv .live-table td{font-size:16px;padding:3px 9px;line-height:1.3}
.live.tv .live-table thead th{font-size:12px;padding:0 9px 5px}
.live.tv .live-warn b{font-size:17px;min-width:190px}
.live.tv .live-warn span{font-size:16px}
.live.tv .live-scroll{overflow:hidden}

/* The two window pickers. Small and quiet: they are settings, not the content,
   and on a wall they are hidden entirely because nobody can reach them. */
.live-pick{display:inline-flex;align-items:center;gap:6px;font-size:11px;
  color:var(--muted);white-space:nowrap}
.live-pick select{font:inherit;font-size:12px;color:var(--text);
  background:var(--panel);border:1px solid var(--border);border-radius:6px;
  padding:3px 6px;cursor:pointer}
.live-pick select:hover{border-color:var(--muted)}
.live.tv .live-pick{display:none}

/* Price lines inside a table cell. A model has up to three prices (input,
   output, cached) and cramming them into one comma-separated string is how the
   cache price ended up invisible on /models: the eye reads "a price" and stops.
   One labelled row each, the label quiet, the number in the mono face used for
   every other figure in the portal, and the pre-discount price struck through
   beside it when a discount applies. */
.price-lines{list-style:none;margin:0;padding:0;display:grid;gap:2px 14px;
  /* max-content, not auto: an `auto` track stretches to the cell and pushed the
     label and its number to opposite ends of a wide column, so the eye had to
     travel to pair them up. */
  grid-template-columns:max-content max-content;
  justify-content:start;align-items:baseline}
.price-lines li{display:contents}
.price-label{color:var(--muted);font-size:11px;white-space:nowrap}
.price-value{font-family:var(--mono);font-size:12px;white-space:nowrap;
  color:var(--text)}
.price-list{color:var(--muted);font-size:11px;opacity:.75;margin-left:8px;
  white-space:nowrap}
