﻿:root {
    --bg: #0f172a;
    --bg-2: #16213c;
    --panel: #f8fafc;
    --panel-line: #e2e8f0;
    --panel-line-2: #cbd5e1;
    --ink: #0f172a;
    --ink-2: #475569;
    --ink-3: #94a3b8;
    --accent: #4f46e5;
    --accent-soft: #eef2ff;
    --accent-line: #c7d2fe;
    --danger: #dc2626;
    --ok: #059669;
    --canvas: #ffffff;
    --grid: #e8eef6;
    --grid-strong: #d8e2ee;
    --shadow: 0 10px 30px rgba(15, 23, 42, .10);
    --bar-h: 46px;
    --font: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
    --mono: "Cascadia Mono", ui-monospace, "SF Mono", Consolas, monospace;

    /* ---- layout rhythm -------------------------------------------------
       Every surface in the app is built from *bands*: a dialog's header,
       list, sections and footer; a settings card; a rail's head and its
       sections. A band always takes the same horizontal padding, so the
       text in one lines up exactly with the text in the next, and the
       band's own vertical padding supplies the gap to its neighbour.

       --pad-x/--pad-y are the wide surfaces (dialogs, cards, the settings
       page). --pad-rail is the same idea inside a 232-360px side rail,
       where 16px of chrome on each edge is too much to give away. */
    --pad-x: 16px;
    --pad-y: 16px;
    --pad-rail: 12px;

    /* Gaps between things inside a band. Nothing should invent its own. */
    --gap-xs: 4px;
    --gap-sm: 8px;
    --gap-md: 12px;
    --gap-lg: 16px;

    /* Control heights. Anything sitting on one row must share one of these,
       or the row loses its baseline: --ctl-h is the workhorse (icon buttons,
       small buttons, compact fields), --ctl-h-lg is a form control or a
       dialog button, --ctl-h-sm is a micro affordance inside another control. */
    --ctl-h-sm: 26px;
    --ctl-h: 30px;
    --ctl-h-lg: 34px;

    /* Corner radii, smallest (a control) to largest (a dialog). */
    --radius-sm: 7px;
    --radius: 10px;
    --radius-lg: 14px;
    --radius-xl: 16px;
    --radius-pill: 999px;

    /* Width of the styled scrollbar below. A scroll container reserves this permanently
       and hands it back out of its own trailing padding, so a list does not shift
       sideways the moment it grows past one screenful. */
    --scrollbar-w: 10px;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    overflow: hidden;
    font-family: var(--font);
    font-size: 14px;
    color: var(--ink);
    background: var(--bg);
}

#app { height: 100%; }

button, input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }

.icon { display: inline-block; vertical-align: middle; flex: none; }

/* ---------------------------------------------------------------- boot */

.boot {
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    color: #e2e8f0;
    background: radial-gradient(1200px 600px at 50% -10%, #1e293b, #0f172a);
}
.boot-mark { font-size: 28px; font-weight: 700; letter-spacing: .14em; }
.boot-bar { width: 180px; height: 3px; background: #1e293b; border-radius: 3px; overflow: hidden; }
.boot-bar span { display: block; width: 40%; height: 100%; background: #6366f1; animation: bootslide 1.1s ease-in-out infinite; }
.boot-note { color: #64748b; font-size: 12px; }
@keyframes bootslide { 0% { transform: translateX(-100%); } 100% { transform: translateX(350%); } }

#blazor-error-ui {
    background: #fef3c7;
    border-top: 1px solid #f59e0b;
    bottom: 0;
    display: none;
    left: 0;
    padding: 10px 16px;
    position: fixed;
    width: 100%;
    z-index: 3000;
}
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 14px; top: 10px; }

/* ---------------------------------------------------------------- shell */

/*
   Flex column rather than a two-row grid: the offline banner is an optional row between the app bar
   and the body, and `grid-template-rows: auto 1fr` would hand the 1fr to whichever child happened to
   be second. Flex keeps the bar and the banner at their natural height and gives the rest to the body.
*/
.studio {
    height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--bg);
}

.studio-body {
    display: grid;
    grid-template-columns: auto 1fr auto;
    flex: 1;
    min-height: 0;
    gap: 1px;
    background: #0b1224;
}

.rail {
    background: var(--panel);
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.rail-left { width: 232px; }
.rail-right { width: 300px; }
.rail.collapsed { width: 0; }

/*
   The right rail holds two panes — properties and the AI chat — stacked, not layered. Each is a
   flex column of its own so both keep their own header, their own scroll region and their own
   composer; the rail only decides how the height is divided.

   The chat is the wider of the two and a 300px rail makes its composer cramped, so the rail widens
   whenever chat is in it, whether or not properties is there as well.
*/
.rail-wide { width: 360px; }

.rail-pane {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
    flex: 1 1 auto;
}

/*
   Sharing the height 46/54: the chat gets the larger share because a conversation reads badly in a
   letterbox, while the properties panel is a list that scrolls happily. The minimums are what stop
   either from being squeezed to uselessness — below them the pane would be a header and a sliver.
*/

.rail-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--gap-sm);
    min-height: var(--bar-h);
    padding: var(--gap-sm) var(--pad-rail);
    border-bottom: 1px solid var(--panel-line);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--ink-2);
    flex: none;
}
/*
   Same contract as a dialog body: the rail's scroll gutter is reserved whether or not the
   content is long enough to need it, and the bands inside give it back out of their own
   trailing padding, so their text stays on the rail head's axis in both states.
*/
.rail-scroll { overflow-y: auto; overflow-x: hidden; scrollbar-gutter: stable; flex: 1; min-height: 0; }

/*
   Narrow windows. The rail keeps its stacked arrangement — that part is width-independent — but it
   stops competing with the canvas for the same pixels: first by giving up width, then, once even a
   trimmed rail would leave the drawing a strip, by floating over the canvas as a drawer. Stacking
   stays the arrangement throughout, so nothing the user learned about the toggles changes with the
   size of the window.
*/
@media (max-width: 1240px) {
    .rail-wide { width: 330px; }
    .rail-left { width: 208px; }
}

@media (max-width: 1000px) {
    .rail-right { width: 272px; }
    .rail-wide { width: 296px; }
    .rail-left { width: 188px; }
}

@media (max-width: 820px) {
    .studio-body { position: relative; }
    .studio-body > .rail-right {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        z-index: 40;
        width: min(320px, 86vw);
        border-left: 1px solid var(--panel-line);
        box-shadow: -18px 0 40px rgb(3 8 22 / .45);
    }
}

/* ---------------------------------------------------------------- app bar */

