/*
 * The viewer's one stylesheet.
 *
 * Two halves, in this order: the parts — tokens, type, lists, tables, the panel
 * and inspector internals — and then the arrangement that places them. The
 * tokens that name a colour twice (--highlight, --good, --bad, --suspect) are
 * the ones the scene also draws with; those two must agree, and each says so
 * where it is declared.
 *
 * The look is the game's own, because the thing on screen is: navy-steel plates
 * with a corner cut off them, one FICSIT orange used only where something is
 * live or selected, condensed uppercase for labels, and figures set in Rajdhani
 * so that a column of them lines up and can be read across a desk.
 */
:root {
    --bg: #0b1016;
    --panel: #141e28;
    --panel-2: #1b2733;
    /* The face of a plate. Every pane, tile and tab body is this. */
    --plate: linear-gradient(180deg, #18242f, #141e28);
    --line: #26374a;
    --text: #e9f0f6;
    --muted: #8ba0b3;
    /* A third step down from --muted, for labels that name rather than say. */
    --faint: #5f7286;
    --accent: #ff9e3d;
    --accent-dim: #7a4a1c;
    --danger: #ff6b81;
    /* The corner taken off a plate, in one place so every plate takes the
       same one: panes, tiles, tabs and the load button all cut this far. */
    --cut: 10px;
    /* The reading column. Wide enough for the cost table's three columns
       and a 43-row outline, narrow enough to leave the scene 1174px. */
    --column: 360px;
    /* Two pixels of it under the command bar, and nowhere else. A whole band
       of hazard tape is a costume; a seam of it is a machine. */
    --hazard: repeating-linear-gradient(115deg, #f0a341 0 8px, #1b1408 8px 16px);
    /* Matches HIGHLIGHT in ui/viewer.ts: the sidebar mark and the mark in the
       scene are the same mark, and should read as one. */
    --highlight: #5ce1ff;
    --highlight-dim: #1d4655;
    /* Matches TONE_COLORS in ui/viewer.ts, for the same reason as --highlight:
       a port drawn green in the scene is listed green in the inspector. */
    --good: #46d17a;
    --bad: #ff4d4d;
    --suspect: #ffb020;
    /*
     * The scheme the browser paints its own controls in.
     *
     * Without this a dark page gets light grey scrollbars — the browser has no
     * other way to know. The thin bar and its colours are read by Firefox and
     * current Chrome; the ::-webkit rules below cover the rest.
     */
    color-scheme: dark;
    scrollbar-width: thin;
    scrollbar-color: #35495e transparent;
}

::-webkit-scrollbar {
    width: 9px;
    height: 9px;
}

::-webkit-scrollbar-track {
    background: rgba(0, 0, 0, 0.22);
}

/*
 * A thumb inset in its channel.
 *
 * `background-clip: padding-box` behind a transparent border is what insets it:
 * the 2px either side stay track, so the bar reads as a marker in a groove
 * rather than as a slab of chrome sitting on the page.
 */
::-webkit-scrollbar-thumb {
    border: 2px solid transparent;
    background: #35495e;
    background-clip: padding-box;
}

::-webkit-scrollbar-thumb:hover {
    background: #4a6480;
    background-clip: padding-box;
}

::-webkit-scrollbar-corner {
    background: transparent;
}

* {
    box-sizing: border-box;
}

html,
body {
    height: 100%;
    margin: 0;
}

body {
    background:
        radial-gradient(1200px 700px at 60% -10%, #16232f 0%, transparent 60%),
        var(--bg);
    color: var(--text);
    font: 400 14px/1.45 Barlow, system-ui, -apple-system, "Segoe UI", sans-serif;
    -webkit-font-smoothing: antialiased;
    overflow: hidden;
}

/*
 * ---- type ----
 *
 * Three faces, each doing one job, and every rule that assigns one is here so
 * that the answer to "why is this uppercase" is in a single place.
 *
 * Barlow Condensed, uppercase and widely spaced, for anything that names a
 * region or a value — it says "label" at a glance and it costs a third of the
 * width of the prose face. Rajdhani for every figure, tabular so that digits in
 * a column sit under each other. Barlow for everything that is a sentence.
 *
 * The faces come from Google Fonts, linked by index.html and roadmap.html. Each
 * stack falls back to the system sans, so the page loses its accent and keeps
 * its shape when the request fails.
 */
.pane-title,
.tab,
.stat-label,
.ins-title,
.samples-title,
.status,
.attribution,
.rail-link,
.bar-link,
.dropzone strong,
.group-name,
.toggle,
.bezel button {
    font-family: "Barlow Condensed", system-ui, sans-serif;
    letter-spacing: 0.16em;
    text-transform: uppercase;
}

.stat-value,
.identity-name,
.identity-meta,
.brand-name,
.library-meta,
.group-count,
.entry-count,
.samples-count,
.ins-name,
.ins-value,
.material-amount,
.materials-total {
    font-family: Rajdhani, system-ui, sans-serif;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

/*
 * The camera buttons and the view toggles, as two groups.
 *
 * Grouped rather than loose in the bezel so that one can go to each end of it:
 * where you are looking from on the left, what is drawn on the right. No gap
 * inside either — the strip is one piece of case, divided by rules, and a gap
 * would make each control a separate thing lying on it.
 */
.view-buttons,
.view-toggles {
    display: flex;
    align-items: stretch;
    gap: 0;
}

/*
 * The mark: a foundation block in isometric, three faces of it, drawn by
 * ui/chrome as inline SVG.
 *
 * Sized by whatever holds it — 26px in the rail, and it would be 15 in a command
 * bar if a page ever put it there. Nothing here says which face is which colour;
 * that is in the SVG, because the shape and its shading are one drawing.
 */
.brand-mark,
.rail-mark-art {
    display: block;
    width: 100%;
    height: auto;
}

.brand-mark {
    width: 15px;
    flex: none;
}

code {
    background: var(--panel-2);
    padding: 0 4px;
    border-radius: 3px;
    font-size: 0.92em;
}

/*
 * Opening a file is one control in the command bar rather than a dashed target
 * to drop onto: the whole window takes a drop (see App.bindDropzone), so a box
 * claiming that job exclusively would be lying about it.
 *
 * It is also the one filled control on the site, so that the thing you do first
 * is the thing that reads loudest. Chamfered like every other plate. Nothing
 * else is painted this way — a page of prose gets `.bar-link` instead, because
 * an outlined link cannot promise a reader that a file is about to load.
 */
.dropzone {
    display: flex;
    flex: none;
    flex-direction: row;
    align-items: center;
    gap: 9px;
    padding: 10px 16px;
    border: 0;
    background: linear-gradient(180deg, #ffab52, #e8801a);
    clip-path: polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
    color: #1c1206;
    text-align: left;
    cursor: pointer;
    transition: background 0.15s;
}

.dropzone:hover,
.dropzone.active {
    background: linear-gradient(180deg, #ffc178, #f28e22);
}

.dropzone strong {
    font-size: 12px;
    font-weight: 700;
}

.dropzone span {
    color: #6a4415;
    font-size: 11px;
}

/* The quiet twin: the way out of a page of prose. See ui/chrome `barLink`. */
.bar-link {
    margin-left: auto;
    padding: 9px 15px;
    border: 1px solid var(--line);
    color: var(--muted);
    text-decoration: none;
    white-space: nowrap;
}

.bar-link::after {
    content: " →";
    color: var(--faint);
}

.bar-link:hover {
    border-color: var(--accent);
    background: rgba(255, 158, 61, 0.07);
    color: var(--accent);
}

.bar-link:hover::after {
    color: var(--accent);
}

.samples {
    padding: 8px 10px 0;
}

.samples-summary {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 10px;
    background: var(--panel-2);
    cursor: pointer;
    list-style: none;
}

.samples-summary::-webkit-details-marker {
    display: none;
}

.samples-summary::before {
    content: "▸";
    color: var(--muted);
    font-size: 11px;
}

.samples-details[open] .samples-summary::before {
    content: "▾";
}

.samples-title {
    flex: 1;
    font-size: 11px;
}

.samples-count {
    color: var(--muted);
    font-variant-numeric: tabular-nums;
    font-size: 12px;
}

.samples-filter {
    width: 100%;
    margin-top: 8px;
    padding: 6px 8px;
    border: 1px solid var(--line);
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-size: 12px;
}

.samples-filter:focus {
    outline: none;
    border-color: var(--accent);
}

.samples-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
    max-height: 150px;
    margin-top: 6px;
    overflow-y: auto;
}

/* A sample in flight is disabled but not a failure, so it stays muted. */
.samples-list .library-item:disabled {
    color: var(--muted);
    cursor: progress;
}

/* The list scrolls, so the last row is often cut: draw where it ends. */
.samples-actions {
    display: flex;
    justify-content: flex-end;
    padding-top: 6px;
    border-top: 1px solid var(--line);
}

.samples-all {
    padding: 5px 11px;
    border: 1px solid var(--line);
    background: var(--panel-2);
    color: var(--muted);
    font: inherit;
    font-size: 12px;
    cursor: pointer;
}

.samples-all:hover:not(:disabled) {
    border-color: var(--accent);
    color: var(--accent);
}

.samples-all:disabled {
    opacity: 0.45;
    cursor: default;
}

.samples-note {
    margin: 8px 2px 0;
    color: var(--muted);
    font-size: 12px;
}

.library {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 8px 10px 10px;
    overflow-y: auto;
}

/*
 * A blueprint in the list. No box: a row, marked on its left edge when it is
 * the one being drawn — the same mark the outline uses for a selected part, so
 * "this is the current thing" reads the same way everywhere on the page.
 */
.library-item {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    padding: 8px 10px;
    border: 0;
    border-left: 2px solid transparent;
    background: none;
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.library-item:hover {
    background: var(--panel-2);
}

.library-item.active {
    border-left-color: var(--accent);
    background: linear-gradient(90deg, #2a1f0e, #1d2b38 70%);
    color: #ffce93;
}

.library-item:disabled {
    color: var(--danger);
    cursor: not-allowed;
}

.library-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.library-meta {
    flex: none;
    color: var(--muted);
    font-size: 12px;
}

.summary {
    padding: 0;
}

.summary:empty {
    display: none;
}

/*
 * A figure, as a plate tile rather than a row in a list of pairs.
 *
 * Six label/value rows are six things to read; six tiles are six things to
 * glance at, and the figure is what the glance lands on. The orange seam along
 * the top is what makes a tile a tile — see .summary for how they are laid out.
 */
.stat {
    display: block;
    position: relative;
    padding: 11px 13px 13px;
    border: 1px solid var(--line);
    background: var(--plate);
    clip-path: polygon(9px 0, 100% 0, 100% calc(100% - 9px), calc(100% - 9px) 100%, 0 100%, 0 9px);
}

.stat::before {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    left: 9px;
    height: 2px;
    background: var(--accent);
    opacity: 0.75;
}

.stat-label {
    display: block;
    color: var(--faint);
    font-size: 10px;
}

/*
 * 17px, and wrapping.
 *
 * Half of these values are sentences — "556 mesh · 0 proxy · 161 generated" — so
 * a tile sized for "769" would clip them. The figure still leads its tile at
 * nearly twice the label's size, which is what the treatment was for.
 */
.stat-value {
    display: block;
    margin-top: 6px;
    font-size: 17px;
    line-height: 1.2;
    text-align: left;
}

.description {
    margin: 10px 0 0;
    padding: 8px 10px;
    border-left: 2px solid var(--accent-dim);
    background: var(--panel-2);
    border-radius: 0 4px 4px 0;
    color: var(--muted);
    font-size: 12px;
    white-space: pre-wrap;
}

.warning {
    margin: 10px 0 0;
    color: var(--danger);
    font-size: 12px;
}

/*
 * The reading column's tabs. Angled on the leading edge, which is the shape the
 * game cuts everything on, and sized to their labels rather than split evenly —
 * a row of headings, not a segmented control.
 *
 * Five of them have to fit across 342px, which is what set two of the labels:
 * "Cost", not "Build cost", and the tightest letter-spacing on the page. It
 * wraps rather than clipping if a sixth is ever added, and the fifth appears
 * only while something is selected — so the wrap would move the panel down under
 * it, which is the reason the labels were cut instead.
 */
.tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
}

.tab {
    flex: none;
    padding: 9px 9px;
    border: 0;
    background: #16212c;
    clip-path: polygon(7px 0, 100% 0, calc(100% - 7px) 100%, 0 100%);
    color: var(--muted);
    font-size: 10.5px;
    letter-spacing: 0.06em;
    cursor: pointer;
}

.tab:hover {
    color: var(--text);
}

.tab.active {
    background: #22323f;
    box-shadow: inset 0 -2px 0 var(--accent);
    color: var(--accent);
}

.panels {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.panel {
    display: none;
    padding: 12px 14px 32px;
}

.panel.active {
    display: block;
}

.group {
    margin-bottom: 10px;
}

.group-header {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 10px;
    border-left: 3px solid var(--accent);
    background: #1c2a36;
    cursor: pointer;
}

.group-name {
    flex: 1;
    font-size: 11px;
    letter-spacing: 0.14em;
}

.group-count,
.entry-count {
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* The category's colour, cut as a diamond: the shape the page uses for a lamp. */
.chip {
    width: 10px;
    height: 10px;
    flex: none;
    clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
}

.entries {
    margin: 4px 0 0;
    padding: 0 0 0 22px;
    list-style: none;
}

.entries li {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    padding: 4px 8px;
    color: var(--muted);
    font-size: 13px;
    cursor: pointer;
}

.entries li:hover {
    background: #1b2836;
    color: var(--text);
}

.entries li.selected {
    background: var(--highlight-dim);
    color: var(--text);
    box-shadow: inset 2px 0 0 var(--highlight);
}

.entries li.unrecognised .entry-name::after {
    content: " ?";
    color: var(--danger);
}

.entry-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.materials {
    width: 100%;
    border-collapse: collapse;
}

.materials td {
    padding: 3px 6px 3px 0;
    vertical-align: middle;
}

.material-amount {
    text-align: right;
    white-space: nowrap;
    color: var(--accent);
}

/*
 * The name is the cell that gives way.
 *
 * `max-width: 0` with `width: 100%` is how a table cell is made to ellipse: the
 * figure and the stack note are sized by their content, and the name claims what
 * is left and clips. A fixed table layout was tried first and was worse — it
 * honoured the declared widths and overflowed the *figures*, which is the one
 * thing in the row you cannot lose. The share-of-peak bar that used to sit in a
 * fourth cell is gone with the drawer it had room in; see materials-panel.
 */
.material-name {
    width: 100%;
    max-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.materials-total {
    margin: 0 0 10px;
    color: var(--muted);
    font-size: 12px;
}

/*
 * The haulable count — "4 stacks + 12" — beside the raw amount. Muted and
 * smaller because it restates the number to its left rather than adding one, so
 * it should read as an annotation and never compete with the amount itself.
 */
.material-stacks {
    color: var(--muted);
    font-size: 11.5px;
    white-space: nowrap;
    padding-right: 8px;
}

/*
 * A machine that arrives with items already in it. Accented rather than
 * dangerous: it is a finding about the file, not a fault in it, and the panel
 * says which in words.
 */
.entry-count.preloaded {
    color: var(--accent);
}

.machine-buildings .entry-name {
    color: var(--text);
    font-size: 12px;
}

.recipes {
    margin-top: 16px;
    color: var(--muted);
    font-size: 12px;
}

.recipes summary {
    cursor: pointer;
}

.recipes ul {
    margin: 6px 0 0;
    padding-left: 18px;
}

/*
 * The stage, and why the canvas is taken out of flow.
 *
 * A canvas has an intrinsic size — its drawing buffer — and in flow that size
 * counts towards its parent's content height. The viewer sets the buffer from
 * the stage's own measured height, so in a box whose height is not definite the
 * two chase each other: the stage is momentarily taller, the buffer is set to
 * that, the buffer then holds the box open, and the stage ends up past where it
 * belongs, painting over what is under it. Measured, not guessed — the stage
 * read 890px tall inside a 674px row.
 *
 * Out of flow it cannot contribute anything, and `flex: 1; min-height: 0`
 * inside .viewer keeps the box from growing for any other reason.
 */
.stage {
    position: relative;
    flex: 1;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}

#viewport {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
}

/*
 * The bezel: the viewpoint buttons and the layer switches, in a strip across the
 * foot of the viewer.
 *
 * It used to be a pill floating in the middle of the scene. An instrument's
 * switches are on its case, not on the thing it is showing you — and the strip
 * is a sibling of the stage rather than a child of it (see layout.ts `viewer`),
 * so the render stops where the controls start instead of continuing behind
 * them. Flush to all three edges: a strip with a margin reads as a thing lying
 * on the viewer, and this is meant to be part of it.
 */
.bezel {
    display: flex;
    justify-content: space-between;
    align-items: stretch;
    flex: none;
    height: 46px;
    border-top: 1px solid var(--line);
    background: linear-gradient(180deg, #182430, #0f171f);
}

.bezel button {
    height: 100%;
    padding: 0 22px;
    border: 0;
    border-right: 1px solid var(--line);
    background: none;
    color: var(--muted);
    font-size: 11px;
    letter-spacing: 0.18em;
    cursor: pointer;
}

.bezel button:hover {
    background: linear-gradient(180deg, #ffab52, #e8801a);
    color: #1c1206;
}

.toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 100%;
    padding: 0 18px;
    border-left: 1px solid var(--line);
    color: var(--muted);
    font-size: 11px;
    letter-spacing: 0.14em;
    white-space: nowrap;
    cursor: pointer;
}

.toggle:has(input:checked) {
    background: rgba(255, 158, 61, 0.07);
    color: var(--text);
}

/*
 * The checkbox as a lamp.
 *
 * A tick box is a form control and this is not a form: it is a switch on an
 * instrument, and what a switch shows is whether the thing is lit. The diamond
 * is the same one the outline uses for a category's colour.
 */
.toggle input {
    appearance: none;
    width: 9px;
    height: 9px;
    margin: 0;
    border: 0;
    background: #2b3d4f;
    clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
    cursor: pointer;
}

.toggle input:checked {
    background: var(--accent);
    box-shadow: 0 0 8px rgba(255, 158, 61, 0.6);
}

/*
 * The hover readout: the one thing still drawn over the scene. It names what is
 * under the pointer, so it has to be where the pointer is, and it is gone the
 * moment the pointer leaves.
 */
.pick {
    position: absolute;
    top: 12px;
    right: 12px;
    padding: 6px 11px;
    border: 1px solid var(--line);
    background: rgba(15, 26, 36, 0.94);
    font-family: Rajdhani, system-ui, sans-serif;
    font-size: 13px;
    font-weight: 600;
    opacity: 0;
    transition: opacity 0.12s;
    pointer-events: none;
}

/* Pinned by a click, so it stays while the pointer goes to fetch it. */
.pick.pinned {
    border-color: var(--accent);
    color: var(--accent);
}

/*
 * The status line, on one line: it is glanced at, so it ellipses rather than
 * wrapping into a bar 26px tall. The attribution beside it does the same.
 */
.status {
    min-width: 0;
    overflow: hidden;
    color: var(--muted);
    font-size: 11px;
    letter-spacing: 0.1em;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.status[data-tone="error"] {
    color: var(--danger);
}

.stage[data-state="loading"]::after {
    content: "";
    position: absolute;
    inset: 0;
    background: rgba(13, 17, 23, 0.5);
}

/*
 * Findings: a fault the viewer can be sent to. A button rather than text
 * because it is the one thing in the sidebar that moves the camera, and because
 * the list is useless if the ids in it have to be hunted for by hand.
 */
.findings {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 6px;
}

.finding {
    padding: 5px 8px;
    border: 1px solid var(--danger);
    border-radius: 4px;
    background: transparent;
    color: var(--danger);
    font: inherit;
    font-size: 11px;
    text-align: left;
    cursor: pointer;
}

.finding:hover {
    background: color-mix(in srgb, var(--danger) 18%, transparent);
}

.finding-rest {
    color: var(--muted);
    font-size: 11px;
}


/*
 * The selection inspector — see ui/panels/inspector-panel.
 *
 * Shared by every layout that has a pane to put it in, which is why it lives
 * here rather than in one layout's stylesheet. The port rows carry the scene's
 * own tones: green is joined, red is open, and both are the colours the marks in
 * the viewport are drawn in.
 */
.inspector {
    flex: 1;
    min-height: 0;
    padding: 10px 12px 24px;
    overflow-y: auto;
}

.ins-head {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding-bottom: 9px;
}

.ins-head .chip {
    margin-top: 4px;
}

.ins-headtext {
    flex: 1;
    min-width: 0;
}

.ins-name {
    font-weight: 600;
    font-size: 13.5px;
}

.ins-sub {
    color: var(--muted);
    font-size: 11.5px;
}

.ins-release {
    flex: none;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 4px;
    background: none;
    color: var(--muted);
    font: inherit;
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
}

.ins-release:hover {
    border-color: var(--line);
    color: var(--text);
}

.ins-id {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 10px;
}

.ins-idtext {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    padding: 3px 6px;
    border-radius: 4px;
    background: var(--panel-2);
    color: var(--muted);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10.5px;
    letter-spacing: -0.01em;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * The copy control. Square, quiet, and against the id it copies — see
 * InspectorPanel.identity. `currentColor` on the icon is what makes one rule
 * cover its states: the glyph follows the button's own colour on hover.
 */
.ins-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    flex: none;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 5px;
    background: var(--panel-2);
    color: var(--muted);
    cursor: pointer;
}

.ins-icon:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.ins-button {
    padding: 4px 9px;
    border: 1px solid var(--line);
    border-radius: 5px;
    background: var(--panel-2);
    color: var(--text);
    font: inherit;
    font-size: 11.5px;
    cursor: pointer;
}

.ins-button:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.ins-button.primary {
    border-color: var(--accent-dim);
    color: var(--accent);
}

.ins-section {
    padding: 9px 0;
    border-top: 1px solid var(--line);
}

.ins-title {
    margin: 0 0 6px;
    color: var(--muted);
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.ins-grid {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.ins-row {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    font-size: 12px;
}

.ins-label {
    color: var(--muted);
}

.ins-value {
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.ins-value.strong {
    font-weight: 600;
}

/* A machine that arrives loaded is a finding about the file, not a fault. */
.ins-value.loaded {
    color: var(--accent);
}

.ins-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    margin-right: 6px;
    border-radius: 2px;
    vertical-align: middle;
}

.ins-note {
    margin-top: 7px;
    color: var(--muted);
    font-size: 11px;
    line-height: 1.4;
}

.ins-warning {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 7px;
    color: var(--danger);
    font-size: 11px;
    line-height: 1.4;
}

.ins-ports {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.ins-port {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 3px 7px;
    border-radius: 4px;
    background: var(--panel-2);
    font-size: 12px;
}

.ins-dot {
    width: 7px;
    height: 7px;
    flex: none;
    border-radius: 50%;
    background: var(--muted);
}

.ins-port.joined .ins-dot {
    background: var(--good);
}

.ins-port.open .ins-dot {
    background: var(--bad);
}

.ins-port.broken .ins-dot {
    background: var(--suspect);
}

.ins-portname {
    width: 84px;
    flex: none;
    overflow: hidden;
    color: var(--muted);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10.5px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ins-portstate {
    flex: 1;
    color: var(--muted);
}

/*
 * The partner is a button because the next move is to go and look at it. It
 * ellipses rather than setting the column's width: a building's name can be
 * longer than the pane it is named in.
 */
.ins-partner {
    flex: 1;
    padding: 0;
    border: 0;
    background: none;
    color: var(--highlight);
    font: inherit;
    font-size: 12px;
    text-align: left;
    cursor: pointer;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ins-partner:hover {
    text-decoration: underline;
}

/*
 * The blueprint's name in the chrome, where a layout has room for it.
 */
/*
 * The blueprint's name in the command bar, separated from the app's own name the
 * way a document title is: this is what you are looking at, not what you are
 * looking at it with.
 */
.identity {
    display: flex;
    flex: 1;
    align-items: baseline;
    gap: 10px;
    min-width: 0;
    padding-left: 14px;
    border-left: 1px solid var(--line);
}

.identity-name {
    overflow: hidden;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.identity-meta {
    flex: none;
    color: var(--muted);
    font-size: 11.5px;
    font-variant-numeric: tabular-nums;
}

.identity-empty {
    color: var(--muted);
    font-size: 12px;
}

/*
 * The Roadmap link, styled as chrome rather than as prose: an underlined blue
 * link in a command bar reads as content, and this is a place to go.
 */
.top-link {
    flex: none;
    padding: 5px 9px;
    border: 1px solid transparent;
    border-radius: 6px;
    color: var(--muted);
    font-size: 12px;
    text-decoration: none;
    white-space: nowrap;
}

.top-link:hover {
    border-color: var(--line);
    background: var(--panel-2);
    color: var(--text);
}

/*
 * ============================================================================
 * The arrangement: rail, command bar, reading column, viewer, status bar.
 *
 * Everything above styles the parts — tokens, type, lists, tables, the
 * inspector's rows. Everything below places them.
 *
 * The premise is that a blueprint is interrogated rather than looked at, and
 * that the scene is the thing being interrogated: it is never covered and never
 * resized. Everything that is read lives in one column — the blueprint list,
 * then one of Outline / Overview / Build cost / Machines, then whatever is
 * selected — and the viewer takes all the room that is left.
 *
 * This replaced a command bar, three panes and a bottom drawer. The two figures
 * that decided it, at 1600 × 1000: the scene went from 1304 × 674 to 1174 × 902
 * — 0.65 to 1.06 megapixels — and selecting a building stopped taking 344px of
 * width off it, which used to re-frame the model under the pointer at the moment
 * of clicking it.
 *
 * What it costs is stated where it is paid: the outline is not on screen while
 * the cost is, because they are two of five tabs on one strip (see .tab-head).
 * ============================================================================
 */

.app {
    display: grid;
    grid-template-columns: 56px var(--column) minmax(0, 1fr);
    grid-template-rows: 52px minmax(0, 1fr) 26px;
    grid-template-areas:
        "rail top    top"
        "rail column viewer"
        "rail status status";
    height: 100%;
    min-height: 0;
}

/* ---- the rail ---- */

/*
 * The rail: the mark, and the way out.
 *
 * Flush to three edges, and carrying two things, which is all it is for. The
 * panels' buttons are their own tabs at the top of the column, so a strip of
 * duplicates for them would be two controls for one job.
 */
.rail {
    grid-area: rail;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 9px 0;
    border-right: 1px solid var(--line);
    background: linear-gradient(180deg, #131d27, #0d151d);
}

.rail-mark {
    display: block;
    width: 26px;
    padding: 4px 0;
}

.rail-gap {
    flex: 1;
}

/*
 * The rail's links, set on their side: "Roadmap" is 52px of text in a 56px rail,
 * and an icon standing in for a word is a rebus. Vertical labels are how a strip
 * this narrow carries one at all.
 *
 * The page you are on is marked rather than dropped from the list, so the rail
 * is the same shape on every page of the site.
 */
.rail-link {
    writing-mode: vertical-rl;
    display: grid;
    place-items: center;
    width: 40px;
    padding: 12px 0;
    border-left: 2px solid transparent;
    color: var(--faint);
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.2em;
    text-decoration: none;
}

.rail-link:hover {
    border-left-color: var(--accent);
    background: #1b2937;
    color: var(--accent);
}

.rail-link.current {
    border-left-color: var(--accent);
    color: var(--text);
}

/* ---- the command bar ---- */

.top-bar {
    grid-area: top;
    position: relative;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 0 16px;
    background: linear-gradient(180deg, #182430, #111a23);
}

/* The hazard seam, and the only place on the page that has one. */
.top-bar::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 2px;
    background: var(--hazard);
    opacity: 0.55;
}

.brand {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: none;
}

.brand-name {
    font-size: 19px;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

/* ---- the reading column ---- */

/*
 * Three regions, in the order they are read: what is available, what the open
 * blueprint is made of, and what you clicked. The middle one takes the room the
 * other two leave, because it is the one holding a list 43 rows long.
 */
.column {
    grid-area: column;
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-height: 0;
    padding: 10px;
}

.pane {
    display: flex;
    flex-direction: column;
    min-height: 0;
    border: 1px solid var(--line);
    background: var(--plate);
    clip-path: polygon(
        var(--cut) 0, 100% 0,
        100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%,
        0 100%, 0 var(--cut));
}

.pane-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 11px 12px 9px;
    border-bottom: 1px solid var(--line);
}

.pane-title {
    color: var(--muted);
    font-size: 10.5px;
}

/* The rule after a title, which is what makes a pane read as an instrument. */
.pane-head::after {
    content: "";
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, var(--line), transparent);
}

/*
 * The blueprint list is capped in pixels rather than as a share of the column:
 * what it holds is a summary row and a short list of what is loaded, and past
 * 210px it would be taking height off the tabs for empty plate.
 */
.sources {
    flex: none;
    max-height: 210px;
}

/*
 * Open, the sample list needs more than the resting cap.
 *
 * Closed, this region is a summary row and a short list of what is loaded, and
 * 210px is generous. Opened, it grows a filter, a list and a "Load all" button —
 * 62px more than the cap, and the button was the part that fell off the bottom,
 * which is the one thing in there you cannot reach any other way. So the pane
 * grows while it is open, and the sample list scrolls inside whatever it gets.
 *
 * `:has()` rather than a class the app toggles: whether the list is open is the
 * <details> element's own business, and nothing else on the page needs to know.
 */
.sources:has(.samples-details[open]) {
    max-height: 340px;
}

.sources .samples {
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
}

.sources .library {
    flex: none;
    max-height: 96px;
    overflow-y: auto;
    overflow-x: hidden;
}

/* ---- the four tabs, and their bodies ---- */

/*
 * The tabs take the room the other two regions leave, but never less than this.
 *
 * Without a floor the inspector's cap wins on a short window — at 1366 × 768 the
 * outline was down to 123px, four rows — because a flex item shrinks from its
 * content and this one's content is a list that can shrink to nothing. With the
 * floor the squeeze lands on the inspector instead, which is the pane that
 * already scrolls and whose first three rows are the ones being read.
 */
.workbench {
    flex: 1 1 auto;
    min-height: 216px;
}

.tab-head {
    display: flex;
    padding: 8px 8px 0;
    border-bottom: 1px solid var(--line);
}

.tabs {
    flex: 1;
}

.panels {
    flex: 1;
    min-height: 0;
    overflow: hidden;
}

/*
 * A tab body, and why sideways scroll is off.
 *
 * The column is 360px; its border, its padding and a scrollbar leave about 320
 * of content, which a long building name or a wide table will happily exceed.
 * Everything in here has somewhere to give — a name ellipses, a figure stays —
 * so the answer to overflow is never a horizontal scrollbar. It was one, once:
 * the Machines grid inherited a 320px minimum from the drawer this panel used to
 * live in, and on a machine whose scrollbar is 17px wide that overflowed by two
 * pixels and grew a bar along the bottom of the column.
 */
.panel {
    display: none;
    height: 100%;
    padding: 10px;
    overflow-y: auto;
    overflow-x: hidden;
}

.panel.active {
    display: block;
}

/* The outline keeps its own scroller: its head is what the tab strip says. */
#outline.panel.active {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
}

.outline-list {
    flex: 1;
    min-height: 0;
    padding: 8px 8px 20px;
    overflow-y: auto;
    overflow-x: hidden;
}

/*
 * The figures, two tiles across.
 *
 * `auto-fit` down to 138px is what makes it two in this column, and 138 is the
 * floor at which a tile still holds "556 mesh · 0 proxy · 161 generated" on
 * three lines. The prose that shares the panel — the blueprint's own
 * description, and any near-miss findings — spans the row instead: a sentence in
 * a 138px column is a column of syllables.
 */
#overview .summary {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(138px, 1fr));
    gap: 8px;
    align-content: start;
}

#overview .description,
#overview .warning,
#overview .findings {
    grid-column: 1 / -1;
}

/* ---- the inspector ---- */

/*
 * The inspector is the fifth tab, so this section is two lines long: it is a
 * panel like the others and it inherits their height, their padding and their
 * scroller.
 *
 * It was a pane under the tabs before that, capped at 46vh, and it scrolled from
 * the first building selected — a conveyor lists its ports, its recipe and its
 * paint, and that is more than half a column. As a tab it has the whole one, and
 * the tabs it shares the strip with keep theirs.
 */
#inspector .ins-section:first-child {
    margin-top: 0;
}

/* ---- the viewer ---- */

/*
 * The canvas and the bezel, stacked.
 *
 * The canvas gets a box of its own so that the viewer's ResizeObserver measures
 * the render area and nothing else — see layout.ts `viewer`, and .stage for what
 * a canvas does to a box it shares. That is what lets the controls sit beside
 * the render rather than on top of it.
 */
.viewer {
    grid-area: viewer;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    margin: 10px 10px 10px 0;
    border: 1px solid var(--line);
    background: #080c11;
    clip-path: polygon(14px 0, 100% 0, 100% calc(100% - 14px), calc(100% - 14px) 100%, 0 100%, 0 14px);
}

/*
 * Bracket corners on the render, painted as eight gradient slices in one
 * pseudo-element — a pseudo-element gives you two, and this wants four corners.
 * They frame the scene only: the bezel is outside this box.
 */
.stage::before {
    --bracket: linear-gradient(#35506a, #35506a);

    content: "";
    position: absolute;
    inset: 8px;
    z-index: 2;
    background:
        var(--bracket) 0 0 / 16px 2px no-repeat,
        var(--bracket) 0 0 / 2px 16px no-repeat,
        var(--bracket) 100% 0 / 16px 2px no-repeat,
        var(--bracket) 100% 0 / 2px 16px no-repeat,
        var(--bracket) 0 100% / 16px 2px no-repeat,
        var(--bracket) 0 100% / 2px 16px no-repeat,
        var(--bracket) 100% 100% / 16px 2px no-repeat,
        var(--bracket) 100% 100% / 2px 16px no-repeat;
    pointer-events: none;
}

/* ---- the status bar ---- */

.status-bar {
    grid-area: status;
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 0 12px;
    border-top: 1px solid var(--line);
    background: #0d151d;
}

/*
 * Coffee Stain's copyright notice, pushed to the right of the status line.
 *
 * `min-width: 0` is what lets it shrink: a flex item defaults to min-content, so
 * without it the notice refuses to go below its longest word and pushes the
 * status line off the bar instead of ellipsing itself. Both items give way the
 * same way, because both are read once and glanced at after that.
 */
.attribution {
    margin-left: auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    color: var(--faint);
    font-size: 11px;
    letter-spacing: 0.1em;
}


/*
 * ============================================================================
 * The roadmap page.
 *
 * roadmap.html is prose rather than an app, and the two things it needs that the
 * viewer forbids are scrolling and a readable measure. Everything is scoped to
 * body.doc so a rule here can never reach the viewer, and it borrows the top
 * bar, the brand and the status bar verbatim — the point of it living in this
 * stylesheet is that the two pages read as one site without a second set of
 * tokens to keep in step.
 * ============================================================================
 */

/*
 * The same page as the viewer, with prose where the column and the scene are.
 *
 * The chrome is not restyled here and it is not re-written either — doc.ts builds
 * it from ui/chrome, the same functions ui/layout calls — so a doc page gets the
 * rail, the mark, the command bar and the attribution by having them, not by
 * imitating them. What is left for this section is the one structural difference:
 * a document scrolls, and the viewer does not.
 *
 * The grid is the app's, one column short. The rail runs the full height, the
 * command bar stays put at the top of the reading area, and the prose scrolls
 * under it.
 */
body.doc {
    display: grid;
    grid-template-columns: 56px minmax(0, 1fr);
    grid-template-rows: 52px minmax(0, 1fr) auto;
    grid-template-areas:
        "rail top"
        "rail main"
        "rail status";
    height: 100%;
    overflow: hidden;
}

body.doc .doc-body {
    grid-area: main;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
}

body.doc .status-bar {
    grid-area: status;
    height: auto;
    padding: 8px 12px;
}

/*
 * The sheet.
 *
 * 82ch rather than the 68 a plain measure wants, because a goal's steps are
 * indented twice — the plate's padding and the step's own gutter — and 68 left
 * them reading as a column of four words. The dot grid is paper: faint enough to
 * be a texture and not a pattern, and it is what stops a page of hairline rules
 * from looking like a form.
 */
.doc-body {
    max-width: 82ch;
    margin: 0 auto;
    padding: 40px 28px 80px;
    background-image: radial-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px);
    background-size: 22px 22px;
    background-position: 12px 12px;
}

/*
 * Links in the prose, and only in the prose.
 *
 * This was `body.doc a`, which is one class and one type — enough to outrank
 * `.bar-action` and `.rail-link` and repaint the chrome's links orange on orange.
 * The chrome is the same on every page and is styled with the app; what is
 * page-specific here is the writing.
 */
.doc-body a {
    color: var(--accent);
    text-decoration-color: var(--accent-dim);
    text-underline-offset: 2px;
}

.doc-body a:hover {
    text-decoration-color: var(--accent);
}

.doc-body h1 {
    margin: 0 0 12px;
    font-family: Rajdhani, system-ui, sans-serif;
    font-size: 54px;
    font-weight: 700;
    letter-spacing: 0.02em;
    line-height: 1;
    text-transform: uppercase;
}

/*
 * A section heading, numbered like a clause in a specification.
 *
 * The number is drawn by a counter rather than typed into the markup, so
 * deleting a section renumbers the rest — which is the point: this page is
 * edited by cutting goals out of it as they ship.
 */
.doc-body h2 {
    display: flex;
    align-items: baseline;
    gap: 14px;
    margin: 0 0 10px;
    font-family: Rajdhani, system-ui, sans-serif;
    font-size: 15px;
    font-weight: 700;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    color: var(--text);
}

.doc-body h2::before {
    content: "§ " counter(clause, decimal-leading-zero);
    color: var(--accent);
    font-family: "JetBrains Mono", ui-monospace, monospace;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.08em;
}

.doc-body h2::after {
    content: "";
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, var(--line), transparent);
}

.doc-body h3 {
    margin: 0;
    font-family: Rajdhani, system-ui, sans-serif;
    font-size: 24px;
    font-weight: 600;
    letter-spacing: 0.01em;
}

.doc-body p {
    margin: 0 0 12px;
}

.doc-body .doc-lede {
    max-width: 58ch;
    margin: 14px 0 0;
    color: var(--muted);
    font-size: 17px;
    font-weight: 300;
    line-height: 1.7;
}

.doc-section {
    margin-top: 52px;
    counter-increment: clause;
}

/* The first section starts the count; every one after it carries on. */
#next {
    counter-reset: clause;
}

.doc-note {
    color: var(--muted);
    font-size: 12.5px;
}

/*
 * ---- the roadmap ----
 *
 * The page reads as a specification: a title block, clause-numbered sections,
 * and each goal a plate with its index hanging in the margin. Nothing moves and
 * nothing glows; what makes it premium is the type and the rules between things.
 */
.doc-head {
    position: relative;
    margin-bottom: 44px;
    padding-bottom: 22px;
    border-bottom: 1px solid var(--line);
}

/* The tick under the title block: 88px of accent on top of the hairline. */
.doc-head::after {
    content: "";
    position: absolute;
    bottom: -1px;
    left: 0;
    width: 88px;
    height: 3px;
    background: var(--accent);
}

.doc-eyebrow {
    margin: 0 0 6px;
    color: var(--faint);
    font-family: "Barlow Condensed", system-ui, sans-serif;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.34em;
    text-transform: uppercase;
}

/*
 * The four figures under the lede: what this version is, in numbers, because a
 * roadmap that opens with prose alone makes a reader hunt for the state of play.
 */
.doc-figures {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 0;
    margin: 26px 0 0;
}

.doc-figures > div {
    padding: 0 18px;
    border-left: 1px solid var(--line);
}

.doc-figures > div:first-child {
    padding-left: 0;
    border-left: 0;
}

.doc-figures dt {
    color: var(--faint);
    font-family: "Barlow Condensed", system-ui, sans-serif;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.2em;
    text-transform: uppercase;
}

.doc-figures dd {
    margin: 5px 0 0;
    font-family: Rajdhani, system-ui, sans-serif;
    font-size: 27px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.doc-section-lede {
    max-width: 62ch;
    margin-bottom: 24px;
    color: var(--muted);
    font-size: 15px;
    font-weight: 300;
}

/*
 * A goal, with its index hanging in the margin.
 *
 * The index is a child of the head in the markup — it belongs to the title it
 * numbers — and is pulled out of the text column by position rather than given a
 * grid track, so the markup stays a heading with a number in front of it.
 *
 * 104px of left padding is what the index needs at 40px plus its rule. It is not
 * decoration: the goals are ordered, 02 waits on 01, and the column of numbers
 * down the left is the order of work.
 */
.plan-item {
    position: relative;
    margin: 0 0 18px;
    padding: 20px 24px 22px 104px;
    border: 1px solid var(--line);
    background: linear-gradient(180deg, rgba(26, 39, 53, 0.92), rgba(19, 29, 39, 0.92));
}

.plan-head {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 8px;
}

.plan-index {
    position: absolute;
    top: 22px;
    left: 26px;
    padding-top: 4px;
    border-top: 2px solid var(--accent);
    font-family: Rajdhani, system-ui, sans-serif;
    font-size: 40px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    color: var(--accent);
}

.plan-head h3 {
    margin: 0;
    flex: 1;
}

.plan-summary {
    margin: 0 0 12px;
    max-width: 60ch;
    color: var(--muted);
    font-size: 15.5px;
    font-weight: 300;
    line-height: 1.7;
}

/*
 * The steps, numbered by the browser and marked by us.
 *
 * `list-style: none` with a counter rather than a marker, because a step's number
 * has to sit in the gutter beside a paragraph that wraps — a marker would put it
 * on the first line and let the rest hang under it.
 */
.plan-steps {
    margin: 0;
    padding: 0;
    list-style: none;
    counter-reset: step;
}

.plan-steps li {
    position: relative;
    counter-increment: step;
    padding: 11px 0 11px 34px;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
    font-weight: 300;
    line-height: 1.7;
}

.plan-steps li::before {
    content: counter(step, decimal-leading-zero);
    position: absolute;
    top: 13px;
    left: 0;
    color: var(--accent);
    opacity: 0.75;
    font-family: "JetBrains Mono", ui-monospace, monospace;
    font-size: 11px;
}

.plan-item strong,
.doc-gaps strong {
    color: var(--text);
    font-weight: 600;
}

/*
 * Where an item stands, in one word.
 *
 * `doc-chip` rather than `chip`, which this stylesheet already uses for the 11px
 * category swatch in the outline and the inspector. The colours are the viewer's
 * own state colours, so "next" reads as the same kind of good as a joined port.
 */
.doc-chip {
    flex: none;
    margin-left: auto;
    padding: 2px 8px;
    border: 1px solid;
    font-family: "Barlow Condensed", system-ui, sans-serif;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    white-space: nowrap;
}

.doc-chip-next {
    border-color: var(--good);
    color: var(--good);
}

.doc-chip-planned {
    border-color: var(--line);
    color: var(--muted);
}

.doc-chip-part {
    border-color: var(--suspect);
    color: var(--suspect);
}

.doc-gaps,
.doc-release ul {
    margin: 0;
    padding-left: 20px;
    color: var(--muted);
    font-size: 13.5px;
}

.doc-gaps li,
.doc-release li {
    padding: 8px 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    font-weight: 300;
    line-height: 1.7;
}

.doc-gaps li::marker,
.doc-release li::marker {
    color: var(--accent-dim);
}

/* The release, as the one plate on the page with a spine down its left. */
.doc-release {
    padding: 20px 22px 8px;
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    background: rgba(255, 255, 255, 0.015);
}

.doc-release h3 {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 4px;
    font-size: 30px;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}

.doc-date {
    color: var(--muted);
    font-size: 12px;
    font-weight: 400;
}

/* The measure is a maximum, not a target: under 720 the padding is what has to
   give, so the card edges do not sit against the window. */
@media (max-width: 720px) {
    .doc-body {
        padding: 22px 14px 44px;
    }

    body.doc .attribution {
        white-space: normal;
    }
}

/*
 * Sharing a blueprint: the offer at the foot of the Overview tab, and the dialog
 * it opens.
 *
 * The offer is outlined rather than filled, deliberately. The command bar's load
 * control is the one filled thing on the site because loading a file is what a
 * visitor came to do; sending one somewhere permanent is not, and painting it the
 * same way would put the same weight behind the irreversible act as behind the
 * reversible one. Chamfered like every other plate — see --cut.
 */
.share-offer {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 14px;
    padding-top: 12px;
    border-top: 1px solid var(--line);
}

.share-open {
    padding: 9px 14px;
    border: 1px solid var(--accent-dim);
    background: var(--panel-2);
    clip-path: polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
    color: var(--accent);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.02em;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}

.share-open:hover {
    border-color: var(--accent);
    background: rgba(255, 158, 61, 0.09);
}

.share-blurb,
.share-thanks {
    margin: 0;
    color: var(--muted);
    font-size: 11px;
    line-height: 1.5;
}

.share-thanks {
    color: var(--good);
}

/*
 * The dialog.
 *
 * ::backdrop rather than a fixed overlay element: the browser draws it behind a
 * modal <dialog> for free, and it is the one thing that cannot be got wrong —
 * an overlay div has a z-index to lose an argument with.
 */
.share {
    width: min(460px, calc(100vw - 32px));
    padding: 0;
    border: 1px solid var(--line);
    background: var(--plate);
    color: var(--text);
    clip-path: polygon(var(--cut) 0, 100% 0, 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, 0 100%, 0 var(--cut));
}

.share::backdrop {
    background: rgba(6, 10, 14, 0.72);
}

.share-form {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 20px 22px 18px;
}

.share-title {
    margin: 0;
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

.share-subject {
    margin: -6px 0 0;
    color: var(--muted);
    font-size: 12px;
}

.share-field {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.share-label {
    color: var(--faint);
    font-size: 11px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.share-hint {
    color: var(--faint);
    font-size: 11px;
}

.share-field input,
.share-field textarea {
    padding: 8px 10px;
    border: 1px solid var(--line);
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-size: 12px;
    resize: vertical;
}

.share-field input:focus,
.share-field textarea:focus {
    border-color: var(--accent);
    outline: none;
}

/*
 * The consent row is the one part of this dialog that is not allowed to be
 * quiet: it is the difference between a collection that may be redistributed and
 * one that may not. So it gets the panel's own background and a full-strength
 * text colour, while the fields above it are labelled in --faint.
 */
.share-consent {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-left: 2px solid var(--accent);
    background: var(--panel-2);
    cursor: pointer;
}

.share-consent input {
    flex: none;
    margin-top: 1px;
    accent-color: var(--accent);
}

.share-consent-text {
    font-size: 12px;
    line-height: 1.5;
}

/* Empty until Turnstile renders into it, and it must not reserve space when the
   deployment has no bot check configured. */
.share-challenge:empty {
    display: none;
}

.share-status {
    margin: 0;
    font-size: 12px;
}

.share-status[data-tone="error"] {
    color: var(--danger);
}

.share-status[data-tone="good"] {
    color: var(--good);
}

.share-status:empty {
    display: none;
}

.share-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
}

.share-cancel,
.share-send {
    padding: 9px 16px;
    border: 1px solid var(--line);
    background: var(--panel-2);
    color: var(--muted);
    font-size: 12px;
    font-weight: 700;
    cursor: pointer;
}

.share-send {
    border-color: var(--accent-dim);
    color: var(--accent);
}

.share-cancel:hover:not(:disabled),
.share-send:hover:not(:disabled) {
    border-color: var(--accent);
    background: rgba(255, 158, 61, 0.09);
    color: var(--accent);
}

.share-cancel:disabled,
.share-send:disabled {
    color: var(--faint);
    cursor: not-allowed;
    opacity: 0.55;
}

/*
 * The credits page's contributor list.
 *
 * A stack of rows rather than a table, because the three things a row carries do
 * not want the same alignment: a nickname is short and wants to lead, a count is
 * a number and wants tabular figures, and the blueprint names are a sentence that
 * has to be free to wrap. A table would have forced the third into a column the
 * width of the widest name on the page.
 *
 * Deliberately not numbered and not ranked. The order is most-given-first because
 * that is the fairest order for a list of thanks, but nothing here draws a
 * position — no index, no bar, no medal. See the note at the top of credits.html.
 */
/*
 * Separators drawn on the rows, not as a gap over a coloured container.
 *
 * The container-background trick was the first attempt and it leaks: the box was
 * 12px taller than its children — a <p> in here inherits a margin from the doc
 * page's prose rules, which outrank a bare .credits-note — and every one of those
 * pixels showed as a stray band of --line under the last row. Borders on the rows
 * cannot do that, whatever the children's margins turn out to be.
 */
.credits {
    display: flex;
    flex-direction: column;
    margin-top: 18px;
    border: 1px solid var(--line);
    background: var(--panel);
}

.credits > * + * {
    border-top: 1px solid var(--line);
}

.credit {
    display: grid;
    grid-template-columns: minmax(120px, auto) auto;
    gap: 2px 14px;
    padding: 12px 14px;
}

.credit-name {
    font-family: Rajdhani, system-ui, sans-serif;
    font-size: 17px;
    font-weight: 600;
    letter-spacing: 0.01em;
}

.credit-count {
    color: var(--faint);
    font-family: "Barlow Condensed", system-ui, sans-serif;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-align: right;
    text-transform: uppercase;
    white-space: nowrap;
}

/* Spans both columns: the names are the interesting part and should have the
   full measure to wrap into. */
.credit-list {
    grid-column: 1 / -1;
    color: var(--muted);
    font-size: 12px;
    line-height: 1.6;
}

/* Scoped through .credits so it outranks the doc pages' paragraph margins —
   see the note on .credits above for what that margin cost the first time. */
.credits .credits-note {
    margin: 0;
    padding: 12px 14px;
    color: var(--muted);
    font-size: 12px;
    line-height: 1.6;
}

.credits .credits-note[data-tone="error"] {
    color: var(--danger);
}

/*
 * The share link: the thing a contributor gets back.
 *
 * A read-only input rather than text, because the one action anybody takes on it
 * is selecting all of it — an input can do that on focus, and a <span> cannot. It
 * keeps the field/button pairing of the rest of the site's controls.
 */
.share-link {
    display: flex;
    gap: 0;
    margin: 0;
}

.share-link input {
    flex: 1;
    min-width: 0;
    padding: 7px 9px;
    border: 1px solid var(--line);
    border-right: 0;
    background: var(--bg);
    color: var(--accent);
    font-family: "JetBrains Mono", ui-monospace, monospace;
    font-size: 11px;
}

.share-link input:focus {
    border-color: var(--accent);
    outline: none;
}

.share-copy {
    display: flex;
    flex: none;
    align-items: center;
    padding: 0 10px;
    border: 1px solid var(--line);
    background: var(--panel-2);
    color: var(--muted);
    cursor: pointer;
}

.share-copy:hover {
    border-color: var(--accent);
    color: var(--accent);
}

/* Held after a successful copy, so the press has a visible result even when the
   status line it wrote to has scrolled out of the reader's attention. */
.share-copy.copied {
    border-color: var(--good);
    color: var(--good);
}

.share-done {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 20px 22px 18px;
}

/*
 * `hidden` has to be spelled out here, because these two carry a `display`.
 *
 * The attribute's effect is `display: none` from the UA stylesheet, which any
 * author rule setting `display` outranks — so `.share-form { display: flex }`
 * quietly cancelled it, and the first version of the result pane appeared
 * *underneath* the form it was meant to replace. Attribute selector rather than
 * !important: `.share-form[hidden]` already beats `.share-form` on specificity.
 */
.share-form[hidden],
.share-done[hidden] {
    display: none;
}

/* Each contributed blueprint opens in the viewer — see ui/credits. */
.credit-link {
    color: var(--muted);
    text-decoration: none;
    border-bottom: 1px solid var(--accent-dim);
}

.credit-link:hover {
    color: var(--accent);
    border-bottom-color: var(--accent);
}
