/* The customer portal.
 *
 * The prototype is no longer in the tree — this file and internal/portal's
 * templates are the reference now. Every citation of it here and there is a git
 * object: `git show 14ba1e8:web/dashboard/styles.css`.
 *
 * This is 14ba1e8:web/dashboard/styles.css rebuilt on the shared tokens in aifu.css:
 * --cobalt became var(--brand), --cobalt-dark var(--brand-ink), --text and the
 * text uses of --navy var(--ink), --font-sans/--font-mono var(--sans)/
 * var(--mono), and --surface --border --ok --ok-bg --warn --warn-bg --err
 * --err-bg --info --info-bg --coral now live in aifu.css rather than here. The
 * design's class names are kept verbatim, so the prototype and the served
 * portal diff cleanly and a rule can be traced back to the file it came from.
 *
 * One split the brief did not foresee: the design's --navy is two things, a
 * text colour and the fixed dark surface under the sidebar, the code blocks,
 * the dialog headers and the avatar. Its dark theme flips --text and leaves
 * --navy alone, so mapping both onto --ink would have turned the sidebar
 * near-white the moment B(d) wires the toggle. The surfaces use --bg-dark
 * instead, which aifu.css already defines as the same #071B4A and which does
 * not flip.
 *
 * Loaded AFTER aifu.css, and that order matters: aifu.css is not purely
 * prefixed — it styles html, body, a, img and h1/h2/h3 globally at landing
 * sizes (aifu.css:64-89), which on a dashboard makes every card heading a
 * 50px hero. The block below re-declares exactly those elements with the
 * design's values; the headings are then scoped the way the design scopes
 * them, under .page-head, .card and .auth-card.
 *
 * Dropped from the prototype: .screen/.active/body.auth-mode (the hash
 * router's artefacts — every page here is its own URL) and .sample-flag
 * (there is no sample data). The html.dark rules and .theme-toggle were
 * carried unused through B(b) and B(c) and are live from B(d) on: the button
 * in the topbar is the one thing the port was missing.
 */

/* ---- the one token this file owns ---------------------------------------- */

/* The ground the cards float on, and the only colour the portal defines for
   itself rather than taking from aifu.css. --bg is the landing's near-white,
   which is the same colour as a card: on a page of white cards with no borders
   left on them, that is a page with nothing on it. A cool grey-blue is what the
   design puts under them, and it is a token because the dark theme answers it
   at the bottom of this file with the page colour it already had.

   Declared here rather than in aifu.css: this file is loaded by the portal
   alone, and the public site's ground is not changing. */
:root{--shell:#F4F6FB}

/* ---- resets aifu.css would otherwise win --------------------------------- */

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:auto;scroll-padding-top:0}
html,body{margin:0;padding:0}
body{background:var(--shell);color:var(--ink);font-family:var(--sans);font-size:16px;line-height:1.6}
img{max-width:100%}
a{color:var(--brand-ink);text-decoration:underline}
a:hover{color:var(--ink)}
h1,h2,h3{font-size:inherit;font-weight:600;line-height:1.3;margin:0}
/* The focus ring is the design's, and it has to out-specify aifu.css's
   a:focus-visible / button:focus-visible (0,1,1) — hence the descendant. */
body :focus-visible{outline:3px solid var(--sky);outline-offset:2px}

/* ---- utilities ----------------------------------------------------------- */