.appbar {
    position: relative;
    z-index: 60;
    display: flex;
    align-items: center;
    gap: 4px;
    height: var(--bar-h);
    padding: 0 10px;
    background: linear-gradient(180deg, #22304a, #161f31);
    border-bottom: 1px solid #0b1224;
    box-shadow: 0 1px 0 rgba(255, 255, 255, .04) inset, 0 4px 14px rgba(2, 6, 23, .35);
    color: #e2e8f0;
    overflow: visible;
    white-space: nowrap;
}

.brand {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-weight: 700;
    letter-spacing: .11em;
    font-size: 12.5px;
    padding: 0 10px 0 4px;
    color: #c7d2fe;
    white-space: nowrap;
    flex: none;
}
.brand svg { color: #818cf8; flex: none; }

.tb-sep { width: 1px; height: 22px; background: #33415580; margin: 0 6px; flex: none; }
.tb-spacer { flex: 1 1 auto; min-width: 8px; }
.tb-group { display: inline-flex; align-items: center; gap: 2px; flex: none; }

/* --- text button (menus) --- */
.tb {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: var(--ctl-h);
    background: transparent;
    border: 1px solid transparent;
    color: #cbd5e1;
    border-radius: var(--radius-sm);
    padding: 0 9px;
    font-size: 13px;
    cursor: pointer;
    white-space: nowrap;
    transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.tb:hover:not(:disabled) { background: #2b384f; color: #f1f5f9; }
.tb:disabled { opacity: .4; cursor: default; }
.tb.on, .tb.open { background: #33425e; border-color: #46587a; color: #fff; }

/* --- icon button: the workhorse of the bar and the panels --- */
.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ctl-h);
    height: var(--ctl-h);
    padding: 0;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    background: transparent;
    color: #cbd5e1;
    cursor: pointer;
    transition: background .12s ease, color .12s ease, border-color .12s ease;
}
.icon-btn:hover:not(:disabled) { background: #2b384f; color: #fff; }
/* An icon button that also carries a chevron, so it reads as opening a menu. */
.icon-btn.wide { width: auto; gap: 1px; padding: 0 5px; }
.icon-btn:active:not(:disabled) { background: #354561; }
.icon-btn:disabled { opacity: .32; cursor: default; }
/* Two-tone active states: a tint for on/off toggles, solid accent for a mode you are *in*. */
.icon-btn.on {
    background: rgba(99, 102, 241, .22);
    border-color: rgba(129, 140, 248, .45);
    color: #a5b4fc;
}
.icon-btn.on:hover { background: rgba(99, 102, 241, .34); color: #c7d2fe; }

.seg-tools .icon-btn.on {
    background: var(--accent);
    border-color: #6366f1;
    color: #fff;
    box-shadow: 0 1px 6px rgba(79, 70, 229, .45);
}
.seg-tools .icon-btn.on:hover { background: #4338ca; color: #fff; }
.icon-btn:focus-visible, .tb:focus-visible { outline: 2px solid #818cf8; outline-offset: 1px; }
.icon-btn .icon { pointer-events: none; }

/* Panel-side icon buttons sit on light chrome. */
.props .icon-btn, .modal .icon-btn, .settings .icon-btn { color: var(--ink-2); }
.props .icon-btn:hover, .modal .icon-btn:hover, .settings .icon-btn:hover { background: #eef2f7; color: var(--ink); }
.props .icon-btn.danger:hover, .modal .icon-btn.danger:hover, .settings .icon-btn.danger:hover {
    background: #fef2f2;
    color: var(--danger);
}

/* --- tool segment --- */
.seg-tools {
    display: inline-flex;
    gap: 2px;
    padding: 2px;
    border-radius: var(--radius);
    background: #162033;
    border: 1px solid #2c3a52;
    flex: none;
}

/* --- menus --- */
.menu-anchor { position: relative; display: inline-flex; }
.menu-backdrop { position: fixed; inset: 0; z-index: 55; }

.menu {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 70;
    min-width: 232px;
    padding: var(--gap-xs);
    background: #fff;
    color: var(--ink);
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    box-shadow: 0 16px 40px rgba(2, 6, 23, .35);
    animation: menuIn .11s ease-out;

    /* The File menu carries the samples group as well as its own actions, so it is the tallest in
       the bar. Cap it to what is left below the app bar and scroll the overflow: the last entry has
       to stay reachable on a short window rather than falling off the bottom of the screen. */
    max-height: calc(100vh - 72px);
    overflow-y: auto;
}
@keyframes menuIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

.menu-item {
    display: flex;
    align-items: center;
    gap: var(--gap-sm);
    width: 100%;
    min-height: var(--ctl-h);
    padding: var(--gap-xs) var(--gap-sm);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    text-align: left;
    font-size: 13px;
    color: var(--ink);
    cursor: pointer;
}
.menu-item:hover:not(:disabled) { background: var(--accent-soft); color: var(--accent); }
.menu-item span { flex: 1; }
.menu-item .icon { color: var(--ink-3); flex: none; }
.menu-item:hover:not(:disabled) .icon { color: var(--accent); }
/* A server-only action offline: visibly dead, but still hoverable so the tooltip explains why. */
.menu-item:disabled { opacity: .45; cursor: not-allowed; color: var(--ink-2); }
.menu-item kbd {
    font-family: var(--mono);
    font-size: 10.5px;
    color: var(--ink-3);
    background: #f1f5f9;
    border: 1px solid var(--panel-line);
    border-radius: 4px;
    padding: 1px 5px;
}
/* Right-anchored dropdown: menus opened from the right half of the bar would run off-screen. */
.menu.menu-right { left: auto; right: 0; }
.menu-label {
    padding: var(--gap-sm) var(--gap-sm) var(--gap-xs);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.menu-item.as-check { cursor: pointer; }
.menu-item.as-check input { accent-color: var(--accent); width: 14px; height: 14px; }
.menu-sep { height: 1px; background: var(--panel-line); margin: var(--gap-xs) var(--gap-sm); }

/* --- diagram style presets: a two-line row with a three-chip colour sample --- */
.menu.menu-wide { min-width: 320px; }
.menu-item.style-item { align-items: flex-start; min-height: 0; padding: var(--gap-sm); }
.menu-item.style-item.on { background: var(--accent-soft); color: var(--accent); }
.style-swatch { display: flex; gap: 3px; flex: none; padding-top: 2px; }
.style-swatch span {
    display: block;
    width: 13px;
    height: 13px;
    border-radius: 4px;
    border: 1px solid;
    flex: none;
}
.style-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.style-text strong { font-size: 12.5px; font-weight: 650; }
.style-text small { font-size: 11px; color: var(--ink-3); line-height: 1.35; }
.menu-item.style-item:hover small { color: inherit; opacity: .8; }

/* --- legend row editor in the properties panel --- */
.legend-row-edit { display: flex; align-items: center; gap: 5px; min-width: 0; }
.legend-row-edit .field { min-width: 0; flex: 1; }
.legend-row-edit select.field { flex: 0 0 82px; }
.legend-row-edit .swatch { flex: none; }

/* --- document title: the only element allowed to shrink when the bar gets tight --- */
.doc-title { display: flex; align-items: center; gap: 7px; flex: 0 1 auto; min-width: 0; }

.tb-name {
    background: #131c2d;
    border: 1px solid #2c3a52;
    border-radius: var(--radius-sm);
    height: var(--ctl-h);
    padding: 0 10px;
    color: #e2e8f0;
    font-size: 13px;
    flex: 1 1 auto;
    width: 220px;
    min-width: 96px;
    max-width: 260px;
    transition: border-color .12s ease, background .12s ease;
}
.tb-name:hover { border-color: #3d4f6d; }
.tb-name:focus { outline: none; border-color: var(--accent); background: #0f172a; box-shadow: 0 0 0 3px rgba(79, 70, 229, .25); }

/* --- zoom box --- */
.zoom-group { gap: 2px; }
.zoom-box {
    position: relative;
    display: inline-flex;
    align-items: center;
    height: var(--ctl-h);
    background: #131c2d;
    border: 1px solid #2c3a52;
    border-radius: var(--radius-sm);
    padding: 0 4px 0 0;
    transition: border-color .12s ease;
}
.zoom-box:hover { border-color: #3d4f6d; }
.zoom-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(79, 70, 229, .25); }

.zoom-input {
    width: 42px;
    height: 100%;
    border: 0;
    background: transparent;
    color: #e2e8f0;
    text-align: right;
    font-size: 12.5px;
    font-variant-numeric: tabular-nums;
    padding: 0 2px 0 8px;
    outline: none;
}
.zoom-suffix { font-size: 11.5px; color: #94a3b8; padding-right: 4px; }

/* ---------------------------------------------------------------- palette */

/* A palette category is a band: same horizontal padding as the rail head above it. */
.palette-group { padding: var(--gap-md) calc(var(--pad-rail) - var(--scrollbar-w)) var(--gap-md) var(--pad-rail); }
.palette-group + .palette-group { padding-top: 0; }
.palette-title {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--ink-3);
    margin: 0 0 var(--gap-sm);
}

/* Collapsible category header: still a title, but it is a real button. */
.palette-title.as-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    border: 0;
    background: transparent;
    padding: 0;
    min-height: var(--ctl-h-sm);
    cursor: pointer;
    text-align: left;
}
.palette-title.as-toggle:hover { color: var(--accent); }
.palette-title.as-toggle .icon { flex: none; opacity: .8; }
.palette-count {
    margin-left: auto;
    font-size: 9.5px;
    font-weight: 600;
    letter-spacing: 0;
    color: var(--ink-3);
    background: var(--panel-line);
    border-radius: var(--radius-pill);
    padding: 1px 6px;
}

/* Sticky search box at the head of the palette rail — its own band. */
.palette-search {
    position: sticky;
    top: 0;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: var(--gap-sm);
    padding: var(--gap-sm) calc(var(--pad-rail) - var(--scrollbar-w)) var(--gap-sm) var(--pad-rail);
    background: var(--panel);
    border-bottom: 1px solid var(--panel-line);
}
.palette-search .icon { flex: none; color: var(--ink-3); }
.palette-search .field { flex: 1; min-width: 0; }
.palette-search .icon-btn.sm { width: var(--ctl-h-sm); height: var(--ctl-h-sm); flex: none; }

/* Connector picker: same "active" language as the pointer-tool segment. */
.connector-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); gap: var(--gap-sm); }
.connector-list > * { min-width: 0; }
.connector-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    background: #fff;
    color: var(--ink-2);
    padding: 7px 4px 5px;
    cursor: pointer;
    font-size: 10.5px;
    transition: border-color .12s ease, box-shadow .12s ease, background .12s ease;
}
.connector-item:hover { border-color: var(--accent-line); }
.connector-item svg { display: block; max-width: 100%; }
.connector-item span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.connector-item.on {
    border-color: var(--accent);
    background: var(--accent-soft, #eef2ff);
    color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
}

/*
   auto-fill + minmax fits a whole number of equal columns to whatever width the rail happens to be,
   instead of hard-coding 2 and 3. The `min-width: 0` on the items is the load-bearing part: a grid
   item's automatic minimum size is its content, so a long nowrap caption such as "Serverless
   function" used to widen its track, push the row past the rail and get clipped at the panel border.
*/
.palette-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); gap: var(--gap-sm); }
.palette-grid.stencils { grid-template-columns: repeat(auto-fill, minmax(62px, 1fr)); gap: var(--gap-sm); }
.palette-grid.stencils .palette-item { padding: 7px 3px 5px; color: var(--accent); }
.palette-grid.stencils .palette-label { color: var(--ink-2); font-size: 9.5px; }
.palette-grid > * { min-width: 0; }

.palette-item {
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    background: #fff;
    padding: 8px 6px 6px;
    cursor: grab;
    text-align: center;
    user-select: none;
    touch-action: none;
    min-width: 0;
    overflow: hidden;
    transition: border-color .12s ease, box-shadow .12s ease, transform .08s ease;
}
.palette-item:hover { border-color: var(--accent-line); box-shadow: 0 3px 10px rgba(79, 70, 229, .12); transform: translateY(-1px); }
.palette-item:active { cursor: grabbing; }
.palette-item svg { display: block; margin: 0 auto; max-width: 100%; }
.palette-item img { display: block; margin: 0 auto; max-width: 100%; height: 44px; object-fit: contain; }
.palette-label {
    margin-top: 5px;
    font-size: 10.5px;
    color: var(--ink-2);
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---- vendor icon packs ----
   Nested one level under each vendor's group header, so 28 categories stay scannable in a 232px
   rail. Azure, AWS and IBM all render through these rules; nothing here is vendor-specific. */
.palette-sub { margin: 0 0 var(--gap-sm); }
.palette-title.as-toggle.sub {
    font-size: 10px;
    letter-spacing: .05em;
    color: var(--ink-3);
    padding-left: var(--gap-xs);
}
.vendor-item img {
    display: block;
    margin: 0 auto;
    width: 40px;
    height: 40px;
    object-fit: contain;
    pointer-events: none;
}

.palette-hint {
    padding: var(--gap-xs) var(--gap-sm);
    font-size: 11px;
    color: var(--ink-3);
}

.asset-item { position: relative; }
.asset-tools {
    position: absolute;
    top: 3px;
    right: 3px;
    display: none;
    gap: 2px;
}
.asset-item:hover .asset-tools { display: flex; }
.asset-tools button {
    border: 1px solid var(--panel-line-2);
    background: #fff;
    border-radius: 5px;
    width: 20px;
    height: 20px;
    line-height: 1;
    padding: 0;
    cursor: pointer;
    font-size: 11px;
}
.asset-tools button:hover { background: var(--accent-soft); }

.drop-zone {
    margin: 0 0 var(--gap-sm);
    border: 1.5px dashed var(--panel-line-2);
    border-radius: var(--radius);
    padding: var(--gap-md);
    text-align: center;
    color: var(--ink-3);
    font-size: 11.5px;
    transition: all .12s ease;
}
.drop-zone.hot { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

.drag-ghost {
    /* Always in the DOM so a drag can position it without a Blazor render; JS turns the display on
       once the press has travelled far enough to count as a drag, and off again when it ends. */
    display: none;
    position: fixed;
    pointer-events: none;
    z-index: 4000;
    opacity: .85;
    transform: translate(-50%, -50%);
    filter: drop-shadow(0 6px 14px rgba(15, 23, 42, .3));
}

/* ---------------------------------------------------------------- canvas */

.canvas-wrap {
    position: relative;
    min-width: 0;
    min-height: 0;
    background: var(--canvas);
    overflow: hidden;
    outline: none;
}
.canvas-svg { display: block; width: 100%; height: 100%; touch-action: none; user-select: none; }

/* ---- live-gesture compositing hints -----------------------------------
   Only while dragging, panning, resizing or linking. Transitions on hundreds of shapes are pure
   style-recalc cost when every frame replaces the geometry anyway, and speed-first rasterising is
   invisible at gesture speed. Both revert the moment the pointer goes up. */
.canvas-svg.gesturing { shape-rendering: optimizeSpeed; }
.canvas-svg.gesturing * { transition: none !important; }
.canvas-svg.gesturing .particle { display: none; }

/* The soft card shadow is an SVG filter set as a presentation attribute (filter="url(#...)"),
   which any author-level CSS declaration outranks - so this plain property genuinely wins.
   Re-rasterising a Gaussian blur per shape per frame is the single most expensive paint the
   Soft preset asks for, and mid-gesture nobody is looking at shadow quality. */
.canvas-svg.gesturing .node-shape { filter: none !important; }

/* Marching ants force a stroke repaint of every animated connection every frame even while the
   gesture is already repainting everything; the dash PATTERN stays (it is stroke-dasharray),
   only the motion pauses, and it resumes the moment the pointer goes up. */
.canvas-svg.gesturing .edge-animated { animation: none !important; }

/* ---- rulers -----------------------------------------------------------
   Overlaid, never in the flow: turning them on must not reflow the drawing,
   and they are inert to the pointer so every gesture still reaches the SVG. */
.ruler {
    position: absolute;
    top: 0;
    left: 0;
    pointer-events: none;
    z-index: 4;
    shape-rendering: crispEdges;
}
.ruler-left { top: 0; }
.ruler-bg { fill: color-mix(in srgb, var(--panel) 92%, transparent); }
.ruler-tick { stroke: var(--ink-3); stroke-width: 1; opacity: .75; }
.ruler-tick.minor { opacity: .35; }
.ruler-edge { stroke: var(--panel-line-2); stroke-width: 1; }
.ruler-label {
    fill: var(--ink-3);
    font-size: 9px;
    font-family: var(--font);
    shape-rendering: auto;
}
.ruler-corner {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 5;
    background: var(--panel);
    border-right: 1px solid var(--panel-line-2);
    border-bottom: 1px solid var(--panel-line-2);
    pointer-events: none;
}

/* ---- containers and notes --------------------------------------------- */

/* Only the title strip and the border band of a container take presses; its
   middle stays transparent so clicks reach whatever is genuinely there — a
   shape, a connection, or nothing.

   The tint itself has to be spelled out as inert: it is a painted path, so by
   default it takes a press right across the container, which is exactly the
   click a connection crossing the container needed. */
.group-hit { cursor: move; }
.node-shape.container-fill { pointer-events: none; }
.note-fold { pointer-events: none; }

/* Hand tool / space-drag: the whole surface reads as grabbable, shapes included. */
.canvas-svg.pan-ready,
.canvas-svg.pan-ready .node-hit,
.canvas-svg.pan-ready .edge-hit,
.canvas-svg.pan-ready .port,
.canvas-svg.pan-ready .handle { cursor: grab; }

.canvas-svg.panning,
.canvas-svg.panning .node-hit,
.canvas-svg.panning .edge-hit,
.canvas-svg.panning .port,
.canvas-svg.panning .handle { cursor: grabbing; }

.node-hit { cursor: move; }
.node-shape { transition: fill .12s ease, stroke .12s ease; }

/* ---- decorations -------------------------------------------------------
   The cards behind a stack, a band's tag chip, a legend's sample rows, the
   numbered step badges and the corner markers. All inert to the pointer: the
   shape's own hit rect is what a press lands on. */
.stack-card { pointer-events: none; opacity: .55; }
.band-chip { pointer-events: none; }
.legend-sample, .legend-caption { pointer-events: none; user-select: none; }
.node-badge, .node-marker { pointer-events: none; user-select: none; }
.node-badge text, .node-marker text { user-select: none; }
/* The caption is draggable, so unlike every other decoration it must receive pointer events —
   but only on the glyphs themselves, never the empty box around them. */
.node-label { user-select: none; pointer-events: visiblePainted; cursor: move; }
/* A label drawn inside its shape is part of the shape: presses fall through to the shape's own hit
   rect, so a card whose text fills it is still grabbed by its middle. */
.node-label.inside { pointer-events: none; }
.node-label-group.dragging .node-label { opacity: .75; }

.sel-outline { fill: none; stroke: var(--accent); stroke-width: 1.25; stroke-dasharray: 4 3; pointer-events: none; }
.handle { fill: #fff; stroke: var(--accent); stroke-width: 1.5; cursor: nwse-resize; }
.handle.n, .handle.s { cursor: ns-resize; }
.handle.e, .handle.w { cursor: ew-resize; }
.handle.ne, .handle.sw { cursor: nesw-resize; }

.port {
    fill: #fff;
    stroke: var(--accent);
    stroke-width: 1.5;
    cursor: crosshair;
    opacity: 0;
    transform-box: fill-box;
    transform-origin: center;
    transition: opacity .1s ease, transform .1s ease, fill .1s ease, stroke .1s ease;
}
.port-host:hover .port, .port.visible { opacity: 1; }
.port:hover { fill: var(--accent); }

/* ---- connection drag feedback: which node, and which port, a drop would land on ---- */

/* Droppable but not under the pointer: present, but out of the way. */
.canvas-svg.linking .port.port-dim { opacity: .3; }

/* The dot the drag started from. */
.canvas-svg.linking .port.port-origin { fill: var(--accent); }

/* The port that would receive the drop right now. */
.canvas-svg.linking .port.port-candidate {
    opacity: 1;
    fill: var(--accent);
    stroke: #fff;
    transform: scale(1.6);
}

.port-ring {
    fill: none;
    stroke: var(--accent);
    opacity: .4;
    pointer-events: none;
    transform-box: fill-box;
    transform-origin: center;
    animation: portRing .1s ease-out both;
}
@keyframes portRing { from { opacity: 0; transform: scale(.55); } to { opacity: .4; transform: scale(1); } }

.link-target { fill: none; stroke: var(--accent); pointer-events: none; }
.link-target-glow { fill: none; stroke: var(--accent); opacity: .16; pointer-events: none; }

.canvas-svg.linking,
.canvas-svg.linking .node-hit,
.canvas-svg.linking .port { cursor: crosshair; }
.canvas-svg.linking.link-ok,
.canvas-svg.linking.link-ok .node-hit,
.canvas-svg.linking.link-ok .port { cursor: alias; }

/* The clickable ribbon around a line. Its width is set per edge in the markup, from a screen-pixel
   figure, so it stays a comfortable target at every zoom — declaring it here would override that. */
.edge-hit {
    stroke: transparent;
    fill: none;
    cursor: pointer;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.edge-line { fill: none; stroke-linecap: round; stroke-linejoin: round; }

/* Selected edge: an accent halo behind the line, so the line's own colour stays visible. */
.edge-halo {
    fill: none;
    stroke: var(--accent);
    opacity: .28;
    stroke-linecap: round;
    stroke-linejoin: round;
    pointer-events: none;
}
/* A connection's caption selects the connection, and dragging it moves the caption — hence the move
   cursor, the same one a node's caption shows. The glyphs are inert so the press lands on the plate
   behind them, which covers the whole caption rather than just the inked strokes. */
.edge-label-group { cursor: move; }
.edge-label-group.dragging .edge-label { opacity: .75; }
/* No font-size here: the size is a presentation attribute off the edge, and a CSS rule would beat it. */
.edge-label { pointer-events: none; user-select: none; }
.edge-label-bg { fill: #fff; opacity: .92; }
.canvas-svg.pan-ready .edge-label-group { cursor: grab; }
.canvas-svg.panning .edge-label-group { cursor: grabbing; }

.rubber { fill: rgba(79, 70, 229, .10); stroke: var(--accent); stroke-width: 1; stroke-dasharray: 3 2; pointer-events: none; }
.guide { stroke: #f43f5e; stroke-width: 1; stroke-dasharray: 3 3; pointer-events: none; }
.link-preview { fill: none; stroke: var(--accent); stroke-width: 2; stroke-dasharray: 5 4; pointer-events: none; }
/* Endpoint locked onto a port: solid line reads as "release to connect". */
.link-preview.snapped { stroke-dasharray: none; }

.label-editor {
    width: 100%;
    height: 100%;
    border: 2px solid var(--accent);
    border-radius: 6px;
    padding: 2px 6px;
    text-align: center;
    background: #fff;
    outline: none;
    resize: none;
    font-family: var(--font);
}

/* ---------------------------------------------------------------- animations */

/* The dash pattern is a property of the line style now, so the ants only supply the motion.
   Travelling exactly one dash cycle keeps the loop seamless for solid, dashed and dotted alike.
   --dash-dir carries the sign: -1 marches source to target, +1 marches back towards the source
   for a connection whose only arrowhead is at its start. */
@keyframes ants { to { stroke-dashoffset: calc(var(--dash-period, 24) * var(--dash-dir, -1)); } }

/* Two-way connections alternate instead: the dashes march a whole number of periods forward over
   the first half of the cycle and rewind over the second, turning round at the same instant the
   flow particle does. A whole number of periods means the reversal point is seamless. */
@keyframes ants-alt {
    0%   { stroke-dashoffset: 0; }
    50%  { stroke-dashoffset: calc(var(--dash-period, 24) * var(--ants-cycles, 1) * -1); }
    100% { stroke-dashoffset: 0; }
}

.edge-animated { animation: ants var(--ants-dur, .7s) linear infinite; }
.edge-alternating { animation-name: ants-alt; }

@keyframes nodePop {
    0%   { opacity: 0; transform: scale(.72); }
    60%  { opacity: 1; transform: scale(1.04); }
    100% { opacity: 1; transform: scale(1); }
}
.node-enter { animation: nodePop .42s cubic-bezier(.2, .9, .3, 1.3) both; transform-box: fill-box; transform-origin: center; }

@keyframes edgeDraw { from { stroke-dashoffset: var(--len, 600); } to { stroke-dashoffset: 0; } }
.edge-enter { stroke-dasharray: var(--len, 600); animation: edgeDraw .5s ease-out both; }

.particle { fill: var(--accent); opacity: .85; pointer-events: none; }

/* Animations off: the ants stop, but a dashed or dotted line keeps its pattern. */
.animations-off .edge-animated { animation: none; }
.animations-off .particle { display: none; }

/* ---------------------------------------------------------------- properties */

.props { background: var(--panel); }

/* A properties section is a band: same horizontal padding as the rail head above it. */
.prop-section {
    padding: var(--gap-md) calc(var(--pad-rail) - var(--scrollbar-w)) var(--gap-md) var(--pad-rail);
    border-bottom: 1px solid var(--panel-line);
}
.prop-section:last-child { border-bottom: 0; }

.prop-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--gap-sm);
    min-height: var(--ctl-h);
}
.prop-kind {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 13px;
    font-weight: 650;
    color: var(--ink);
}
.prop-kind .icon { color: var(--accent); }

.prop-title {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--ink-3);
    margin: 0 0 var(--gap-sm);
}

/* Label / control grid — every row lines up on one axis. */
.prop-grid {
    display: grid;
    grid-template-columns: 66px 1fr;
    align-items: center;
    gap: var(--gap-sm) var(--gap-md);
}
.prop-grid.four { grid-template-columns: 20px 1fr 20px 1fr; }
.prop-grid > label {
    font-size: 11.5px;
    color: var(--ink-2);
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.prop-grid .readout { font-size: 12px; color: var(--ink-2); font-family: var(--mono); }

/*
   Every control that can sit on a row with another control is pinned to one of the three
   --ctl-h sizes, so a field, a button and an icon button on the same line share a baseline
   and a hit target instead of each ending up whatever their own padding made them.
*/
.field {
    width: 100%;
    min-height: var(--ctl-h-lg);
    border: 1px solid var(--panel-line-2);
    border-radius: var(--radius-sm);
    padding: 6px var(--gap-sm);
    background: #fff;
    min-width: 0;
    transition: border-color .12s ease, box-shadow .12s ease;
}
.field:hover { border-color: var(--panel-line-2); }
.field:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.field.sm { min-height: var(--ctl-h); padding: 4px 7px; font-size: 12.5px; }
/* Multi-line fields size to their own content, so they opt out of the row height. */
textarea.field { min-height: var(--ctl-h-lg); }
/* A native select ignores min-height and sizes itself, ending up a pixel or two taller
   than the input beside it. Pinning the height keeps a select and an input on one line. */
select.field { height: var(--ctl-h-lg); }
select.field.sm { height: var(--ctl-h); }
.label-area { resize: vertical; min-height: 46px; line-height: 1.45; font-family: var(--font); }

/* Colour picker + hex text, always in step with each other. */
.color-field { display: flex; align-items: center; gap: var(--gap-sm); min-width: 0; }
.color-field .swatch {
    flex: none;
    width: 38px;
    height: var(--ctl-h-lg);
    padding: 2px;
    border: 1px solid var(--panel-line-2);
    border-radius: var(--radius-sm);
    background: #fff;
    cursor: pointer;
}
.color-field .swatch::-webkit-color-swatch-wrapper { padding: 0; }
.color-field .swatch::-webkit-color-swatch { border: 0; border-radius: 4px; }
.color-field .swatch::-moz-color-swatch { border: 0; border-radius: 4px; }
.color-field .hex { flex: 1; min-width: 0; text-transform: lowercase; font-size: 12px; }

.slider-field { display: flex; align-items: center; gap: var(--gap-sm); min-width: 0; }
.slider-field input[type=range] { flex: 1; min-width: 0; accent-color: var(--accent); }
/* Paired with the slider once the range is wide enough that one pixel is more than one step. */
.slider-field .num-input { flex: none; width: 58px; min-height: var(--ctl-h); padding: 4px 6px; text-align: right; }
.slider-field .btn { flex: none; }
.slider-field .num {
    flex: none;
    width: 30px;
    text-align: right;
    font-family: var(--mono);
    font-size: 11.5px;
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
}

.swatch-row { display: flex; flex-wrap: wrap; gap: var(--gap-sm); }
.preset-chip {
    position: relative;
    width: var(--ctl-h);
    height: var(--ctl-h-sm);
    padding: 0;
    border: 1.5px solid var(--panel-line-2);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: transform .08s ease, box-shadow .12s ease;
}
.preset-chip span {
    position: absolute;
    left: 4px;
    right: 4px;
    bottom: 3px;
    height: 3px;
    border-radius: 2px;
}
.preset-chip:hover, .preset-dot:hover { transform: translateY(-1px); box-shadow: 0 3px 9px rgba(15, 23, 42, .18); }
.preset-dot {
    width: 24px;
    height: 24px;
    padding: 0;
    border: 2px solid #fff;
    border-radius: 50%;
    cursor: pointer;
    box-shadow: 0 0 0 1px var(--panel-line-2);
    transition: transform .08s ease, box-shadow .12s ease;
}

.empty-state { padding: 24px var(--pad-x); text-align: center; color: var(--ink-3); font-size: 12.5px; line-height: 1.6; }
.empty-state strong { display: block; color: var(--ink-2); margin-bottom: 5px; font-size: 13px; }
.empty-state.tall { padding: 40px var(--pad-x); }
/* A quieter second thought under an empty state — where to go instead, not what went wrong.
   Same muted size as the offline notice's aside, so the two read as the same kind of remark. */
.empty-state .empty-aside { display: block; margin-top: var(--gap-sm); font-size: 11.5px; color: var(--ink-3); }
/* Inside a rail the surrounding band already supplies the horizontal inset. */
.rail .empty-state, .palette .empty-state { padding-left: 0; padding-right: 0; }
.empty-actions { display: flex; justify-content: center; gap: var(--gap-sm); margin-top: var(--gap-md); }
.empty-glyph {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 52px;
    height: 52px;
    margin-bottom: 12px;
    border-radius: var(--radius-lg);
    background: var(--accent-soft);
    color: var(--accent);
}

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: var(--ctl-h-lg);
    border: 1px solid var(--panel-line-2);
    background: #fff;
    border-radius: var(--radius-sm);
    padding: var(--gap-xs) var(--gap-md);
    cursor: pointer;
    transition: background .12s ease, border-color .12s ease;
}
.btn:hover:not(:disabled) { background: #f1f5f9; }
.btn:disabled { opacity: .5; cursor: default; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.primary:hover:not(:disabled) { background: #4338ca; }
.btn.danger { color: var(--danger); border-color: #fecaca; }
.btn.danger:hover:not(:disabled) { background: #fef2f2; }
/* The small button is exactly icon-button height, so the two can share a row. */
.btn.sm { min-height: var(--ctl-h); padding: 2px var(--gap-sm); font-size: 12px; }

.btn.ghost-light { background: transparent; border-color: #33415580; color: #cbd5e1; }
.btn.ghost-light:hover:not(:disabled) { background: #2b384f; color: #fff; }
.btn .icon { flex: none; }

.grow { flex: 1; min-width: 0; }

.chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border-radius: var(--radius-pill);
    padding: 3px 9px;
    font-size: 11px;
    font-weight: 600;
    background: #f1f5f9;
    color: var(--ink-2);
    white-space: nowrap;
}
.chip.ok { background: #ecfdf5; color: var(--ok); }
.chip.bad { background: #fef2f2; color: var(--danger); }
.chip.warn { background: #fffbeb; color: #b45309; }
.chip.tiny { padding: 1px 7px; font-size: 10px; margin-left: 6px; }
.appbar .chip { background: #16233a; color: #cbd5e1; border: 1px solid #2c3a52; }
.appbar .chip.ok { background: #052e22; color: #34d399; border-color: #10503c; }
.appbar .chip.warn { background: #3a2a06; color: #fbbf24; border-color: #6b4708; }
.appbar .chip.bad { background: #3b0d0d; color: #fca5a5; border-color: #7f1d1d; }

/* ---------------------------------------------------------------- backend offline */

.offline-group { gap: 4px; }
.offline-group .chip { cursor: help; }

.offline-banner {
    display: flex;
    align-items: center;
    gap: var(--gap-md);
    padding: var(--gap-md) var(--pad-x);
    background: #fef2f2;
    border-bottom: 1px solid #fecaca;
    color: #7f1d1d;
    font-size: 12.5px;
    line-height: 1.55;
}
.offline-banner > .icon { flex: none; color: var(--danger); }
.offline-banner-text { flex: 1; min-width: 0; }
.offline-banner code {
    font-family: var(--mono);
    font-size: 11.5px;
    background: #fff;
    border: 1px solid #fecaca;
    border-radius: 5px;
    padding: 1px 6px;
    white-space: nowrap;
}
.offline-banner .mono { font-family: var(--mono); font-size: 11.5px; }
.offline-banner-ok { display: block; color: #9a3412; opacity: .85; }
.offline-banner .btn { flex: none; }
.offline-banner .icon-btn { flex: none; color: #7f1d1d; }

.offline-note .empty-glyph { background: #fef2f2; color: var(--danger); }
.offline-note .offline-what { display: block; color: var(--ink-2); margin-bottom: 4px; }

.offline-steps {
    display: flex;
    flex-direction: column;
    gap: var(--gap-sm);
    margin: var(--gap-md) auto;
    padding: var(--gap-md);
    max-width: 420px;
    text-align: left;
    background: #fff;
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
}
.offline-step { display: flex; align-items: center; gap: 8px; color: var(--ink-2); }
.offline-step-n {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 17px;
    height: 17px;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 10px;
    font-weight: 700;
}
.offline-cmd {
    display: block;
    font-family: var(--mono);
    font-size: 11.5px;
    color: var(--ink);
    background: var(--panel);
    border: 1px solid var(--panel-line-2);
    border-radius: 6px;
    padding: 6px 8px;
    margin-left: 25px;
    overflow-x: auto;
    white-space: nowrap;
    user-select: all;
}
.offline-aside { margin: 6px 0 0; font-size: 11.5px; color: var(--ink-3); }
.offline-aside .mono { font-family: var(--mono); font-size: 11px; }
.offline-aside.ok { color: var(--ok); }
.offline-actions { margin-top: var(--gap-md); }

.offline-note.compact { padding: var(--gap-md) 0; font-size: 11.5px; }
.offline-note.compact .empty-glyph { width: 38px; height: 38px; margin-bottom: var(--gap-sm); border-radius: var(--radius); }
.offline-note.compact .offline-steps { margin: var(--gap-sm) 0; padding: var(--gap-sm); gap: var(--gap-xs); }
.offline-note.compact .offline-cmd { margin-left: 0; font-size: 10.5px; }

.drop-zone.disabled { opacity: .55; cursor: not-allowed; }

/* ---------------------------------------------------------------- animated GIF export */

.gif-progress { padding: var(--gap-sm) 0; }
.gif-progress-label {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: 12.5px;
    color: var(--ink-2);
    margin-bottom: var(--gap-sm);
}
.gif-bar {
    height: 8px;
    border-radius: var(--radius-pill);
    background: var(--panel-line);
    overflow: hidden;
}
.gif-bar-fill {
    height: 100%;
    background: var(--accent);
    border-radius: var(--radius-pill);
    transition: width .12s linear;
}
.gif-estimate {
    margin-top: var(--gap-lg);
    padding: var(--gap-md);
    border-radius: var(--radius);
    background: var(--panel);
    border: 1px solid var(--panel-line);
    font-size: 12px;
    color: var(--ink-2);
    line-height: 1.55;
}
.gif-estimate strong { color: var(--ink); }
.gif-warn { color: #b45309; }

.spinner {
    width: 11px;
    height: 11px;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.seg { display: flex; border: 1px solid var(--panel-line-2); border-radius: var(--radius-sm); overflow: hidden; background: #fff; }
.seg button {
    flex: 1;
    min-width: 0;
    min-height: var(--ctl-h);
    border: 0;
    background: #fff;
    padding: 4px;
    cursor: pointer;
    font-size: 12px;
    color: var(--ink-2);
}
.seg button + button { border-left: 1px solid var(--panel-line); }
.seg button:hover:not(.on) { background: #f1f5f9; }
.seg button.on { background: var(--accent); color: #fff; }

/* Four options do not fit on one row in the rail, so the Direction control wraps. */
.seg.seg-wrap { flex-wrap: wrap; }
.seg.seg-wrap button { flex: 1 0 46%; }
.seg.seg-wrap button:nth-child(3) { border-left: 0; border-top: 1px solid var(--panel-line); }
.seg.seg-wrap button:nth-child(4) { border-top: 1px solid var(--panel-line); }

.btn.wide { width: 100%; }

.switch { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; user-select: none; font-size: 12.5px; }
.switch input { width: 15px; height: 15px; accent-color: var(--accent); cursor: pointer; }

/* ---------------------------------------------------------------- chat */

/* The chat rail is bands too: head, log, suggestions, composer — all on --pad-rail. */
.chat { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.chat-log {
    flex: 1;
    overflow-y: auto;
    scrollbar-gutter: stable;
    padding: var(--gap-md) calc(var(--pad-rail) - var(--scrollbar-w)) var(--gap-md) var(--pad-rail);
    display: flex;
    flex-direction: column;
    gap: var(--gap-md);
}

.msg { max-width: 92%; border-radius: var(--radius); padding: 8px 11px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; font-size: 13px; }
.msg.user { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 4px; }
.msg.assistant { align-self: flex-start; background: #f1f5f9; border-bottom-left-radius: 4px; }
.msg.error { align-self: stretch; background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; max-width: 100%; }
.msg.system { align-self: center; background: #ecfdf5; color: #065f46; font-size: 12px; }

/* A reasoning model's chain of thought: available, collapsed, and visibly not the answer. */
.msg-thinking { align-self: flex-start; max-width: 92%; font-size: 11.5px; }
.msg-thinking summary {
    cursor: pointer;
    color: var(--ink-3);
    padding: 3px 2px;
    user-select: none;
    list-style: revert;
}
.msg-thinking summary:hover { color: var(--accent); }
.msg-thinking .thinking-body {
    margin-top: 5px;
    padding: 8px 10px;
    background: #f8fafc;
    border: 1px dashed var(--panel-line-2);
    border-radius: var(--radius);
    color: var(--ink-2);
    font-family: var(--mono);
    font-size: 11px;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 260px;
    overflow: auto;
}

.msg .caret { display: inline-block; width: 7px; background: currentColor; animation: blink 1s step-end infinite; }
.msg .pending { opacity: .55; font-style: italic; }
@keyframes blink { 50% { opacity: 0; } }

/* What the turn cost. Quiet by design: it is there to be glanced at, not read. */
.msg-cost {
    align-self: flex-start;
    margin: -2px 0 2px 2px;
    color: var(--ink-2);
    font-family: var(--mono);
    font-size: 10.5px;
    letter-spacing: 0.01em;
    cursor: help;
}

.chat-progress {
    padding: 6px 12px;
    border-top: 1px solid var(--panel-line);
    color: var(--ink-2);
    font-size: 11.5px;
}

.chat-input {
    border-top: 1px solid var(--panel-line);
    padding: var(--gap-md) var(--pad-rail);
    display: flex;
    flex-direction: column;
    gap: var(--gap-sm);
    flex: none;
}
.chat-input textarea { resize: none; min-height: 60px; max-height: 160px; }
.chat-actions { display: flex; gap: var(--gap-sm); align-items: center; }

.suggestions { display: flex; flex-wrap: wrap; gap: var(--gap-sm); padding: 0 var(--pad-rail) var(--gap-md); flex: none; }
.suggestion {
    display: inline-flex;
    align-items: center;
    min-height: var(--ctl-h-sm);
    border: 1px solid var(--panel-line-2);
    background: #fff;
    border-radius: var(--radius-pill);
    padding: var(--gap-xs) var(--gap-md);
    font-size: 11.5px;
    cursor: pointer;
    color: var(--ink-2);
    text-align: left;
}
.suggestion:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* ---------------------------------------------------------------- settings page */

.settings {
    height: 100vh;
    overflow-y: auto;
    background: #eef2f7;
    color: var(--ink);
}

.settings-bar {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    gap: var(--gap-md);
    height: var(--bar-h);
    padding: 0 var(--pad-x);
    background: linear-gradient(180deg, #22304a, #161f31);
    border-bottom: 1px solid #0b1224;
    color: #e2e8f0;
}
.settings-heading {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    font-size: 14.5px;
    font-weight: 650;
    color: #f1f5f9;
}
.settings-heading .icon { color: #818cf8; }

.settings-inner { max-width: 1180px; margin: 0 auto; padding: var(--pad-y) var(--pad-x) 60px; }
.settings .sub { color: var(--ink-2); margin: 0 0 var(--pad-y); max-width: 78ch; line-height: 1.6; }

/*
   `min-width: 0` on the tracks is load-bearing: a grid track's automatic minimum is its
   content's min-content size, so a card holding a nowrap URL or a mono hint used to widen
   its own column past the viewport and get silently clipped by the page's overflow:hidden.
*/
.settings-cols { display: grid; grid-template-columns: minmax(300px, 400px) 1fr; gap: var(--gap-lg); align-items: start; }
.settings-cols > * { min-width: 0; }
@media (max-width: 900px) { .settings-cols { grid-template-columns: minmax(0, 1fr); } }

/* A card is a band: --pad-x horizontally, --pad-y vertically, like a dialog. */
.card {
    background: #fff;
    border: 1px solid var(--panel-line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    padding: var(--pad-y) var(--pad-x);
    margin-bottom: var(--gap-lg);
    min-width: 0;
}
.card-head { display: flex; align-items: center; gap: var(--gap-sm); min-height: var(--ctl-h); margin-bottom: var(--gap-md); }
.card-head h2 { font-size: 14.5px; margin: 0; flex: 1; min-width: 0; }
.card-head .count {
    font-size: 11px;
    font-weight: 700;
    color: var(--ink-3);
    background: #f1f5f9;
    border-radius: var(--radius-pill);
    padding: 2px 9px;
}

/* Same row vocabulary as the dialog's diagram list: glyph/toggle, meta, actions. */
.provider-list { display: flex; flex-direction: column; gap: var(--gap-sm); }
.provider-row {
    display: flex;
    align-items: flex-start;
    gap: var(--gap-md);
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    padding: var(--gap-md);
    cursor: pointer;
    background: #fff;
    min-width: 0;
    transition: border-color .12s ease, box-shadow .12s ease, background .12s ease;
}
.provider-row:hover { border-color: var(--accent-line); box-shadow: 0 2px 10px rgba(79, 70, 229, .08); }
.provider-row.sel { border-color: var(--accent); background: var(--accent-soft); }
/* The active marker is an inset rule, not a border: a 3px border would shift the row's
   whole content 3px left of every other row's. */
.provider-row.active { box-shadow: inset 3px 0 0 var(--ok); }
.provider-row.active:hover { box-shadow: inset 3px 0 0 var(--ok), 0 2px 10px rgba(79, 70, 229, .08); }
.provider-row .meta { flex: 1; min-width: 0; }
.provider-row .nm { font-weight: 650; display: flex; align-items: center; flex-wrap: wrap; min-height: var(--ctl-h-sm); }
.provider-row .url {
    color: var(--ink-3);
    font-size: 11.5px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin-top: 2px;
}
.provider-row .row-tools { display: flex; align-items: center; gap: var(--gap-xs); flex: none; height: var(--ctl-h); }

.active-dot {
    flex: none;
    width: 20px;
    height: 20px;
    margin-top: 3px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    border: 1.5px solid var(--panel-line-2);
    background: #fff;
    color: #fff;
    cursor: pointer;
    padding: 0;
    transition: background .12s ease, border-color .12s ease;
}
.active-dot:hover { border-color: var(--ok); }
.active-dot.on { background: var(--ok); border-color: var(--ok); }

.preset-block { margin-top: var(--pad-y); border-top: 1px dashed var(--panel-line); padding-top: var(--pad-y); }
.preset-label {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--ink-3);
    margin-bottom: var(--gap-sm);
}
.preset-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: var(--gap-sm); }
.preset-grid > * { min-width: 0; }
.preset-btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-height: var(--ctl-h-lg);
    min-width: 0;
    border: 1px solid var(--panel-line-2);
    background: #fff;
    border-radius: var(--radius-sm);
    padding: var(--gap-xs) var(--gap-md);
    font-size: 12.5px;
    cursor: pointer;
    transition: border-color .12s ease, background .12s ease, color .12s ease;
}
.preset-btn span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.preset-btn:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.preset-btn .icon { color: var(--ink-3); }
.preset-btn:hover .icon { color: var(--accent); }

.field-group { border-top: 1px solid var(--panel-line); padding-top: var(--pad-y); margin-top: var(--pad-y); }
.field-group:first-of-type { border-top: 0; padding-top: 0; margin-top: 0; }
.group-title {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--ink-3);
    margin-bottom: var(--gap-md);
}

.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap-md) var(--gap-lg); }
.form-grid .full { grid-column: 1 / -1; }
.form-grid > * { min-width: 0; }
.form-grid label:not(.switch) { display: block; font-size: 11.5px; color: var(--ink-2); margin-bottom: var(--gap-xs); font-weight: 600; }
@media (max-width: 620px) { .form-grid { grid-template-columns: minmax(0, 1fr); } }

/* A field with a trailing control: both take the form-control height so the row has one baseline. */
.key-field { display: flex; align-items: center; gap: var(--gap-sm); min-width: 0; }
.key-field .field { flex: 1; min-width: 0; }
.key-field .icon-btn,
.key-field .btn { flex: none; height: var(--ctl-h-lg); min-height: var(--ctl-h-lg); }
.key-field .icon-btn { width: var(--ctl-h-lg); border: 1px solid var(--panel-line-2); }

.card-actions { display: flex; gap: var(--gap-sm); align-items: center; flex-wrap: wrap; margin-top: var(--pad-y); }

.test-result { margin-top: var(--gap-md); border-radius: var(--radius); padding: var(--gap-md); border: 1px solid var(--panel-line); }
.test-result.ok { background: #f0fdf4; border-color: #bbf7d0; }
.test-result.bad { background: #fef2f2; border-color: #fecaca; }
.test-head { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }
.test-detail {
    margin: var(--gap-sm) 0 0;
    font-size: 11.5px;
    color: var(--ink-2);
    white-space: pre-wrap;
    word-break: break-word;
    font-family: var(--mono);
    max-height: 190px;
    overflow: auto;
}
/* An explanatory note about a technically-successful test, not model output. */
.test-detail.note {
    font-family: var(--font);
    color: #b45309;
    background: #fffbeb;
    border: 1px solid #fde68a;
    border-radius: var(--radius-sm);
    padding: 7px 9px;
}

.example-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--gap-md) var(--gap-lg); }
.example-grid > * { min-width: 0; }
.example-grid label { display: block; font-size: 11.5px; color: var(--ink-2); margin-bottom: var(--gap-xs); font-weight: 600; }

.hint { font-size: 11.5px; color: var(--ink-3); margin-top: var(--gap-xs); line-height: 1.55; }
.hint .mono { font-size: 11px; }
.mono { font-family: var(--mono); font-size: 12px; }
/* A URL or a model id is one unbreakable token; without this it walks straight out of
   its grid cell and over the neighbouring column instead of wrapping inside it. */
.example-grid .mono, .hint .mono, .settings .sub .mono { overflow-wrap: anywhere; }

.toast-host {
    position: fixed;
    right: var(--pad-x);
    bottom: var(--pad-x);
    display: flex;
    flex-direction: column;
    gap: var(--gap-sm);
    z-index: 5000;
}
.toast {
    background: #0f172a;
    color: #e2e8f0;
    border-radius: var(--radius);
    padding: var(--gap-md) var(--pad-x);
    box-shadow: var(--shadow);
    font-size: 12.5px;
    max-width: 380px;
    animation: toastin .2s ease-out;
}
.toast.bad { background: #7f1d1d; }
.toast.good { background: #064e3b; }
@keyframes toastin { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

.modal-back {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, .5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 4500;
    padding: var(--pad-x);
}
.modal {
    background: #fff;
    border-radius: var(--radius-xl);
    padding: 0;
    width: 100%;
    max-width: 460px;
    box-shadow: 0 24px 60px rgba(2, 6, 23, .45);
    display: flex;
    flex-direction: column;
    max-height: min(78vh, 720px);
    animation: modalIn .14s ease-out;
}
.modal-lg { max-width: 620px; }
@keyframes modalIn { from { opacity: 0; transform: translateY(10px) scale(.985); } to { opacity: 1; transform: none; } }

/*
   ---- dialog bands ----------------------------------------------------------
   A dialog is a stack of bands: head, body, any number of --modal-band sections,
   and the actions footer. Every one of them takes --pad-x horizontally and
   --pad-y vertically, so the title, the list rows, a section label and the
   footer buttons all sit on the same two vertical axes, and the gap between any
   two neighbouring bands is the same 2 x --pad-y.

   Only the head and the footer are fixed: the body is the flex child that gives,
   so a library of twenty diagrams scrolls under a header and footer that stay put.
*/
.modal-head,
.modal-band,
.modal-actions { flex: none; }

.modal-head {
    display: flex;
    align-items: flex-start;
    gap: var(--gap-md);
    padding: var(--pad-y) var(--pad-x);
    border-bottom: 1px solid var(--panel-line);
}
.modal-head h3 { margin: 0; font-size: 16px; line-height: var(--ctl-h); }
.modal-head-text { flex: 1; min-width: 0; }
.modal-head > div:first-child { flex: 1; min-width: 0; }
.modal-sub { margin: 0; font-size: 12px; color: var(--ink-3); line-height: 1.5; }

/*
   The body is the only band that scrolls, so it is the only one whose width can change
   under it. `scrollbar-gutter: stable both-edges` reserves that width on both sides
   permanently — the list is laid out identically at two diagrams and at twenty — and the
   rows below hand the reserved gutter back out of their own padding, so a row's content
   still starts and ends on the same axes as the fixed header and footer either way.
*/
.modal-body {
    padding: var(--gap-sm) var(--pad-x);
    overflow-y: auto;
    scrollbar-gutter: stable both-edges;
    flex: 1;
    min-height: 0;
}
/* The band already supplies the horizontal inset. */
.modal-body > .empty-state { padding-left: 0; padding-right: 0; }

/* A titled section between the body and the footer. */
.modal-band { padding: var(--pad-y) var(--pad-x); border-top: 1px solid var(--panel-line); }
.modal-band-head { margin-bottom: var(--gap-sm); }
.modal-band-title {
    display: block;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--ink-3);
    margin-bottom: var(--gap-xs);
}
.modal-band-note { display: block; font-size: 12px; color: var(--ink-3); line-height: 1.5; }

.modal-actions {
    display: flex;
    align-items: center;
    gap: var(--gap-sm);
    padding: var(--pad-y) var(--pad-x);
    border-top: 1px solid var(--panel-line);
}

/* ---- server diagram list ----
   The rows are full-bleed inside their band — pulled out to the band's edges and
   given the band's own --pad-x back as their padding — so a row's glyph starts on
   exactly the same axis as the dialog title, and a row's last action button ends
   on the same axis as the close button and the footer's Cancel. */
.doc-list {
    display: flex;
    flex-direction: column;
    margin-inline: calc(-1 * var(--pad-x));
}
.doc-row {
    display: flex;
    align-items: center;
    gap: var(--gap-md);
    padding: var(--gap-sm) var(--pad-x);
    min-height: 56px;
    border: 0;
    border-top: 1px solid transparent;
    border-bottom: 1px solid transparent;
    cursor: pointer;
    transition: background .12s ease, box-shadow .12s ease;
}
.doc-row + .doc-row { border-top-color: var(--panel-line); }
/* Inside the scrolling body the reserved gutter already supplies part of the inset. */
.modal-body .doc-row { padding-inline: calc(var(--pad-x) - var(--scrollbar-w)); }
.doc-row:hover { background: #f8fafc; }
.doc-row.sel { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.doc-glyph {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ctl-h-lg);
    height: var(--ctl-h-lg);
    flex: none;
    border-radius: var(--radius);
    background: #eef2ff;
    color: var(--accent);
}
.doc-meta { flex: 1; min-width: 0; }
.doc-name { font-weight: 600; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.doc-sub { font-size: 11.5px; color: var(--ink-3); margin-top: 1px; }
/* Fixed height and a shared control size keep the actions on the row's centre line
   whether the row is showing Open/rename/delete or the rename Save/Cancel pair. */
.doc-actions {
    display: flex;
    align-items: center;
    gap: var(--gap-xs);
    flex: none;
    height: var(--ctl-h);
    opacity: .8;
    transition: opacity .12s ease;
}
.doc-actions > .btn, .doc-actions > .icon-btn { height: var(--ctl-h); min-height: var(--ctl-h); flex: none; }
.doc-row:hover .doc-actions, .doc-row.sel .doc-actions { opacity: 1; }
/* The rename box replaces the name in place, so it must not change the row's height. */
.doc-meta > .field.sm { height: var(--ctl-h); }

::-webkit-scrollbar { width: var(--scrollbar-w); height: var(--scrollbar-w); }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 6px; border: 2px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; }

/* ---------------------------------------------------------------- new-from-template gallery
   A card per starting point: the preview does the explaining, the name identifies it and the
   description says what it is for. Two columns, collapsing to one where the dialog is narrower
   than two readable cards. */
.tpl-group + .tpl-group { margin-top: var(--gap-lg); }
.tpl-group-head {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--ink-3);
    margin-bottom: var(--gap-sm);
}
.tpl-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--gap-md);
}
.tpl-card {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: var(--gap-sm);
    text-align: left;
    background: #fff;
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    cursor: pointer;
    transition: border-color .12s ease, box-shadow .12s ease, background .12s ease;
}
.tpl-card:hover, .tpl-card:focus-visible {
    background: #f8fafc;
    border-color: var(--accent);
    box-shadow: 0 2px 10px rgb(15 23 42 / 8%);
}
/* The preview is a data-URI <img>, so it is its own document: the diagram's marker ids and font
   rule cannot reach the live canvas behind the dialog. Fixed height, contained, so six drawings of
   six different shapes still line their captions up. */
.tpl-preview {
    display: block;
    width: 100%;
    height: 104px;
    object-fit: contain;
    background: #f8fafc;
    border-radius: var(--radius-sm);
    margin-bottom: 2px;
}
.tpl-name { font-size: 13px; font-weight: 600; color: var(--ink); }
.tpl-desc { font-size: 11.5px; line-height: 1.45; color: var(--ink-3); }

/*
   ---- share dialog ----------------------------------------------------------
   Two states of one panel, told apart by colour rather than by wording alone:
   on is the same green the "saved" chip uses, off is the neutral panel tint. The
   address itself is a real read-only input rather than styled text, because the
   thing a reader most wants to do with it is select it, and only an input makes
   that work with a click, a keyboard and a screen reader alike.
*/
.share-state {
    display: flex;
    flex-direction: column;
    gap: var(--gap-xs);
    padding: var(--gap-sm) var(--gap-md);
    border-radius: var(--radius);
    border: 1px solid var(--panel-line);
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--ink-2);
}
.share-state strong { display: flex; align-items: center; gap: var(--gap-xs); font-size: 13px; color: var(--ink); }
.share-state.on { background: #ecfdf5; border-color: #a7f3d0; }
.share-state.on strong { color: var(--ok); }
.share-state.off { background: #f8fafc; }

.share-address { display: flex; align-items: center; gap: var(--gap-sm); margin-top: var(--gap-sm); }
/* The address can be long; it gives, the Copy button never does. */
.share-address .field { flex: 1; min-width: 0; }
.share-address .btn { flex: none; }

/*
   ---- Mermaid import --------------------------------------------------------
   The text box is the dialog. It is monospaced because Mermaid's meaning is in
   its indentation and its arrows, and it resizes vertically only, so dragging it
   taller cannot push the dialog wider than the screen.
*/
.mermaid-import { display: flex; flex-direction: column; gap: var(--gap-sm); }
.mermaid-import-bar { display: flex; align-items: center; gap: var(--gap-sm); }
.mermaid-import-file {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--ink-3);
}
.mermaid-import-text {
    width: 100%;
    resize: vertical;
    line-height: 1.55;
    tab-size: 2;
    white-space: pre;
    overflow-wrap: normal;
    overflow-x: auto;
}

/* ============================================================================
   Cookie consent bar.
   ----------------------------------------------------------------------------
   In app.css rather than marketing.css because it lives in MainLayout and can
   therefore appear over any route, including the editor shell, which never
   loads the marketing stylesheet. It defines every colour it uses instead of
   borrowing a token, for the same reason: neither stylesheet is guaranteed to
   have run first.

   It is a strip, not a modal. No backdrop, no scroll lock, nothing that makes
   the page unusable behind it — a consent request that holds the page hostage
   is not a request. The page's own calls to action sit above it, and the bar
   caps its own height and scrolls internally so a long sentence on a short
   phone cannot grow it over the whole viewport.
   ========================================================================== */

.cc-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 400;
    max-height: 55vh;
    overflow-y: auto;
    padding: 14px 18px calc(14px + env(safe-area-inset-bottom, 0px));
    background: #ffffff;
    color: #0f172a;
    border-top: 1px solid #cfd8e6;
    box-shadow: 0 -12px 34px rgba(15, 23, 42, .14);
    font-size: 14px;
    line-height: 1.55;
}

.cc-inner {
    max-width: 1140px;
    margin: 0 auto;
    display: flex;
    gap: 18px;
    align-items: center;
    flex-wrap: wrap;
}

.cc-copy { flex: 1 1 420px; min-width: 0; }

.cc-copy strong {
    display: block;
    margin-bottom: 3px;
    font-size: 15px;
    font-weight: 680;
}

/* The heading is focusable so a visitor who reopened the bar from the footer
   can be sent to it. It must not draw a ring when it is not the reason focus
   moved, hence :focus-visible rather than :focus. */
.cc-copy strong:focus { outline: none; }

.cc-copy strong:focus-visible {
    outline: 2px solid #4f46e5;
    outline-offset: 3px;
    border-radius: 4px;
}

.cc-copy p { margin: 0; color: #475569; }

.cc-copy a { color: #4338ca; }

.cc-actions {
    display: flex;
    gap: 10px;
    flex: 0 0 auto;
}

/* Accept and Decline share one class and differ only in fill. Same height, same
   padding, same weight, same font size, adjacent, one click each — the
   asymmetry that makes a consent bar dishonest is a decline that costs more
   than an accept, so there is deliberately no way to style one down. */
.cc-btn {
    min-width: 118px;
    height: 40px;
    padding: 0 20px;
    border-radius: 12px;
    border: 1px solid transparent;
    font-family: inherit;
    font-size: 14.5px;
    font-weight: 620;
    cursor: pointer;
}

.cc-btn:focus-visible {
    outline: 2px solid #4f46e5;
    outline-offset: 2px;
}

.cc-yes { background: #4f46e5; color: #ffffff; }
.cc-yes:hover { background: #4338ca; }

.cc-no { background: #eef1f6; color: #0f172a; border-color: #cfd8e6; }
.cc-no:hover { background: #e2e7ef; }

/* The note shown once, after a withdrawal. */
.cc-note {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 400;
    display: flex;
    gap: 16px;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    padding: 14px 18px calc(14px + env(safe-area-inset-bottom, 0px));
    background: #0f172a;
    color: #e2e8f0;
    font-size: 14px;
    line-height: 1.55;
}

.cc-note p { margin: 0; max-width: 720px; }

.cc-note .cc-btn {
    background: rgba(255, 255, 255, .12);
    color: #ffffff;
    border-color: rgba(255, 255, 255, .26);
}

.cc-note .cc-btn:hover { background: rgba(255, 255, 255, .2); }

@media (prefers-color-scheme: dark) {
    .cc-bar {
        background: #131a2e;
        color: #e8ecf7;
        border-top-color: #2a3350;
        box-shadow: 0 -12px 34px rgba(0, 0, 0, .5);
    }

    .cc-copy p { color: #aab4d0; }
    .cc-copy a { color: #a5b4fc; }

    .cc-no { background: #1e2740; color: #e8ecf7; border-color: #38425f; }
    .cc-no:hover { background: #26314e; }
}

@media (max-width: 620px) {
    .cc-actions { width: 100%; }
    .cc-btn { flex: 1 1 0; min-width: 0; }
}

/* ----------------------------------------------------------------- admin page */
/* User management reuses the settings shell (.settings, .settings-bar, .card); what follows is
   only what that page does not already have: the slim totals strip with its sparkline, the
   accounts table with its search and sortable headers, and the typed delete confirmation's body. */

/* ---- the totals strip: one row of small numbers, the signups line at its end ---- */
.admin-strip {
    display: flex;
    align-items: center;
    gap: var(--gap-lg) calc(var(--gap-lg) + var(--gap-sm));
    flex-wrap: wrap;
    padding: var(--gap-md) var(--pad-x);
}
.admin-strip-loading { font-size: 12px; color: var(--ink-3); }
.admin-stat { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.admin-stat .num {
    font-size: 15px;
    font-weight: 700;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.admin-stat .lbl,
.admin-spark-head .lbl {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ink-3);
    white-space: nowrap;
}
.admin-spark { display: flex; flex-direction: column; gap: 3px; margin-left: auto; min-width: 0; }
.admin-spark-head { display: flex; align-items: center; gap: var(--gap-sm); }
.mini-toggle {
    display: inline-flex;
    border: 1px solid var(--panel-line-2);
    border-radius: var(--radius-pill);
    overflow: hidden;
}
.mini-toggle button {
    border: 0;
    background: #fff;
    font-size: 10px;
    font-weight: 700;
    padding: 1px 8px;
    color: var(--ink-2);
    cursor: pointer;
}
.mini-toggle button.on { background: var(--accent); color: #fff; }
.signup-chart {
    width: 200px;
    max-width: 100%;
    height: 40px;
    display: block;
    border: 1px solid var(--panel-line);
    border-radius: var(--radius-sm);
    background: #fbfdff;
}
.signup-chart .axis { stroke: var(--panel-line); stroke-width: 1; }
.signup-chart .line { stroke: var(--accent); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
@media (max-width: 900px) {
    .admin-spark { margin-left: 0; flex-basis: 100%; }
    .signup-chart { width: 100%; }
}

/* ---- the accounts table ---- */
.admin-search { position: relative; width: min(280px, 60vw); flex: none; }
.admin-search .icon {
    position: absolute;
    left: 8px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--ink-3);
    pointer-events: none;
}
.admin-search .field { padding-left: 28px; }

/* Full-bleed inside the card, like the dialog's document list: the first and last cells take the
   card's own --pad-x back as their padding, so the email column starts on the card title's axis
   and the actions end on the search box's. Below 900px the wrap scrolls sideways, never the page. */
.admin-table-wrap { overflow-x: auto; margin-inline: calc(-1 * var(--pad-x)); }
.admin-table { width: 100%; min-width: 760px; border-collapse: collapse; font-size: 12.5px; }
.admin-table th {
    text-align: left;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ink-3);
    padding: var(--gap-xs) var(--gap-sm);
    border-bottom: 1px solid var(--panel-line);
    white-space: nowrap;
}
.admin-table th:first-child, .admin-table td:first-child { padding-left: var(--pad-x); }
.admin-table th:last-child, .admin-table td:last-child { padding-right: var(--pad-x); }
.admin-table th.sortable button {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    border: 0;
    background: none;
    padding: 0;
    margin: 0;
    cursor: pointer;
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
}
.admin-table th.sortable button:hover { color: var(--ink); }
.admin-table td {
    padding: var(--gap-xs) var(--gap-sm);
    height: 40px;
    border-bottom: 1px solid var(--panel-line);
    vertical-align: middle;
    white-space: nowrap;
}
.admin-table tbody tr:hover td { background: #f8fafc; }
.admin-table .em { max-width: 420px; }
.admin-table .em-row { display: flex; align-items: center; min-width: 0; }
.admin-table .em-text { font-weight: 650; overflow: hidden; text-overflow: ellipsis; }
.admin-table .verified-mark { color: var(--ok); display: inline-flex; margin-left: 6px; flex: none; }
.admin-table .dt { color: var(--ink-2); }
.admin-table .r { text-align: right; }
.admin-table td.num { font-variant-numeric: tabular-nums; }
.admin-table td.num.zero { color: var(--ink-3); }
.admin-table tr.off td { color: var(--ink-3); }
.admin-table tr.off .em-text { text-decoration: line-through; text-decoration-color: var(--panel-line-2); }

/* The actions keep one height whatever mix of buttons a row carries. */
.admin-actions { display: inline-flex; align-items: center; justify-content: flex-end; gap: var(--gap-xs); height: var(--ctl-h); }
.admin-actions .btn, .admin-actions .icon-btn { flex: none; }

.admin-pager {
    display: flex;
    align-items: center;
    gap: var(--gap-md);
    margin-top: var(--gap-md);
    font-size: 12px;
    color: var(--ink-2);
}

/* ---- the typed delete confirmation's body; the dialog chrome is the shared .modal ---- */
.admin-delete-facts {
    margin: var(--gap-sm) 0 var(--gap-md);
    padding-left: 18px;
    font-size: 12px;
    color: var(--ink-2);
    line-height: 1.6;
}
.admin-delete-facts li + li { margin-top: 2px; }
.admin-delete-label {
    display: block;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--ink-2);
    margin-bottom: var(--gap-xs);
}
.modal-body > .admin-delete-facts:first-child { margin-top: var(--gap-xs); }