.skip-link{position:absolute;left:-9999px;top:0;background:var(--bg-dark);color:#fff;padding:12px 18px;z-index:300;font-weight:600}
.skip-link:focus{left:12px;top:12px}
.num{font-variant-numeric:tabular-nums}
.mono{font-family:var(--mono);font-size:0.94em}
.muted{color:var(--muted)}
.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* ---- shell --------------------------------------------------------------- */

.app{display:grid;grid-template-columns:240px minmax(0,1fr);min-height:100vh}
.sidebar{background:var(--bg-dark);color:#DCE7F7;display:flex;flex-direction:column}
.brand{background:#fff;border-bottom:1px solid var(--border);padding:16px 18px;display:flex;align-items:center;justify-content:space-between;gap:10px}
.brand img{height:40px;width:auto;display:block}
.brand .env{color:var(--brand-ink);font-weight:700;letter-spacing:0.14em;font-size:11.5px;border:1px solid rgba(8,104,242,0.35);padding:4px 9px;background:var(--info-bg);font-family:var(--mono)}
.nav{display:flex;flex-direction:column;padding:12px 0}
.nav a{display:flex;align-items:center;gap:12px;min-height:50px;padding:0 20px;color:#C4D4F0;text-decoration:none;border-left:3px solid transparent;font-weight:500;font-size:15.5px}
.nav a:hover{background:rgba(67,185,255,0.12);color:#fff}
.nav a[aria-current="page"]{background:rgba(8,104,242,0.28);border-left-color:var(--sky);color:#fff}
.nav a svg{flex:none;color:#8FB3E8}
.nav a[aria-current="page"] svg{color:var(--sky)}
.sidebar .foot{margin-top:auto;padding:14px 18px;border-top:1px solid rgba(220,231,247,0.14);display:flex;flex-direction:column;gap:6px;font-size:13px;color:#8FB3E8}
/* The design carried this column as an inline style on an anonymous div. */
.shell-col{min-width:0;display:flex;flex-direction:column}

/* ---- topbar -------------------------------------------------------------- */

.topbar{position:sticky;top:0;z-index:70;background:var(--surface);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;padding:10px clamp(14px,2.5vw,28px);min-height:62px}
.menu-btn{display:none;min-width:44px;min-height:44px;background:transparent;border:1px solid var(--border);border-radius:8px;color:var(--ink);cursor:pointer;align-items:center;justify-content:center}
/* The bar itself is left alone — same white, same hairline under it — but the
   controls standing in it are the same controls as everywhere else, and three
   square ones beside a row of soft-cornered buttons is the spreadsheet look
   this pass is removing. The chip's own background stays the page's old
   near-white, which is now one step lighter than the shell behind the bar. */
.topbar .plan-chip{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--border);border-radius:8px;background:var(--bg);padding:8px 12px;font-size:13.5px}
.topbar .plan-chip b{font-family:var(--mono)}
/* The prototype pushed its sample-data flag to the right edge and the theme
   toggle sat immediately after it; here the toggle is itself the first thing
   in the right-hand group, and the gap opens in front of it. */
.topbar .theme-toggle{margin-left:auto}
/* One rounded pair rather than two rounded halves: the corner is on the group
   and the group clips, so the pressed link's solid fill stops at it. */
.lang-switch{display:inline-flex;border:1px solid var(--border);border-radius:8px;overflow:hidden}
/* Two links rather than two buttons: the choice is a URL, so it is navigable,
   bookmarkable and works before any script has run. */
.lang-switch a{display:inline-flex;align-items:center;justify-content:center;min-width:48px;min-height:44px;background:#fff;color:var(--muted);text-decoration:none;font-family:var(--mono);font-size:13px;letter-spacing:0.08em}
.lang-switch a[aria-pressed="true"]{background:var(--brand);color:#fff}
.user-menu{position:relative}
.user-menu>summary{list-style:none;display:flex;align-items:center;gap:10px;min-height:44px;padding:0 10px;border:1px solid var(--border);border-radius:8px;cursor:pointer;background:#fff}
.user-menu>summary::-webkit-details-marker{display:none}
.user-menu .avatar{width:32px;height:32px;border-radius:50%;background:var(--bg-dark);color:#fff;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;flex:none}
.user-menu .menu{position:absolute;right:0;top:calc(100% + 6px);background:#fff;border:1px solid var(--border);box-shadow:0 12px 28px rgba(7,27,74,0.14);min-width:220px;display:flex;flex-direction:column;z-index:90}
.user-menu .menu a,.user-menu .menu button{display:flex;align-items:center;min-height:46px;padding:0 16px;font:inherit;font-size:14.5px;color:var(--ink);text-decoration:none;background:#fff;border:0;cursor:pointer;text-align:left;border-bottom:1px solid var(--border);width:100%}
.user-menu .menu a:hover,.user-menu .menu button:hover{background:var(--bg)}
.user-menu .menu .who{padding:12px 16px;border-bottom:1px solid var(--border);font-size:13px;color:var(--muted)}

/* ---- main ---------------------------------------------------------------- */

.main{min-width:0;padding:clamp(18px,3vw,34px);display:flex;flex-direction:column;gap:18px;max-width:1160px;width:100%}
.page-head{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
.page-head h1{margin:0;font-size:clamp(23px,2.4vw,29px);line-height:1.2}
.page-head .sub{color:var(--muted);font-size:14.5px}
.page-actions{margin-left:auto;display:flex;gap:10px;flex-wrap:wrap}
/* A card is a sheet of paper on the shell, not a cell in a spreadsheet: no
   border, a soft corner, and two shadows — a tight one that seats the card on
   the page and a wide, very light one that lifts it off. Both are tinted navy
   rather than black, so the shadow is the same hue as everything above it. */
.card{background:var(--surface);border-radius:14px;padding:22px;box-shadow:0 1px 2px rgba(18,38,90,.05),0 10px 28px rgba(18,38,90,.06)}
.card h2,.card h3{margin:0 0 8px;font-size:17.5px}
.card .lead{margin:0 0 14px;color:var(--muted);font-size:14.5px}
.grid{display:grid;gap:16px}
/* The design set each grid's tracks inline. Two widths cover every page. */
.grid.cols-2{grid-template-columns:repeat(auto-fit,minmax(min(100%,380px),1fr))}
.grid.cols-3{grid-template-columns:repeat(auto-fit,minmax(min(100%,340px),1fr))}
.band{background:var(--bg-alt);border:1px solid var(--border);padding:14px 16px;font-size:14.5px;margin:0}
hr.sep{border:0;border-top:1px solid var(--border);margin:16px 0}
/* A card whose heading sits in its own bordered strip: the card loses its
   padding and the strip carries it. Nothing inside it knows about the corner —
   the head's hairline, the table header's tinted band, the dark code block —
   so the card clips them rather than each of them repeating the radius. The
   charts are not in here: they live in a padded .card, where a tooltip that
   reaches past the edge is still drawn. */
.card.flush{padding:0;overflow:hidden}
.card-head{display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:1px solid var(--border)}
.card-head h2{margin:0}
.card-head .spacer{margin-left:auto;font-size:14px}
.card-body{padding:14px 20px;font-size:14px}
.row-actions{display:flex;gap:8px;flex-wrap:wrap}
/* The design wrote the usage page's filter row and the billing page's plan
   strip as inline flex rows on anonymous divs. */
.filters{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.facts{display:flex;gap:clamp(18px,4vw,44px);flex-wrap:wrap;align-items:baseline}
.facts>div{display:flex;flex-direction:column;gap:2px}
.facts .k{font-size:13px;color:var(--muted)}
.facts .v{font-size:24px;font-weight:700}
.facts .v small{font-size:13px;font-weight:400;color:var(--muted)}
/* A card whose body is one figure — the cache share on /portal/usage. The
   credit hero has its own, larger .big; this is the card-sized one. */
.card .big{font-family:var(--mono);font-size:36px;font-weight:700;line-height:1.1;color:var(--ink)}
/* A <details> card: the design styled its summary inline. */
.card>summary{cursor:pointer;font-weight:600;font-size:17.5px;min-height:44px;display:flex;align-items:center}

/* ---- buttons ------------------------------------------------------------- */

/* Every control in the portal carries the same 8px corner: buttons here, the
   chips in the top bar, the switchers on the usage page. The size is the
   design's — 44px of target, unchanged — and only the corner is new. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:0 18px;font:inherit;font-weight:600;font-size:14.5px;cursor:pointer;border:1px solid transparent;border-radius:8px;text-decoration:none}
.btn.primary{background:var(--brand);color:#fff}
.btn.primary:hover{background:var(--brand-ink);color:#fff}
.btn.ghost{background:transparent;color:var(--brand-ink);border-color:var(--brand)}
.btn.ghost:hover{background:var(--info-bg)}
.btn.quiet{background:#fff;color:var(--muted);border-color:var(--border)}
.btn.quiet:hover{color:var(--ink);border-color:var(--muted)}
.btn.danger{background:var(--err);color:#fff}
.btn.danger:hover{background:#8F1D17;color:#fff}
.btn[disabled],.btn[aria-disabled="true"]{opacity:0.55;cursor:not-allowed}
.btn.small{min-height:38px;padding:0 12px;font-size:13.5px}
/* A copy button swaps its own label — portal.js writes "Copied" over "Copy key"
   and puts it back — so the button changes width while it is being pressed and
   shoves whatever is beside it, which on the reveal page is the key. The floor
   clears the widest of the four labels in either language plus this button's
   padding, so no swap moves anything. */
.btn[data-copy]{min-width:8em;text-align:center}
/* The design's full-width auth buttons, which it wrote inline. */
.btn.block{width:100%}
/* A submit that spends 600,000 PBKDF2 iterations before the page changes
   looked frozen with a clickable button on it. Mirrors aifu.css's
   .aifu-btn[aria-busy]; portal.js sets the attribute, .portal-busy is the
   same state for anything that is not a .btn. */
.btn[aria-busy="true"],.portal-busy{opacity:.85;cursor:progress;position:relative;padding-right:46px}
.btn[aria-busy="true"]::after,.portal-busy::after{
  content:"";position:absolute;right:18px;top:50%;width:14px;height:14px;margin-top:-7px;
  border:2px solid rgba(255,255,255,0.4);border-top-color:#fff;border-radius:50%;
  animation:portal-spin 700ms linear infinite;
}
@keyframes portal-spin{to{transform:rotate(360deg)}}

/* ---- badges -------------------------------------------------------------- */

/* Six rather than eight: a badge is small enough that the button's corner would
   read as a pill. Every variant below already carries a tinted background of
   its own colour — the warn one included — so the corner is the only change. */
.badge{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;padding:3px 9px;border:1px solid;border-radius:6px;white-space:nowrap}
.badge::before{content:"";width:7px;height:7px;border-radius:50%;background:currentColor;flex:none}
.badge.ok{color:var(--ok);background:var(--ok-bg);border-color:rgba(11,107,88,0.4)}
.badge.solid-ok{background:#0F9D6E;color:#fff;border-color:#0F9D6E}
.badge.warn{color:var(--warn);background:var(--warn-bg);border-color:rgba(168,65,15,0.4)}
.badge.err{color:var(--err);background:var(--err-bg);border-color:rgba(179,38,30,0.4)}
.badge.info{color:var(--info);background:var(--info-bg);border-color:rgba(10,87,200,0.35)}
.badge.neutral{color:var(--muted);background:#EDF1F8;border-color:rgba(93,109,139,0.4)}

/* ---- forms --------------------------------------------------------------- */

.field{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
.field label{font-weight:600;font-size:14.5px}
/* Not .field .hint any more: the same small grey line is now the note under the
   key box on the reveal page, which is not inside a field. Same declarations,
   one selector. */
.hint{font-size:13px;color:var(--muted)}
.field input,.field select,.field textarea{font:inherit;padding:10px 12px;border:1px solid var(--border);background:#fff;color:var(--ink);min-height:44px}
.field input:focus,.field select:focus{outline:3px solid var(--sky);outline-offset:0;border-color:var(--brand)}

/* ---- tables -------------------------------------------------------------- */

.table-wrap{overflow-x:auto;background:var(--surface);border:1px solid var(--border)}
.table-wrap.flush{border:0}
table{border-collapse:collapse;width:100%;font-size:14.5px}
caption{text-align:left;padding:12px 16px;font-weight:600;border-bottom:1px solid var(--border)}
th,td{text-align:left;padding:11px 14px;border-bottom:1px solid var(--border);vertical-align:middle}
th{font-size:12px;letter-spacing:0.08em;text-transform:uppercase;color:var(--muted);background:var(--bg-alt);white-space:nowrap}
tbody tr:hover{background:#FBFDFF}
td.r,th.r{text-align:right}

/* ---- credit meter -------------------------------------------------------- */

/* The hero floats with the rest of the cards now: same radius, same navy
   shadow, no hairline — only the brand accent along the top survives, clipped
   to the corner by the card's own overflow. */
.credit-hero{background:var(--surface);border-top:3px solid var(--brand);border-radius:14px;overflow:hidden;box-shadow:0 1px 2px rgba(18,38,90,.05),0 10px 28px rgba(18,38,90,.06);padding:clamp(22px,3vw,32px);display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:clamp(18px,3vw,36px)}
.credit-hero .label{font-size:13px;letter-spacing:0.1em;text-transform:uppercase;color:var(--muted)}
.credit-hero .big{font-size:clamp(38px,4.6vw,54px);font-weight:700;line-height:1.05;color:var(--ink);font-family:var(--mono)}
.credit-hero .big small{font-size:0.42em;font-weight:600;color:var(--muted)}
.meter{height:16px;background:var(--bg-alt);border-radius:999px;overflow:hidden;position:relative;margin:14px 0 8px}
.meter>span{position:absolute;top:0;bottom:0;left:0;background:linear-gradient(90deg,var(--brand),var(--sky))}
.credit-hero .side{display:flex;flex-direction:column;gap:12px;border-left:1px solid var(--border);padding-left:clamp(16px,2.5vw,30px)}
.credit-hero .side .row{display:flex;justify-content:space-between;gap:12px;font-size:14.5px;border-bottom:1px solid var(--border);padding-bottom:10px}
.credit-hero .side .row:last-of-type{border-bottom:0}
.credit-hero .side .row b{font-family:var(--mono)}
.credit-hero .foot{font-size:14px;color:var(--muted)}

/* ---- model usage rows ---------------------------------------------------- */

.model-rows{display:flex;flex-direction:column;gap:14px}
.model-rows .row{display:grid;grid-template-columns:64px 1fr auto;gap:14px;align-items:center}
.model-rows .alias{font-family:var(--mono);font-weight:600}
/* A pill rather than a boxed bar, and it clips: the fill inside it is a square
   absolutely-positioned span, and overflow is what rounds its ends with the
   track's. The hover affordance was the border's colour, which there no longer
   is one of — it is a ring now, drawn outside the clip. */
.model-rows .track{height:12px;background:var(--bg-alt);border-radius:999px;overflow:hidden;position:relative;cursor:help;transition:box-shadow 150ms ease}
/* The prototype set a colour per model inline, out of a fixed palette of three.
   The server draws whatever aliases were actually called, so the fill is one
   rule rather than a colour the markup has to carry. */
.model-rows .track>span{position:absolute;top:0;bottom:0;left:0;background:linear-gradient(90deg,var(--brand),var(--sky))}
.model-rows .row:hover .track{box-shadow:0 0 0 2px rgba(8,104,242,0.35)}
.model-rows .val{font-family:var(--mono);font-size:14px;white-space:nowrap}

/* ---- today's stat strip and the token mix -------------------------------- */

/* The dashboard strip's two panels were drawn on .model-rows above, whose first
   column is 64px because it is sized for a model alias: "Success rate" wrapped
   inside it, "Requests" ended up hard against its own figure, and "Reasoning"
   broke mid-word. They get their own rules here. A stat is a small uppercase
   label over a big number — no label column at all — and a mix row is a name
   and a value on one line with a bar under both, so nothing is trying to fit
   a sentence into the width of an alias. */

.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}
/* A tile inside a card, so it is drawn the way the card is: the tint carries it
   and there is no line around it. */
.stat{display:flex;flex-direction:column;gap:6px;padding:14px 16px;background:var(--bg-alt);border-radius:10px}
.stat .label{font-size:11px;letter-spacing:0.08em;text-transform:uppercase;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* The figure is what the tile is for, so it is read across the room and the
   tracking is pulled in a little to stop a wide mono number from sprawling. */
.stat .value{font-family:var(--mono);font-size:28px;font-weight:700;line-height:1.1;letter-spacing:-0.02em;color:var(--ink)}
.stat .sub{font-size:12px;color:var(--muted)}
/* A day with failures in it says so in the status colour, which the dark theme
   flips with the rest of them; a clean day is the same line in muted grey. */
.stat .sub.bad{color:var(--err);font-weight:600}

.mix-total{display:flex;justify-content:space-between;align-items:baseline;gap:12px;margin-bottom:14px}
.mix-total .label{font-size:11px;letter-spacing:0.08em;text-transform:uppercase;color:var(--muted)}
.mix-total .value{font-family:var(--mono);font-size:22px;font-weight:700;color:var(--ink)}
.mix-rows{display:flex;flex-direction:column;gap:12px}
.mix-rows .mrow{display:flex;flex-direction:column;gap:5px}
.mix-rows .head{display:flex;justify-content:space-between;align-items:baseline;gap:12px}
.mix-rows .name{display:flex;align-items:center;gap:8px;white-space:nowrap}
.mix-rows .dot{width:10px;height:10px;flex:none}
.mix-rows .val{font-family:var(--mono);font-size:13px;white-space:nowrap}
.mix-rows .mbar{height:5px;background:var(--bg-alt);border-radius:999px;overflow:hidden}
.mix-rows .mbar>span{display:block;height:100%}
/* One colour per token class, on the dot and on the fill under it, so the two
   read as the one row: cached coral, input cobalt, output green — the shared
   tokens' own three — and reasoning the one hue they do not carry. Square, like
   the legend swatches, which is where these came from. */
.mix-cached-c{background:var(--coral)}
.mix-input-c{background:var(--brand)}
.mix-output-c{background:var(--green)}
.mix-reasoning-c{background:#8E6FD8}

/* The monitor's usage cell reads the way CPAMP writes it: the total on its own
   line, the split underneath in small figures — and the split wears the mix's
   own colours, ↑ input in brand, ↓ output in green, so the table and the token
   mix card are one colour system rather than two. */
.usage-cell{display:inline-flex;flex-direction:column;align-items:flex-end;gap:2px;line-height:1.25}
.usage-total{font-size:15px}
.usage-split{font-size:12px}
.usage-split .u-in{color:var(--brand)}
.usage-split .u-out{color:var(--green)}

/* ---- charts -------------------------------------------------------------- */

.chart{position:relative}
.chart svg{width:100%;height:auto;display:block}
.chart .tick{font:11px var(--mono);fill:var(--muted)}
/* The prototype passed the bar colour into its SVG generator. Server-rendered
   markup carries no colours, so the unit the page is drawn in is an attribute
   and the fill is a rule: cobalt for credits, sky for raw tokens, as the
   design's two calls did.

   The fill is a gradient now, and the gradient is still a rule: the template
   writes the two stops and nothing else, and which colours they are is this
   file's answer. Cobalt into sky down the bar for credits; for raw tokens the
   same shape one step lighter, so the unit still reads off the colour. The
   usage page is the only page that carries data-unit and it draws one chart, so
   the override cannot reach a second SVG's stops — which matters, because every
   chart on a page defines the same gradient id and the first one wins. */
.chart rect.bar{fill:url(#aifu-bar);transition:opacity 120ms ease}
.chart .ch-a{stop-color:var(--brand)}
.chart .ch-b{stop-color:var(--sky)}
.chart[data-unit="raw"] .ch-a{stop-color:var(--sky)}
.chart[data-unit="raw"] .ch-b{stop-color:#8FD8FF}
.chart.hovering rect.bar{opacity:0.42}
.chart rect.bar.active{opacity:1}
.chart rect.hit{cursor:crosshair}
.chart-tip{position:absolute;transform:translateX(-50%);background:var(--bg-dark);color:#fff;border:1px solid var(--sky);padding:8px 12px;font-size:13px;line-height:1.45;pointer-events:none;z-index:20;white-space:nowrap;display:flex;flex-direction:column;gap:2px;box-shadow:0 8px 20px rgba(7,27,74,0.35)}
.chart-tip b{font-size:12px;letter-spacing:0.08em;text-transform:uppercase;color:var(--mint)}
.chart-tip span{font-family:var(--mono);font-variant-numeric:tabular-nums}
.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13.5px}
.legend span{display:inline-flex;align-items:center;gap:7px}
.legend i{width:11px;height:11px;display:inline-block}

/* ---- tabs and code ------------------------------------------------------- */

.tabs{display:flex;border-bottom:2px solid var(--border);flex-wrap:wrap}
.tabs button{font:inherit;font-weight:600;font-size:14.5px;background:transparent;border:0;border-bottom:3px solid transparent;margin-bottom:-2px;padding:10px 18px;min-height:44px;color:var(--muted);cursor:pointer}
.tabs button[aria-selected="true"]{color:var(--ink);border-bottom-color:var(--brand)}
pre.code{margin:0;padding:18px;background:var(--bg-dark);color:#CFE3FF;font-family:var(--mono);font-size:13.5px;line-height:1.7;overflow-x:auto}
pre.code .c1{color:var(--mint)}
pre.code .c2{color:#FFC59E}
.code-head{display:flex;align-items:center;gap:12px;padding:10px 14px;border:1px solid var(--border);border-bottom:0;background:var(--surface);font-family:var(--mono);font-size:13px;color:var(--muted)}
.code-head.flush{border:0;border-bottom:1px solid var(--border)}
.code-head .btn{margin-left:auto}

/* ---- switcher chips ------------------------------------------------------ */

.switcher{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.switcher .lbl{font-size:12px;letter-spacing:0.1em;text-transform:uppercase;color:var(--muted)}
/* The prototype's chips were buttons a script listened to. Here each chip is a
   URL the server re-renders, for the reason .lang-switch is: the choice is
   navigable, bookmarkable and works before any script has run. The anchor is
   given the button's own rule rather than a second one. */
.switcher button,.switcher a{min-height:38px;padding:0 12px;font:inherit;font-size:13.5px;background:#fff;border:1px solid var(--border);border-radius:8px;color:var(--muted);cursor:pointer}
.switcher a{display:inline-flex;align-items:center;text-decoration:none}
.switcher button[aria-pressed="true"],.switcher a[aria-pressed="true"]{background:var(--bg-dark);border-color:var(--bg-dark);color:#fff}

/* ---- dialogs ------------------------------------------------------------- */

dialog{border:1px solid var(--border);padding:0;max-width:540px;width:calc(100vw - 32px)}
dialog::backdrop{background:rgba(7,27,74,0.55)}
.dlg-head{background:var(--bg-dark);color:#fff;padding:14px 18px;font-weight:600;display:flex;justify-content:space-between;gap:12px;align-items:center}
.dlg-head.danger{background:var(--err)}
.dlg-head .btn.quiet{color:#fff;background:transparent;border-color:rgba(255,255,255,0.5)}
.dlg-body{padding:20px;display:flex;flex-direction:column;gap:14px}
.dlg-foot{padding:14px 18px;border-top:1px solid var(--border);display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap}
.key-reveal{display:flex;gap:10px;flex-wrap:wrap;align-items:stretch}
.key-reveal code{flex:1;min-width:200px;display:flex;align-items:center;padding:12px 14px;background:var(--bg-dark);color:#BFE3FF;font-family:var(--mono);font-size:13.5px;overflow-wrap:anywhere}
.check-line{display:flex;gap:10px;align-items:flex-start;font-size:14.5px}
.check-line input{width:20px;height:20px;margin-top:2px}

/* ---- states -------------------------------------------------------------- */

.empty-state{text-align:center;padding:46px 20px;color:var(--muted)}
.empty-state h2{margin:0 0 8px}
.error-state{border:1px solid rgba(179,38,30,0.4);background:var(--err-bg);color:var(--err);padding:16px 18px;display:flex;gap:12px;align-items:flex-start}
.loading-row{display:flex;flex-direction:column;gap:10px;padding:20px}
.skl{height:14px;background:linear-gradient(90deg,var(--bg-alt),#F2F7FF,var(--bg-alt));background-size:200% 100%;animation:skl 1.4s linear infinite}
@keyframes skl{to{background-position:-200% 0}}

/* ---- plans --------------------------------------------------------------- */

.plan-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:16px}
.plan-card{background:var(--surface);border:1px solid var(--border);border-top:3px solid var(--brand);padding:20px;display:flex;flex-direction:column;gap:10px;position:relative}
.plan-card.popular{border-color:var(--brand);box-shadow:0 10px 26px rgba(8,104,242,0.12)}
.plan-card .pop{position:absolute;top:-11px;left:16px;background:var(--bg-dark);color:var(--mint);font-family:var(--mono);font-size:11px;letter-spacing:0.12em;padding:3px 9px}
.plan-card h3{font-size:17.5px}
/* The struck list price and the discount badge beside it, which the landing
   draws with the same two values. */
.plan-card .was{font-family:var(--mono);font-size:15px;color:var(--muted);text-decoration:line-through}
.plan-card .off{background:var(--coral);color:#fff;font-size:12.5px;letter-spacing:0.12em;padding:3px 8px}
.plan-card .price{font-size:30px;font-weight:700;color:var(--ink);font-family:var(--mono)}
.plan-card .price small{font-size:13px;font-weight:400;color:var(--muted);font-family:var(--sans)}
.plan-card .rows{display:flex;flex-direction:column;border-top:1px solid var(--border)}
.plan-card .rows>div{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--border);font-size:14px}

/* ---- model panels -------------------------------------------------------- */

.model-panels{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px}
.model-panel{background:var(--surface);border:1px solid var(--border);padding:22px;display:flex;flex-direction:column;gap:12px}
.model-panel .head{display:flex;justify-content:space-between;align-items:center;gap:10px}
.model-panel .alias{font-family:var(--mono);font-size:26px;font-weight:700;color:var(--ink)}
.model-panel .tagline{color:var(--muted);font-size:14.5px;margin:0}
.model-panel ul{margin:0;padding-left:18px;font-size:14.5px;display:flex;flex-direction:column;gap:6px}
.model-panel .w{display:flex;flex-direction:column;border-top:1px solid var(--border)}
.model-panel .w>div{display:flex;justify-content:space-between;gap:10px;padding:7px 0;border-bottom:1px solid var(--border);font-size:13.5px}
.model-panel .w b{font-family:var(--mono)}

/* ---- auth ---------------------------------------------------------------- */

.auth-wrap{min-height:100vh;display:grid;place-items:center;padding:24px;background:radial-gradient(ellipse 70% 40% at 50% -10%, rgba(8,104,242,0.12), transparent 60%),var(--bg)}
.auth-card{width:min(440px,100%);background:var(--surface);border:1px solid var(--border);border-top:3px solid var(--brand);padding:32px}
.auth-card .brandline{display:flex;justify-content:center;margin-bottom:18px}
.auth-card img{height:46px}
.auth-card h1{font-size:21px;margin:0 0 8px;text-align:center}
.auth-card .lead{text-align:center;color:var(--muted);font-size:14.5px;margin:0 0 18px}
/* The design wrote this pair of buttons as an inline flex row. */
.auth-actions{display:flex;gap:10px;flex-wrap:wrap}
.auth-actions .btn{flex:1}
/* A button that submits something is wrapped in a form, and the form is the
   flex item: without this it sizes to its content while the button beside it
   grows, which is the rotate button on the key-unavailable page. */
.auth-actions form{flex:1;display:flex}

/* The same card, anchored near the top of the viewport instead of centred in
   it. Two pages are drawn on it, and both are read rather than filled in: the
   key that is shown once, and the refusal a second claim gets.
   The wrapper above stretches its single grid row to the full height and
   centres the card in it, so where the card sits depends on how tall the
   viewport is — which on a phone changes by the height of the URL bar the
   moment the page scrolls, moving the key out from under the thumb reaching
   for it. Starting the row's content pins it to the top instead, and small
   viewport units are the height measured with that bar showing, which is the
   one that does not change when it hides. */
.auth-wrap--top{align-content:start;min-height:100svh;padding-top:clamp(48px,12vh,120px)}
.kv{display:grid;grid-template-columns:auto 1fr;gap:8px 18px;font-size:14.5px;margin:10px 0 0}
.kv dt{color:var(--muted)}
.kv dd{margin:0;font-weight:600}

/* ---- footer -------------------------------------------------------------- */

.page-foot{border-top:1px solid var(--border);padding-top:16px;font-size:13px;color:var(--muted);display:flex;flex-direction:column;gap:4px}

/* ---- responsive ---------------------------------------------------------- */

@media (max-width:980px){
  .app{grid-template-columns:1fr}
  .sidebar{position:fixed;inset:0 24% 0 0;z-index:120;transform:translateX(-102%);transition:transform 200ms ease;overflow-y:auto}
  .sidebar.open{transform:none;box-shadow:0 0 0 100vmax rgba(7,27,74,0.45)}
  .menu-btn{display:inline-flex}
  .credit-hero{grid-template-columns:1fr}
  .credit-hero .side{border-left:0;padding-left:0;border-top:1px solid var(--border);padding-top:14px}
  .topbar .plan-chip .plan-name{display:none}
}
@media (max-width:760px){
  table.stack thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
  table.stack tr{display:block;border-bottom:6px solid var(--bg);padding:6px 0}
  table.stack td{display:grid;grid-template-columns:44% 1fr;gap:10px;border-bottom:1px dotted var(--border)}
  table.stack td::before{content:attr(data-label);font-size:11px;letter-spacing:0.08em;text-transform:uppercase;color:var(--muted);padding-top:2px}
  td.r{text-align:left}
}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important}
  /* An infinite spinner shortened to 0.01ms is a strobe, not a reduction. */
  .btn[aria-busy="true"]::after,.portal-busy::after{animation:none}
}

/* ---- dark theme ---------------------------------------------------------- */

/* Carried over from the prototype with the tokens renamed to this file's. One
   class on <html> is the whole mechanism: theme.js puts it there before the
   first paint and the button in the topbar flips it, and everything below is
   what answers. */
html.dark{color-scheme:dark}
html.dark body{
  --shell:#060F26;
  --bg:#060F26; --bg-alt:#0B1B3F; --surface:#0A1730; --ink:#E6EEFC; --muted:#A6B8DA; --border:#1B2C55;
  --ok:#7FDCC8; --ok-bg:rgba(141,223,208,0.12); --warn:#FFB38A; --warn-bg:rgba(255,154,107,0.12);
  --err:#FF9C94; --err-bg:rgba(179,38,30,0.18); --info:#7FBDFF; --info-bg:rgba(8,104,242,0.18);
  --brand-ink:#5EA4FF;
}
html.dark .brand{background:#0E1F42;border-bottom-color:var(--border)}
html.dark .brand img{filter:brightness(1.35) saturate(1.1)}
html.dark .brand .env{background:rgba(8,104,242,0.22);border-color:rgba(94,164,255,0.5);color:#9CC6FF}
/* The card keeps its dark surface and loses the light theme's navy tint: a blue
   shadow on a blue-black page is a halo, and what a card needs there is depth
   rather than colour. */
html.dark .card,
html.dark .credit-hero{box-shadow:0 1px 2px rgba(0,0,0,.4),0 10px 28px rgba(0,0,0,.28)}
html.dark .lang-switch a,html.dark .switcher button,html.dark .switcher a,html.dark .user-menu>summary,html.dark .user-menu .menu a,html.dark .user-menu .menu button,html.dark .btn.quiet{background:var(--surface);color:var(--muted)}
html.dark .user-menu .menu a:hover,html.dark .user-menu .menu button:hover{background:var(--bg-alt);color:var(--ink)}
html.dark .btn.quiet:hover{color:var(--ink)}
html.dark .field input,html.dark .field select,html.dark .field textarea{background:var(--surface);color:var(--ink)}
html.dark tbody tr:hover{background:#0D1E3E}
html.dark .badge.err{color:#FFB4AD}
html.dark .badge.solid-ok{background:#12B981;color:#FFFFFF;border-color:#12B981;font-weight:700}
html.dark .badge.neutral{color:#B6C6E4;background:rgba(93,109,139,0.22);border-color:rgba(150,170,205,0.45)}
html.dark .skl{background:linear-gradient(90deg,var(--bg-alt),#12244A,var(--bg-alt));background-size:200% 100%}
html.dark .auth-wrap{background:radial-gradient(ellipse 70% 40% at 50% -10%, rgba(8,104,242,0.25), transparent 60%),var(--bg)}
html.dark .plan-card.popular{box-shadow:0 10px 26px rgba(8,104,242,0.3)}
/* The panel itself, which the design never flipped (styles.css:84): its rows
   answer the rule above and its own background stayed #fff, so the customer
   menu opened as a white card with dark rows inside it. The .who line keeps
   var(--muted), which flips with the theme on its own. */
html.dark .user-menu .menu{background:var(--surface);border-color:var(--border);color:var(--ink);box-shadow:0 12px 28px rgba(0,0,0,0.5)}
html.dark pre.code{background:#050B1E}
html.dark .chart .tick{fill:var(--muted)}
html.dark .chart-tip{background:#02060F;border-color:var(--sky)}
html.dark .credit-hero .big,
html.dark .card .big,
html.dark .stat .value,
html.dark .mix-total .value,
html.dark .plan-card .price,
html.dark .model-panel .alias,
html.dark .tabs button[aria-selected="true"],
html.dark .menu-btn{color:#EAF2FF}
html.dark .user-menu .avatar{background:var(--brand)}
html.dark a{color:#7FBDFF}
html.dark a:hover{color:#B8DBFF}

/* The toggle itself. Both icons are in the button and one of them is on
   screen: the moon while the page is light, the sun once it is dark. */
.theme-toggle{min-width:44px;min-height:44px;background:#fff;border:1px solid var(--border);border-radius:8px;color:var(--muted);cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
.theme-toggle:hover{color:var(--ink);border-color:var(--muted)}
.theme-toggle .sun{display:none}
html.dark .theme-toggle{background:var(--surface)}
html.dark .theme-toggle .sun{display:block}
html.dark .theme-toggle .moon{display:none}
