/*
 * A dark, low-chroma interface: the globe and the data carry the colour, and
 * the chrome stays out of the way. Everything that encodes meaning also encodes
 * it in shape, size or text, never in hue alone.
 */

:root {
    --bg: #070b12;
    --panel: rgba(13, 19, 30, 0.94);
    --panel-border: rgba(120, 160, 210, 0.18);
    --ink: #dce6f2;
    --ink-dim: #8a9bb2;
    --ink-faint: #5d6d84;
    --accent: #64d2ff;
    --accent-warm: #f5a623;

    --sev-critical: #ff4d5e;
    --sev-high: #ff8a3d;
    --sev-medium: #f5c542;
    --sev-low: #7fd1c1;
    --sev-info: #7f95b5;

    --panel-width: 320px;
    --topbar-height: 52px;
}

* { box-sizing: border-box; }

html {
    height: 100%;
}

body {
    margin: 0;
    /*
     * min-height, not height. Every page loads this stylesheet, and a fixed
     * height with overflow:hidden here silently truncated each of the document
     * pages - accounts, sources, identity, models - at one viewport, with no
     * scrollbar to suggest anything was missing.
     *
     * The two pages that fill the viewport and scroll inside their own
     * containers ask for that below, rather than every other page inheriting
     * it.
     */
    min-height: 100%;
    background: var(--bg);
    color: var(--ink);
    font: 14px/1.5 "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
    font-variant-numeric: tabular-nums;
}

/*
 * The map holds a WebGL globe sized to the window: the page itself must not
 * scroll, or the canvas and the panels over it come apart.
 */
body.map-page {
    height: 100%;
    overflow: hidden;
}

/*
 * The catalogue lays out full-height columns that scroll individually
 * (.cat-tab and friends), so it needs the height even though it sets its own
 * overflow.
 */
body.catalogue-body {
    height: 100%;
}

#globe { position: absolute; inset: 0; }

/* Cesium's own widgets, toned down to match. */
.cesium-viewer-bottom { display: none; }
.cesium-viewer-animationContainer { bottom: 6px; left: 8px; }
.cesium-viewer-timelineContainer { bottom: 6px; left: 178px; right: 8px; }
.cesium-widget-credits { display: none !important; }

/* ---------------------------------------------------------------- top bar */

#topbar {
    position: absolute;
    top: 0; left: 0; right: 0;
    height: var(--topbar-height);
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 0 16px;
    background: linear-gradient(180deg, rgba(7, 11, 18, 0.95), rgba(7, 11, 18, 0.72));
    border-bottom: 1px solid var(--panel-border);
    backdrop-filter: blur(8px);
    z-index: 20;
}

.brand { display: flex; align-items: baseline; gap: 8px; }
.brand-mark {
    width: 10px; height: 10px; border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 12px var(--accent);
    align-self: center;
}
.brand-name { font-weight: 650; letter-spacing: 0.02em; }
.brand-sub { color: var(--ink-faint); font-size: 12px; }

.readout { display: flex; flex-direction: column; line-height: 1.25; }
/*
 * `display: flex` above beats the browser's own [hidden] rule, so every
 * readout stayed on screen after being hidden in script. It was patched once
 * for #identity-readout alone, which fixed that badge and left the others -
 * the selection cursor never actually hid, and the follow indicator would have
 * gone the same way. One rule for the class, so the next readout added does
 * not have to rediscover this.
 */
.readout[hidden] { display: none; }
.readout-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-faint); }
.readout-value { font-size: 13px; color: var(--ink); font-variant-numeric: tabular-nums; }

/*
 * The bar carries what has to be read, and one button for everything else.
 *
 * It used to carry eight buttons, which fitted until it did not: Add TLE
 * wrapped onto two lines and the readouts started losing room to navigation.
 * Readouts earn their place because they change on their own and are watched;
 * a button does not become more useful for being visible.
 *
 * Two things stay outside the menu. Who you are signed in as, because acting
 * on the wrong installation's picture is a real mistake and the answer should
 * never be a click away. And the alert lamp, because it is the indicator this
 * whole display exists to be read at a glance.
 */
#topbar > .readout:last-of-type,
#topbar #identity-readout { margin-left: auto; }
#topbar #identity-readout + .ghost-button,
#topbar .ghost-button + .menu-wrap { margin-left: 0; }
#topbar .ghost-button { margin-left: 0; }

#identity-readout .readout-value { max-width: 220px; overflow: hidden; text-overflow: ellipsis; }

/*
 * A readout is one line or it is not a readout.
 *
 * Wrapped, they push the bar taller and take the height from the globe; worse,
 * a count that reflows every time it changes is a count you cannot read while
 * it changes. So they never wrap, and the bar sheds the least useful things
 * first as it narrows: the tagline, then the label above the catalogue count,
 * then the count itself. Who you are and what time it is survive to the end.
 */
#topbar .readout-value,
#topbar .readout-label { white-space: nowrap; }

/*
 * Readouts give way; the menu never does.
 *
 * They shrink and truncate rather than pushing the bar wider, because a bar
 * that overflows pushes the menu button off the right-hand edge - which is
 * exactly the button somebody reaches for when they cannot find anything.
 * Buttons hold their size; text yields.
 */
#topbar .readout { flex: 0 1 auto; min-width: 0; overflow: hidden; }
#topbar .readout .readout-value { overflow: hidden; text-overflow: ellipsis; }
#topbar .ghost-button, #topbar .menu-wrap, #topbar .brand { flex: 0 0 auto; }

/* Steppers must not be truncated: a half-drawn arrow is not a control. */
#time-controls, #cursor-readout { flex: 0 0 auto; overflow: visible; }

@media (max-width: 1400px) {
    #topbar { gap: 14px; }
    .brand-sub { display: none; }
}

@media (max-width: 1150px) {
    #status-readout .readout-label { display: none; }
    #identity-readout .readout-value { max-width: 150px; }
}

@media (max-width: 980px) {
    #status-readout { display: none; }
}

.menu-wrap { position: relative; }

/* Three lines, drawn rather than typed, so it does not depend on a font. */
.menu-glyph {
    display: inline-block;
    width: 11px; height: 8px;
    border-top: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    position: relative;
    vertical-align: -1px;
    margin-right: 3px;
}
.menu-glyph::after {
    content: '';
    position: absolute;
    left: 0; right: 0; top: 50%;
    border-top: 1.5px solid currentColor;
    transform: translateY(-50%);
}

/*
 * The menu scrolls rather than being cut off.
 *
 * It grows an item whenever the software grows a panel, and the map page sets
 * overflow:hidden on the body - so on a short window the bottom of the menu was
 * simply not there, with nothing to suggest anything was missing. Sign out is
 * the last item, which is the worst one to lose.
 */
.menu {
    position: absolute;
    top: calc(100% + 7px);
    right: 0;
    min-width: 268px;
    max-height: calc(100vh - var(--topbar-height) - 18px);
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 8px;
    padding: 7px;
    box-shadow: 0 12px 34px rgba(0, 0, 0, 0.55);
    z-index: 40;
}
.menu[hidden] { display: none; }

.menu-heading {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--ink-faint);
    margin: 8px 8px 4px;
}
.menu-heading:first-child { margin-top: 4px; }

/*
 * Each item says what the thing is for. The bar could only afford a word, and
 * "Area" does not tell anybody it is where overpasses and standing watches
 * live - the room a menu has is the reason to open one.
 */
.menu-item {
    display: block;
    width: 100%;
    text-align: left;
    background: transparent;
    border: 0;
    border-radius: 6px;
    padding: 7px 9px;
    color: var(--ink);
    font: inherit;
    font-size: 13px;
    cursor: pointer;
    text-decoration: none;
}
.menu-item em {
    display: block;
    font-style: normal;
    font-size: 11.5px;
    color: var(--ink-faint);
    margin-top: 1px;
}
.menu-item:hover { background: rgba(120, 160, 210, 0.10); }
.menu-item[hidden] { display: none; }

/* A panel that is already open, so the menu says what is on rather than only
   what can be turned on. */
.menu-item[aria-pressed="true"] { color: var(--accent); }
.menu-item[aria-pressed="true"] em { color: var(--ink-dim); }

.ghost-button {
    background: transparent;
    border: 1px solid var(--panel-border);
    color: var(--ink-dim);
    padding: 6px 12px;
    border-radius: 6px;
    cursor: pointer;
    font: inherit;
    font-size: 13px;
}
.ghost-button:hover { color: var(--ink); border-color: rgba(120, 160, 210, 0.45); }
.ghost-button[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }

.badge {
    display: inline-block;
    min-width: 18px;
    padding: 0 5px;
    margin-left: 4px;
    border-radius: 9px;
    background: var(--sev-high);
    color: #1a0e04;
    font-size: 11px;
    font-weight: 700;
    text-align: center;
}
.badge[data-count="0"] { background: rgba(120, 160, 210, 0.25); color: var(--ink-faint); }

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

/*
 * Every panel scrolls when it does not fit, and none of them disappears.
 *
 * The 108px is the room the globe's own timeline and animation widget need at
 * the foot of the window. On a short window that reserve can eat the whole
 * panel, so it is a floor rather than a subtraction: below about 400px of
 * height the panel keeps 220px and overlaps the timeline, because a panel
 * covering a widget is a nuisance and a panel two rows tall is unusable.
 */
.panel {
    position: absolute;
    top: var(--topbar-height);
    width: var(--panel-width);
    max-height: max(220px, calc(100% - var(--topbar-height) - 108px));
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    backdrop-filter: blur(10px);
    z-index: 15;
}

#left-panel { left: 0; border-left: none; border-top: none; }

/*
 * The close button floats over the first section, so that section's heading
 * needs to keep clear of it. Only the first: the button scrolls away with it.
 */
#left-panel > .panel-section:first-of-type h2 { padding-right: 26px; }
#right-panel { right: 0; border-right: none; border-top: none; width: 380px; }
#alert-panel { right: 0; border-right: none; border-top: none; width: 420px; }
.panel[hidden] { display: none; }

/*
 * The panels whose content sits directly against the frame.
 *
 * .panel deliberately provides no inset, because two different arrangements
 * need none: #left-panel and #area-panel are built from .panel-section, which
 * brings its own, and #right-panel wraps its content in a padded container. The
 * rest set "padding: 0 16px 20px" individually - and four of them were simply
 * never given it, so their headings and text ran into the border.
 *
 * Listed by name rather than fixed with a blanket rule on .panel, which would
 * double the inset on the three arrangements that already have one.
 */
#capability-panel,
#sensor-panel,
#account-panel,
#alert-panel {
    padding: 0 16px 20px;
}

.panel-section { padding: 14px 16px; border-bottom: 1px solid rgba(120, 160, 210, 0.1); }
.panel h2 {
    margin: 0 0 10px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--ink-faint);
    font-weight: 600;
}
.panel h2 em { font-style: normal; color: var(--ink-dim); text-transform: none; letter-spacing: 0; }

.close-button {
    position: sticky;
    top: 8px;
    float: right;
    margin: 8px 8px 0 0;
    background: transparent;
    border: none;
    color: var(--ink-faint);
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    z-index: 2;
}
.close-button:hover { color: var(--ink); }

.note { margin: 8px 0 0; font-size: 11px; color: var(--ink-faint); line-height: 1.45; }

/* ---------------------------------------------------------------- controls */

.stack { display: flex; flex-direction: column; gap: 7px; }

.field { display: block; margin-bottom: 12px; }
.field > span:first-child {
    display: block;
    font-size: 11px;
    color: var(--ink-faint);
    margin-bottom: 5px;
}
.field em { font-style: normal; color: var(--ink); }

input[type="search"], select {
    width: 100%;
    padding: 7px 9px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--panel-border);
    border-radius: 5px;
    color: var(--ink);
    font: inherit;
    font-size: 13px;
}
input[type="search"]:focus, select:focus { outline: 1px solid var(--accent); border-color: var(--accent); }
input[type="range"] { width: 100%; accent-color: var(--accent); }

.checkbox, .layer-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--ink-dim);
    cursor: pointer;
}
.checkbox input, .layer-toggle input { accent-color: var(--accent); }
.layer-toggle span { line-height: 1.3; }
.layer-toggle small { display: block; color: var(--ink-faint); font-size: 11px; }

.chip-row { display: flex; flex-wrap: wrap; gap: 5px; }
.chip {
    padding: 4px 9px;
    border: 1px solid var(--panel-border);
    border-radius: 12px;
    font-size: 12px;
    color: var(--ink-dim);
    cursor: pointer;
    user-select: none;
    background: transparent;
}
.chip[aria-pressed="true"] { background: rgba(100, 210, 255, 0.16); border-color: var(--accent); color: var(--accent); }
.chip small { color: var(--ink-faint); margin-left: 4px; }

.primary-button {
    width: 100%;
    padding: 8px;
    background: rgba(100, 210, 255, 0.16);
    border: 1px solid var(--accent);
    border-radius: 5px;
    color: var(--accent);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}
.primary-button:hover { background: rgba(100, 210, 255, 0.26); }

/* ----------------------------------------------------------------- legend */

.legend { margin-top: 10px; display: flex; flex-direction: column; gap: 5px; }
.legend-item { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--ink-dim); }
.legend-swatch {
    width: 12px; height: 12px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.55);
    flex: none;
}
/* Shape repeats what colour says, so the legend survives a colour-blind reader. */
.legend-swatch[data-shape="square"] { border-radius: 2px; }
.legend-swatch[data-shape="diamond"] { border-radius: 2px; transform: rotate(45deg); }
.legend-swatch[data-shape="ring"] { background: transparent !important; border-width: 3px; }
.legend-count { margin-left: auto; color: var(--ink-faint); font-size: 11px; }

/* ------------------------------------------------------------ object list */

.object-list { display: flex; flex-direction: column; gap: 1px; }
.object-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 2px 8px;
    padding: 7px 8px;
    border-radius: 4px;
    cursor: pointer;
    border-left: 3px solid transparent;
}
.object-row:hover { background: rgba(255, 255, 255, 0.05); }
.object-row[aria-selected="true"] { background: rgba(100, 210, 255, 0.14); border-left-color: var(--accent); }
.object-row .name { font-size: 13px; color: var(--ink); }
.object-row .meta { grid-column: 1 / -1; font-size: 11px; color: var(--ink-faint); }
.object-row .alert-dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--sev-medium);
    align-self: center;
}

/* Data age is shown, never implied. Stale rows read as less certain. */
.freshness { font-size: 10px; padding: 1px 5px; border-radius: 3px; text-transform: uppercase; letter-spacing: 0.05em; }
.freshness[data-level="fresh"] { background: rgba(127, 209, 193, 0.18); color: #7fd1c1; }
.freshness[data-level="ageing"] { background: rgba(245, 197, 66, 0.16); color: #f5c542; }
.freshness[data-level="stale"] { background: rgba(255, 138, 61, 0.16); color: #ff8a3d; }
.freshness[data-level="very_stale"] { background: rgba(255, 77, 94, 0.18); color: #ff4d5e; }
.freshness[data-level="unknown"] { background: rgba(120, 160, 210, 0.14); color: var(--ink-faint); }

/* ---------------------------------------------------------- object detail */

#object-detail { padding: 6px 16px 20px; }
.detail-title { margin: 0 0 2px; font-size: 17px; font-weight: 600; }
.detail-sub { color: var(--ink-faint); font-size: 12px; margin-bottom: 14px; }

.detail-group { margin-bottom: 16px; }
.detail-group > h3 {
    margin: 0 0 7px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--ink-faint);
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 6px;
}

.kv { display: grid; grid-template-columns: 118px 1fr; gap: 3px 10px; font-size: 12.5px; }
.kv dt { color: var(--ink-faint); }
.kv dd { margin: 0; color: var(--ink); word-break: break-word; }

/*
 * The origin tag is load-bearing, not decoration: it separates what a sensor
 * measured, what a source asserted, and what this system computed.
 */
.origin {
    display: inline-block;
    font-size: 9.5px;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    padding: 1px 5px;
    border-radius: 3px;
    border: 1px solid;
    font-weight: 600;
}
.origin[data-origin="observed"] { color: #7fd1c1; border-color: rgba(127, 209, 193, 0.5); }
.origin[data-origin="reported"] { color: var(--accent); border-color: rgba(100, 210, 255, 0.45); }
.origin[data-origin="derived"] { color: var(--accent-warm); border-color: rgba(245, 166, 35, 0.45); }

.timeline-list { display: flex; flex-direction: column; gap: 7px; }
.timeline-item {
    border-left: 2px solid rgba(120, 160, 210, 0.25);
    padding: 2px 0 2px 9px;
    font-size: 12px;
}
.timeline-item .when { color: var(--ink-faint); font-size: 11px; }
.timeline-item .what { color: var(--ink-dim); }

/* ------------------------------------------------------------------ alerts */

.alert-list { padding: 0 16px 20px; display: flex; flex-direction: column; gap: 10px; }
.alert-card {
    border: 1px solid var(--panel-border);
    border-left-width: 3px;
    border-radius: 5px;
    padding: 10px 11px;
    background: rgba(255, 255, 255, 0.025);
}
.alert-card[data-severity="critical"] { border-left-color: var(--sev-critical); }
.alert-card[data-severity="high"] { border-left-color: var(--sev-high); }
.alert-card[data-severity="medium"] { border-left-color: var(--sev-medium); }
.alert-card[data-severity="low"] { border-left-color: var(--sev-low); }
.alert-card[data-severity="info"] { border-left-color: var(--sev-info); }

.alert-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 5px; flex-wrap: wrap; }
.alert-object { font-weight: 600; font-size: 13px; cursor: pointer; color: var(--accent); }
.alert-type { font-size: 11px; color: var(--ink-faint); }
.alert-title { font-size: 12.5px; color: var(--ink-dim); margin-bottom: 7px; }

/* Severity and confidence are separate quantities and are shown as two. */
.alert-metrics { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
.metric {
    font-size: 10.5px;
    padding: 2px 6px;
    border-radius: 3px;
    background: rgba(255, 255, 255, 0.06);
    color: var(--ink-dim);
}
.metric b { color: var(--ink); font-weight: 600; }

.alert-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.alert-actions button {
    flex: 1;
    min-width: 84px;
    padding: 5px 8px;
    font-size: 11.5px;
    background: transparent;
    border: 1px solid var(--panel-border);
    border-radius: 4px;
    color: var(--ink-dim);
    cursor: pointer;
    font-family: inherit;
}
.alert-actions button:hover { color: var(--ink); border-color: rgba(120, 160, 210, 0.45); }

.evidence {
    margin-top: 8px;
    border-top: 1px solid rgba(120, 160, 210, 0.12);
    padding-top: 8px;
}
.evidence summary {
    cursor: pointer;
    font-size: 11px;
    color: var(--ink-faint);
    text-transform: uppercase;
    letter-spacing: 0.07em;
}
.evidence pre {
    margin: 8px 0 0;
    padding: 8px;
    background: rgba(0, 0, 0, 0.35);
    border-radius: 4px;
    font-size: 11px;
    line-height: 1.45;
    color: var(--ink-dim);
    overflow-x: auto;
    white-space: pre-wrap;
    word-break: break-word;
}

/* ------------------------------------------------------------------ misc */

#boot-error {
    position: absolute;
    inset: auto 0 0 0;
    padding: 12px 16px;
    background: rgba(120, 20, 30, 0.92);
    color: #ffd9dd;
    font-size: 13px;
    z-index: 50;
}
#boot-error[hidden] { display: none; }

.empty { color: var(--ink-faint); font-size: 12px; padding: 8px 0; }

/*
 * Scrollbars, so the panels do not sprout a bright native bar.
 *
 * Every scrolling surface over the globe is listed here rather than only the
 * panels: a menu or a dialog that scrolls with the platform's own bar looks
 * like a different application has opened on top of this one.
 */
.panel::-webkit-scrollbar,
.menu::-webkit-scrollbar,
.welcome::-webkit-scrollbar,
.object-list::-webkit-scrollbar { width: 8px; }
.panel::-webkit-scrollbar-thumb,
.menu::-webkit-scrollbar-thumb,
.welcome::-webkit-scrollbar-thumb,
.object-list::-webkit-scrollbar-thumb {
    background: rgba(120, 160, 210, 0.22);
    border-radius: 4px;
}
.panel::-webkit-scrollbar-track,
.menu::-webkit-scrollbar-track,
.welcome::-webkit-scrollbar-track { background: transparent; }

/*
 * Firefox and anything else following the standard property. Same intent as
 * the WebKit block above: a thin bar in the palette rather than the platform's.
 */
.panel, .menu, .welcome, .object-list {
    scrollbar-width: thin;
    scrollbar-color: rgba(120, 160, 210, 0.28) transparent;
}

/* A short window has no tenth of a viewport to spend on a dialog's margin. */
@media (max-height: 720px) {
    .welcome { padding-top: 3vh; }
}

@media (max-width: 900px) {
    :root { --panel-width: 280px; }
    #right-panel, #alert-panel { width: min(92vw, 380px); }
    .brand-sub { display: none; }
}

/* ------------------------------------------------------------ follow list */

.follow-list {
    width: 100%;
    padding: 4px;
    background: rgba(0, 0, 0, 0.28);
    border: 1px solid var(--panel-border);
    border-radius: 5px;
    color: var(--ink);
    font: inherit;
    font-size: 12.5px;
}
.follow-list option {
    padding: 3px 5px;
    border-radius: 3px;
}
.follow-list option:checked {
    background: rgba(100, 210, 255, 0.22);
    color: var(--accent);
}

.button-row { display: flex; gap: 6px; margin-top: 8px; }
.button-row .primary-button { flex: 2; }
.button-row .ghost-button { flex: 1; }
.ghost-button[disabled] { opacity: 0.4; cursor: default; }

/*
 * The primary buttons had no disabled state at all, so a button the code had
 * switched off still looked like the thing to press - Ask, on an installation
 * with no model connected, most visibly.
 */
.primary-button[disabled] { opacity: 0.4; cursor: default; }

/* Following is a mode, and the interface should say so rather than leaving the
   user to work out why the camera will not stay where they put it. */
#follow-status[data-following="true"] {
    color: var(--accent);
}

/*
 * The flag is an identity cue, never the identity itself: the country code and
 * name are always written out beside it, and an object whose owner is an
 * organisation shows no flag rather than a misleading one.
 */
.flag {
    font-size: 13px;
    line-height: 1;
    margin-right: 4px;
    /* Keep the glyph from stretching the row on systems that substitute. */
    font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}
.object-row .flag { font-size: 12px; }
.detail-title .flag { font-size: 18px; }

/* ------------------------------------------------------- traffic lights */

/*
 * Red, amber, green. The point of a traffic light is that it is read before it
 * is understood, so the lamps are large, saturated and always in the same
 * order - and critical carries a ring the others do not, so it is still
 * distinguishable in a greyscale screenshot or to a red-green colour-blind
 * reader.
 */
:root {
    --lamp-red: #ff2f43;
    --lamp-amber: #ffb020;
    --lamp-green: #35c66b;
}

.lamp {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex: none;
    vertical-align: middle;
    margin-right: 5px;
    border: 1px solid rgba(0, 0, 0, 0.45);
}
.lamp[data-level="red"] {
    background: var(--lamp-red);
    box-shadow: 0 0 8px rgba(255, 47, 67, 0.85);
}
.lamp[data-level="amber"] {
    background: var(--lamp-amber);
    box-shadow: 0 0 6px rgba(255, 176, 32, 0.6);
}
.lamp[data-level="green"] {
    background: var(--lamp-green);
    box-shadow: none;
    opacity: 0.75;
}
/* Critical is red plus a halo: two cues, not one. */
.lamp[data-critical="true"] {
    outline: 2px solid rgba(255, 47, 67, 0.45);
    outline-offset: 2px;
}

.badge[data-level="red"] { background: var(--lamp-red); color: #fff; }
.badge[data-level="amber"] { background: var(--lamp-amber); color: #1a0e04; }
.badge[data-level="green"] { background: rgba(53, 198, 107, 0.25); color: var(--lamp-green); }

/* The alert list is sorted worst-first and banded by level, so the eye lands
   on red without reading a word. */
.alert-card[data-level="red"] { border-left-color: var(--lamp-red); background: rgba(255, 47, 67, 0.07); }
.alert-card[data-level="amber"] { border-left-color: var(--lamp-amber); background: rgba(255, 176, 32, 0.05); }
.alert-card[data-level="green"] { border-left-color: var(--lamp-green); }

.level-tag {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 1px 6px;
    border-radius: 3px;
}
.level-tag[data-level="red"] { background: var(--lamp-red); color: #fff; }
.level-tag[data-level="amber"] { background: var(--lamp-amber); color: #1a0e04; }
.level-tag[data-level="green"] { background: rgba(53, 198, 107, 0.2); color: var(--lamp-green); }

.object-row .alert-dot { background: var(--lamp-amber); }
.object-row .alert-dot[data-level="red"] {
    background: var(--lamp-red);
    box-shadow: 0 0 6px rgba(255, 47, 67, 0.8);
}

/* ---------------------------------------------------------- country menu */

.field-label {
    display: block;
    font-size: 11px;
    color: var(--ink-faint);
    margin-bottom: 5px;
}

.button-row.compact { margin: 6px 0; }
.button-row.compact .ghost-button { flex: 1; padding: 4px 8px; font-size: 12px; }

/*
 * The four orbit regimes, on one line. They are short labels and a column of
 * them would push the angle slider off the panel for no gain.
 */
.coverage-orbits {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
    margin: 2px 0 4px;
}

.coverage-orbits .checkbox { margin: 0; }

.country-list {
    max-height: 168px;
    overflow-y: auto;
    border: 1px solid var(--panel-border);
    border-radius: 5px;
    background: rgba(0, 0, 0, 0.22);
    padding: 4px;
}
.country-row {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 3px 5px;
    border-radius: 3px;
    cursor: pointer;
    font-size: 12.5px;
    color: var(--ink-dim);
}
.country-row:hover { background: rgba(255, 255, 255, 0.05); }
.country-row input { accent-color: var(--accent); flex: none; }
.country-row .country-swatch {
    width: 9px; height: 9px; border-radius: 50%; flex: none;
    border: 1px solid rgba(255, 255, 255, 0.5);
}
.country-row .country-name { flex: 1; }
.country-row .country-count { color: var(--ink-faint); font-size: 11px; }


/* ------------------------------------------------------ loading feedback */

/*
 * A filter change on a large set takes seconds. Without this the globe simply
 * sits there and the user concludes the control did nothing - which is exactly
 * what was reported. The banner is centred over the globe and says what is
 * being fetched, so waiting is a visible state rather than an ambiguous one.
 */
.loading {
    position: absolute;
    top: calc(var(--topbar-height) + 14px);
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 16px;
    background: rgba(13, 19, 30, 0.95);
    border: 1px solid var(--accent);
    border-radius: 20px;
    color: var(--ink);
    font-size: 13px;
    z-index: 30;
    box-shadow: 0 4px 22px rgba(0, 0, 0, 0.55);
    pointer-events: none;
}
.loading[hidden] { display: none; }

.spinner {
    width: 13px;
    height: 13px;
    border: 2px solid rgba(100, 210, 255, 0.28);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.7s linear infinite;
    flex: none;
}
@keyframes spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
    .spinner { animation-duration: 2.4s; }
}


/* ------------------------------------------------- manual trajectory entry */

#tle-panel { right: 0; border-right: none; border-top: none; width: 420px; padding: 0 16px 20px; }
#tle-panel h2 { margin-top: 14px; }

/*
 * Monospace and no wrapping: a TLE is a fixed-column format, and a proportional
 * font hides the misalignment that is the most common way to paste one wrong.
 */
.tle-input {
    width: 100%;
    margin: 10px 0 0;
    padding: 9px 10px;
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid var(--panel-border);
    border-radius: 5px;
    color: var(--ink);
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 11.5px;
    line-height: 1.5;
    white-space: pre;
    overflow-x: auto;
    resize: vertical;
}
.tle-input:focus { outline: 1px solid var(--accent); border-color: var(--accent); }

#tle-result { margin-top: 14px; }

.tle-status {
    padding: 9px 11px;
    border-radius: 5px;
    font-size: 12.5px;
    border-left: 3px solid;
    margin-bottom: 12px;
}
.tle-status[data-state="ok"] {
    background: rgba(53, 198, 107, 0.09);
    border-left-color: var(--lamp-green);
    color: var(--ink);
}
.tle-status[data-state="error"] {
    background: rgba(255, 47, 67, 0.09);
    border-left-color: var(--lamp-red);
    color: #ffd9dd;
}
.tle-status[data-state="added"] {
    background: rgba(100, 210, 255, 0.1);
    border-left-color: var(--accent);
    color: var(--ink);
}

/* The preview is not catalogue data and must not look like it. */
.preview-tag {
    display: inline-block;
    font-size: 9.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: 1px 6px;
    border-radius: 3px;
    background: #c792ea;
    color: #16091f;
    margin-left: 6px;
}


/* --------------------------------------------------------- affiliation */

#affiliation-panel { right: 0; border-right: none; border-top: none; width: 400px; padding: 0 16px 20px; }
#affiliation-panel h2 { margin-top: 14px; }

.affiliation-list {
    max-height: 340px;
    overflow-y: auto;
    border: 1px solid var(--panel-border);
    border-radius: 5px;
    background: rgba(0, 0, 0, 0.22);
    padding: 4px;
}
.affiliation-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 6px;
    border-radius: 3px;
    font-size: 12.5px;
    color: var(--ink-dim);
}
.affiliation-row:hover { background: rgba(255, 255, 255, 0.04); }
.affiliation-row .country-name { flex: 1; }
.affiliation-row .country-count { color: var(--ink-faint); font-size: 11px; min-width: 46px; text-align: right; }
.affiliation-row select {
    width: 104px;
    padding: 3px 5px;
    font-size: 11.5px;
    /* The control carries the colour it will paint on the map, so the choice
       and its consequence are the same thing on screen. */
    border-left-width: 3px;
    border-left-style: solid;
}
.affiliation-row select[data-value="own"] { border-left-color: #4a90ff; }
.affiliation-row select[data-value="ally"] { border-left-color: #35c66b; }
.affiliation-row select[data-value="unknown"] { border-left-color: #ffb020; }
.affiliation-row select[data-value="adversary"] { border-left-color: #ff2f43; }

.affiliation-row.changed { background: rgba(100, 210, 255, 0.1); }


/* ------------------------------------------- steppers: selection and time */

/*
 * Where you are in a sequence, and how to move through it. The buttons carry
 * the same action as the arrow keys rather than describing them: a shortcut
 * nobody knows about is not a feature, and a hint that cannot be clicked is
 * not a control.
 */
.stepper { display: flex; align-items: center; gap: 4px; }

.step-button {
    background: transparent;
    border: 1px solid var(--panel-border);
    color: var(--ink-dim);
    border-radius: 5px;
    padding: 1px 6px;
    font: inherit;
    font-size: 11px;
    line-height: 1.5;
    cursor: pointer;
}
.step-button:hover:not(:disabled) { color: var(--ink); border-color: rgba(120, 160, 210, 0.45); }
.step-button:disabled { opacity: 0.35; cursor: default; }

#time-step {
    background: var(--panel);
    border: 1px solid var(--panel-border);
    color: var(--ink-dim);
    border-radius: 5px;
    padding: 1px 4px;
    font: inherit;
    font-size: 11px;
}

/* Present, so the clock reads differently from a moment you have travelled to. */
#time-now[data-at-present="true"] { color: var(--accent); border-color: var(--accent); }

.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip-path: inset(50%);
    overflow: hidden;
}

#cursor-readout .readout-value { color: var(--accent); min-width: 5.5em; text-align: center; }

kbd {
    display: inline-block;
    padding: 0 3px;
    border: 1px solid var(--panel-border);
    border-bottom-width: 2px;
    border-radius: 3px;
    font-family: inherit;
    font-size: 10px;
    line-height: 1.3;
    color: var(--ink-dim);
}


/* -------------------------------------------------------- overpass search */

#area-panel { right: 0; border-right: none; border-top: none; width: 430px; padding: 0 16px 20px; }
#area-panel h2 { margin-top: 14px; }
#area-result { margin-top: 14px; }

.area-summary {
    padding: 10px 11px;
    border-radius: 5px;
    background: rgba(100, 210, 255, 0.08);
    border-left: 3px solid var(--accent);
    font-size: 12.5px;
    margin-bottom: 12px;
}
.area-summary b { color: var(--ink); }

.pass-list { display: flex; flex-direction: column; gap: 1px; max-height: 46vh; overflow-y: auto; }
.pass-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 1px 8px;
    padding: 6px 8px;
    border-radius: 4px;
    cursor: pointer;
    border-left: 3px solid transparent;
    font-size: 12.5px;
}
.pass-row:hover { background: rgba(255, 255, 255, 0.05); }
.pass-row[aria-selected="true"] { background: rgba(100, 210, 255, 0.14); border-left-color: var(--accent); }
.pass-row .when { color: var(--accent); font-variant-numeric: tabular-nums; }
.pass-row .meta { grid-column: 1 / -1; font-size: 11px; color: var(--ink-faint); }

/* ------------------------------------------------------------ orbital data */

/*
 * The age of the picture, stated before anything is offered about it. The
 * colour is a second carrier, never the only one: the words say "24 days old"
 * whether or not anybody can see the amber.
 */
.refresh-age {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
    margin: 10px 0 12px;
    padding: 12px;
    border: 1px solid var(--panel-border);
    border-left: 3px solid var(--sev-low);
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.02);
}

.refresh-age[data-state="warn"] { border-left-color: var(--sev-medium); }
.refresh-age[data-state="bad"] { border-left-color: var(--sev-high); }

.age-figure { display: flex; flex-direction: column; gap: 2px; }
.age-figure .value { font-size: 17px; font-weight: 600; }
.age-figure .label { font-size: 11.5px; color: var(--ink-dim); }
.age-figure .note { font-size: 10.5px; color: var(--ink-faint); }

.refresh-arrived {
    margin: 10px 0;
    padding: 10px 12px;
    border: 1px solid var(--accent);
    border-radius: 6px;
    background: rgba(100, 210, 255, 0.08);
}

.refresh-arrived p { margin: 0 0 8px; font-size: 12.5px; color: var(--ink); }

.refresh-progress { margin-top: 10px; }

.refresh-log {
    margin: 6px 0 0;
    max-height: 22vh;
    overflow-y: auto;
    padding: 8px 10px;
    border: 1px solid var(--panel-border);
    border-radius: 5px;
    background: rgba(8, 12, 20, 0.75);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    line-height: 1.5;
    color: var(--ink-dim);
    white-space: pre-wrap;
}

.refresh-sources { display: flex; flex-direction: column; gap: 1px; max-height: 32vh; overflow-y: auto; }

.source-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 1px 8px;
    padding: 6px 8px;
    border-radius: 4px;
    border-left: 3px solid transparent;
    font-size: 12.5px;
}

.source-row .when { color: var(--ink-dim); font-variant-numeric: tabular-nums; }
.source-row .meta { grid-column: 1 / -1; font-size: 11px; color: var(--ink-faint); }

/* A feed that failed is the likeliest answer to "why is this old". */
.source-row[data-status="failed"] { border-left-color: var(--sev-high); background: rgba(255, 138, 61, 0.07); }
.source-row[data-status="running"] { border-left-color: var(--accent); }

/* --------------------------------------------------------- quiet periods */

/*
 * The gaps read as the absence they describe: no accent, no pointer, nothing
 * to click. They are a different kind of row from a pass - a pass is an object
 * you can select and follow, a gap is a period when there is no object.
 */
.coverage-gaps { margin-top: 14px; }

.coverage-gaps h3 {
    margin: 0 0 6px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--ink-dim);
}

.gap-summary { margin: 0 0 8px; font-size: 12.5px; color: var(--ink); }

.gap-list { display: flex; flex-direction: column; gap: 1px; max-height: 26vh; overflow-y: auto; }

.gap-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 8px;
    padding: 5px 8px;
    border-radius: 4px;
    border-left: 3px solid rgba(120, 160, 210, 0.25);
    background: rgba(255, 255, 255, 0.02);
    font-size: 12px;
}

.gap-row .when { color: var(--ink-dim); font-variant-numeric: tabular-nums; }
.gap-row .meta { color: var(--ink-faint); font-variant-numeric: tabular-nums; }

/* The one a reader is usually looking for. */
.gap-row.gap-longest {
    border-left-color: var(--accent-warm);
    background: rgba(245, 166, 35, 0.08);
}

.gap-row.gap-longest .when, .gap-row.gap-longest .meta { color: var(--accent-warm); }


/* ------------------------------------------------------------ launch log */

#launch-panel { right: 0; border-right: none; border-top: none; width: 420px; padding: 0 16px 20px; }
#launch-panel h2 { margin-top: 14px; }

.year-bars { display: flex; flex-direction: column; gap: 2px; margin: 10px 0 4px; }
.year-bar { display: grid; grid-template-columns: 42px 1fr 34px; align-items: center; gap: 7px; font-size: 11.5px; }
.year-bar .year { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.year-bar .bar { height: 8px; background: var(--accent); border-radius: 2px; min-width: 2px; opacity: 0.75; }
.year-bar .count { color: var(--ink-dim); text-align: right; font-variant-numeric: tabular-nums; }

.launch-list { display: flex; flex-direction: column; gap: 1px; max-height: 40vh; overflow-y: auto; }
.launch-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 1px 8px;
    padding: 6px 8px;
    border-radius: 4px;
    cursor: pointer;
    border-left: 3px solid transparent;
    font-size: 12.5px;
}
.launch-row:hover { background: rgba(255, 255, 255, 0.05); }
.launch-row .when { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.launch-row .meta { grid-column: 1 / -1; font-size: 11px; color: var(--ink-faint); }

/* The launch site in the object panel is a way in, so it should look like one. */
.linkish { color: var(--accent); cursor: pointer; text-decoration: underline dotted; }


/* ------------------------------------------------------- sensor access */

/*
 * The access zones, in the panel and on the globe.
 *
 * Green and yellow, and the words beside them every time. The colours are the
 * display's choice and not a property of the sensor - the model stores a class,
 * so a fourth one later is a row here rather than a change to the data - and
 * neither colour means a percentage. Saying "probable" and "possible" in the
 * legend is what stops them being read as 80 against 30.
 */
.zone-row {
    display: grid;
    grid-template-columns: 12px auto 1fr auto;
    align-items: baseline;
    gap: 8px;
    padding: 5px 0;
    font-size: 12.5px;
    border-bottom: 1px solid var(--panel-border);
}
.zone-row:last-of-type { border-bottom: none; }

.zone-swatch {
    width: 10px; height: 10px;
    border-radius: 2px;
    border: 1px solid;
}
.zone-row[data-class="PROBABLE"] .zone-swatch {
    background: rgba(52, 199, 89, 0.45);
    border-color: rgba(52, 199, 89, 0.9);
}
.zone-row[data-class="POSSIBLE"] .zone-swatch {
    background: rgba(245, 197, 66, 0.28);
    border-color: rgba(245, 197, 66, 0.7);
}

.zone-name { color: var(--ink); }
.zone-angles { color: var(--ink-dim); font-variant-numeric: tabular-nums; }
.zone-ground { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.zone-ground em { font-style: normal; color: var(--accent-warm); }

.zone-grid {
    display: grid;
    grid-template-columns: auto 1fr 1fr;
    gap: 6px 10px;
    align-items: center;
    margin: 10px 0;
}
.zone-grid .zone-label { font-size: 12.5px; color: var(--ink); }
.zone-grid .zone-label[data-class="PROBABLE"] { color: #7ee2a0; }
.zone-grid .zone-label[data-class="POSSIBLE"] { color: #f5c542; }
.zone-grid label { display: flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--ink-faint); }
.zone-grid input {
    width: 100%;
    background: var(--bg);
    border: 1px solid var(--panel-border);
    color: var(--ink);
    border-radius: 5px;
    padding: 4px 6px;
    font: inherit;
    font-size: 12.5px;
}
.zone-grid input:disabled { opacity: 0.4; }

.detail-actions { margin: 8px 0 12px; }

/* A change described before it is made. */
.preview-question { font-size: 13.5px; color: var(--ink); margin: 0 0 6px; }

.preview-changes {
    border: 1px solid var(--panel-border);
    border-radius: 6px;
    padding: 8px 10px;
    margin: 8px 0;
}
.preview-change {
    display: grid;
    grid-template-columns: 1fr auto auto auto;
    gap: 8px;
    align-items: baseline;
    padding: 3px 0;
    font-size: 12px;
}
.preview-change .field { color: var(--ink-dim); }
.preview-change .from { color: var(--ink-faint); text-decoration: line-through; }
.preview-change .arrow { color: var(--ink-faint); }
.preview-change .to { color: var(--accent); font-variant-numeric: tabular-nums; }

.muted { color: var(--ink-faint); }

/*
 * Where a sensor profile came from, and how firmly a group was assembled.
 *
 * Four sources draw the same shape on the globe, so this line is the only thing
 * separating a measured field of regard from a guess. Marked by kind rather
 * than left as prose: the placeholder in particular has to be unmistakable, or
 * a neutral 45-degree cone reads as a finding about the satellite.
 */
.profile-source {
    border-left: 3px solid var(--panel-border);
    padding: 6px 0 6px 10px;
    margin: 8px 0 12px;
}
.profile-source .source-label {
    display: inline-block;
    font-size: 12px;
    color: var(--ink);
    font-weight: 600;
}
.profile-source .source-key {
    display: inline-block;
    margin-left: 6px;
    font-size: 11px;
    color: var(--ink-faint);
}
.profile-source p { margin: 3px 0 0; font-size: 11.5px; color: var(--ink-faint); line-height: 1.45; }

.profile-source[data-source="MANUAL_SATELLITE_OVERRIDE"] { border-left-color: var(--sev-low); }
.profile-source[data-source="MANUAL_SATELLITE_OVERRIDE"] .source-label { color: var(--sev-low); }

.profile-source[data-source="CONSTELLATION_PROFILE"] { border-left-color: var(--accent); }
.profile-source[data-source="CONSTELLATION_PROFILE"] .source-label { color: var(--accent); }

.profile-source[data-source="GENERIC_MISSION_MAPPING"] { border-left-color: var(--sev-medium); }
.profile-source[data-source="GENERIC_MISSION_MAPPING"] .source-label { color: var(--sev-medium); }

/* Not a finding. Dashed, because the value is a stand-in rather than a claim. */
.profile-source[data-source="DEFAULT_UNKNOWN"] {
    border-left-style: dashed;
    border-left-color: var(--ink-faint);
}
.profile-source[data-source="DEFAULT_UNKNOWN"] .source-label { color: var(--ink-dim); }

.grounds {
    border-left: 3px solid var(--panel-border);
    padding: 6px 0 6px 10px;
    margin: 8px 0;
}
.grounds-label { font-size: 11.5px; font-weight: 600; color: var(--ink-dim); }
.grounds p { margin: 3px 0 0; font-size: 11.5px; color: var(--ink-faint); line-height: 1.45; }

.grounds[data-membership="CURATED"] { border-left-color: var(--sev-low); }
.grounds[data-membership="CURATED"] .grounds-label { color: var(--sev-low); }

/* Inferred membership is a warning, not a note: this is the last screen before
   one decision is applied to every member of a set nobody published. */
.grounds[data-membership="NAME_INFERRED"],
.grounds[data-membership="MIXED"] {
    border-left-color: var(--accent-warm);
    background: rgba(245, 166, 35, 0.07);
}
.grounds[data-membership="NAME_INFERRED"] .grounds-label,
.grounds[data-membership="MIXED"] .grounds-label { color: var(--accent-warm); }

/* -------------------------------------------------- capability and access */

.capability-group {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--ink-faint);
    margin: 9px 0 3px;
}
.capability-group:first-child { margin-top: 2px; }

.chip-count { color: var(--ink-faint); font-variant-numeric: tabular-nums; font-size: 10.5px; }

/*
 * A capability nothing is recorded for is shown, greyed, with its zero.
 *
 * Hiding it would make "which satellites have laser communications" impossible
 * to ask, and the honest answer to that question today is none - which a
 * missing option cannot say.
 */
.chip[data-empty="true"] { opacity: 0.45; }
.chip[data-empty="true"][aria-pressed="true"] { opacity: 1; }

.capability-mode { margin-top: 8px; }

.target-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }

.access-class[data-class="PROBABLE"] { color: #7ee2a0; }
.access-class[data-class="POSSIBLE"] { color: #f5c542; }

.access-row { cursor: pointer; }
.access-row:hover { background: rgba(120, 160, 210, 0.08); }

/* Repeated where the results are, because this is the screen where a list of
   times is most likely to be read as a schedule. */
.access-caveat {
    border-left: 3px solid var(--accent-warm);
    background: rgba(245, 166, 35, 0.07);
    padding: 7px 9px;
    margin: 8px 0;
}

/* ------------------------------------------------ capabilities and payloads */

.capability-row {
    border: 1px solid var(--panel-border);
    border-left-width: 3px;
    border-radius: 7px;
    padding: 9px 11px;
    margin-bottom: 8px;
}

/*
 * Marked by how firmly it is attributed. A confirmed payload and a guess from a
 * mission label read identically otherwise, and the difference is the whole
 * reason the status exists.
 */
.capability-row[data-status="CONFIRMED"] { border-left-color: var(--sev-low); }
.capability-row[data-status="REPORTED"] { border-left-color: var(--accent); }
.capability-row[data-status="INFERRED"] { border-left-color: var(--sev-medium); }
.capability-row[data-status="PLACEHOLDER"] { border-left-style: dashed; border-left-color: var(--ink-faint); }

.capability-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.capability-name { font-size: 13.5px; color: var(--ink); }
.capability-status { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-dim); }
.capability-flag {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--accent);
    border: 1px solid var(--accent);
    border-radius: 999px;
    padding: 1px 6px;
}
.capability-detail { margin: 4px 0 0; font-size: 11.5px; color: var(--ink-faint); line-height: 1.45; }
.capability-source { margin: 3px 0 0; font-size: 11px; color: var(--ink-dim); }

/*
 * Who says so, on every capability and every drawn footprint.
 *
 * Not a hover, not a panel behind a button: a claim whose source you have to
 * go looking for is one people stop checking. The grade sits next to the
 * source rather than under it because the two are read together - "CNSA" and
 * "CNSA, A2" carry different weight, and the second is the honest one.
 *
 * The ungraded and unattributed states are styled as deliberately as the
 * graded one. An empty space there would read as an oversight; these read as
 * an answer.
 */
.attribution {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 6px;
    margin: 6px 0 0;
    padding: 5px 8px;
    border-left: 2px solid var(--panel-border);
    background: rgba(255, 255, 255, 0.02);
}
.attribution-says {
    font-size: 9.5px;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-faint);
}
.attribution-source {
    font-size: 11.5px;
    color: var(--ink);
    font-weight: 600;
    /* Publication names are long; wrap rather than push the grade off-panel. */
    overflow-wrap: anywhere;
}
a.attribution-source { color: var(--accent); text-decoration: none; }
a.attribution-source:hover { text-decoration: underline; }

.attribution-count {
    font-size: 10.5px;
    color: var(--ink-faint);
}
.attribution-explains {
    flex-basis: 100%;
    margin: 1px 0 0;
    font-size: 11px;
    color: var(--ink-faint);
    line-height: 1.45;
}
.attribution[data-attributed="no"] { border-left-style: dashed; }
.attribution[data-attributed="no"] .attribution-source {
    color: var(--ink-faint);
    font-weight: 500;
    font-style: italic;
}

/* The grade in the evidence panel, where each record carries its own. */
.evidence-grade { margin: 4px 0 0; }
.capability-actions { display: flex; gap: 6px; margin-top: 8px; }
.capability-actions .ghost-button { padding: 3px 8px; font-size: 11.5px; }
.capability-actions .ghost-button.danger { color: var(--sev-critical); border-color: rgba(255,77,94,0.35); }

/* Said in words, because an empty list and "nobody has looked" render the same. */
.capability-empty {
    border-left: 3px dashed var(--ink-faint);
    padding-left: 10px;
}

.matrix-row {
    display: flex;
    justify-content: space-between;
    padding: 3px 0;
    font-size: 12px;
    border-bottom: 1px solid var(--panel-border);
}
.matrix-row:last-child { border-bottom: none; }
.matrix-row[data-known="false"] { color: var(--ink-faint); }
.matrix-row[data-known="true"] .matrix-status { color: var(--sev-low); }
.matrix-status { font-size: 11px; }

/* A match found through a capability nobody confirmed is a weaker result, and
   in a list of times it otherwise looks identical to a confirmed one. */
.inferred-flag {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--sev-medium);
    border: 1px solid rgba(245, 197, 66, 0.4);
    border-radius: 999px;
    padding: 0 5px;
}

.access-links { grid-column: 1 / -1; display: flex; gap: 10px; margin-top: 5px; }
.access-links .linkish {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    font-size: 11px;
    color: var(--accent);
    cursor: pointer;
    text-decoration: underline;
}

/* --------------------------------------------------- capability evidence */

.capability-why { margin-top: 9px; }

.evidence-record {
    border-left: 2px solid var(--panel-border);
    padding: 8px 0 8px 11px;
    margin-top: 8px;
    font-size: 11.5px;
}
.evidence-record[data-activity="ACTIVE"] { border-left-color: var(--sev-low); }
/* Historical evidence: kept and shown, and not a claim about now. */
.evidence-record[data-activity="INACTIVE"] { border-left-color: var(--ink-faint); border-left-style: dashed; }
.evidence-record[data-activity="UNKNOWN"] { border-left-color: var(--sev-medium); }

.evidence-head { display: flex; gap: 7px; align-items: baseline; flex-wrap: wrap; margin-bottom: 5px; }
.evidence-source { color: var(--ink); font-weight: 600; }
.evidence-activity, .evidence-status {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ink-faint);
}

.evidence-detail {
    display: grid;
    grid-template-columns: 8.5rem 1fr;
    gap: 2px 10px;
    margin: 0;
}
.evidence-detail dt { color: var(--ink-faint); font-size: 11px; }
.evidence-detail dd { margin: 0; color: var(--ink-dim); font-variant-numeric: tabular-nums; word-break: break-word; }
.evidence-detail a { color: var(--accent); }

.evidence-licence { margin: 6px 0 0; font-size: 10.5px; color: var(--ink-faint); }

/*
 * What the drawn access region rests on. Paired with the dashed outline on the
 * globe, so the convention is explained where it is used rather than left for
 * the reader to infer.
 */
.geometry-basis {
    margin: 8px 0 0;
    font-size: 11px;
    line-height: 1.45;
    padding: 6px 9px;
    border-radius: 0 4px 4px 0;
    border-left: 2px solid var(--accent-warm);
    background: rgba(245, 166, 35, 0.08);
    color: var(--ink-dim);
}

.geometry-basis[data-assumed="no"] {
    border-left-color: var(--lamp-green, #35c66b);
    background: rgba(53, 198, 107, 0.08);
}

/* A shortcut nobody is told about is not a feature. */
.field-hint {
    font-style: normal;
    font-size: 9.5px;
    letter-spacing: 0.04em;
    color: var(--ink-faint);
    margin-left: 6px;
}

/* ------------------------------------------------------------------ grades */

/*
 * The Admiralty code, in colour.
 *
 * An earlier version of this file argued against colouring these at all, and
 * then against putting F and 6 at the red end - F means "cannot be judged"
 * rather than "bad". Both were overruled, and the reasoning is recorded here
 * only so the next person does not spend the argument again: a page carrying
 * hundreds of grades needs them scannable, and an ungradeable source is a
 * weaker thing to build on than a well-placed one, whatever the reason.
 *
 * Letters run A to F, green to red. Numbers run 1 to 6 on the same idea, with
 * the three grades this catalogue actually uses pinned where they were asked
 * for: 1 green, 2 yellow, 3 red. The rest continue past red rather than
 * doubling back, so the ranking still reads in the order the scale intends.
 *
 * Defined once here because app.css is loaded on every page, so a grade looks
 * the same in the catalogue, on the globe and on the sources page.
 */
:root {
    --grade-a: #3fb950;   /* A - completely reliable */
    --grade-b: #a9c93a;   /* B - usually reliable */
    --grade-c: #d8b430;   /* C - fairly reliable */
    --grade-d: #e08a3c;   /* D - not usually reliable */
    --grade-e: #dd5f45;   /* E - unreliable */
    --grade-f: #d92d20;   /* F - cannot be judged */

    --credibility-1: #3fb950;  /* confirmed by other sources */
    --credibility-2: #d8b430;  /* probably true */
    --credibility-3: #d92d20;  /* possibly true */
    --credibility-4: #b3261e;  /* doubtful */
    --credibility-5: #8c1d18;  /* improbable */
    --credibility-6: #6b1512;  /* cannot be judged */
}

[data-grade="A"], [data-letter="A"], [data-reliability="A"] { --grade-colour: var(--grade-a); }
[data-grade="B"], [data-letter="B"], [data-reliability="B"] { --grade-colour: var(--grade-b); }
[data-grade="C"], [data-letter="C"], [data-reliability="C"] { --grade-colour: var(--grade-c); }
[data-grade="D"], [data-letter="D"], [data-reliability="D"] { --grade-colour: var(--grade-d); }
[data-grade="E"], [data-letter="E"], [data-reliability="E"] { --grade-colour: var(--grade-e); }
[data-grade="F"], [data-letter="F"], [data-reliability="F"] { --grade-colour: var(--grade-f); }

[data-grade="1"], [data-number="1"], [data-credibility="1"] { --grade-colour: var(--credibility-1); }
[data-grade="2"], [data-number="2"], [data-credibility="2"] { --grade-colour: var(--credibility-2); }
[data-grade="3"], [data-number="3"], [data-credibility="3"] { --grade-colour: var(--credibility-3); }
[data-grade="4"], [data-number="4"], [data-credibility="4"] { --grade-colour: var(--credibility-4); }
[data-grade="5"], [data-number="5"], [data-credibility="5"] { --grade-colour: var(--credibility-5); }
[data-grade="6"], [data-number="6"], [data-credibility="6"] { --grade-colour: var(--credibility-6); }

/*
 * The combined code, as two coloured halves.
 *
 * The letter and the number are decided by different people about different
 * things (ADR-016), and one badge in one colour would average them into a
 * judgement neither of them made. A B1 - an ordinary source whose claim is
 * corroborated - should look like what it is.
 */
.attribution-grade {
    display: inline-flex;
    overflow: hidden;
    gap: 0;
    padding: 0;
    background: transparent;
}
.attribution-grade .grade-part {
    padding: 1px 4px;
    font-weight: 700;
    font-size: 10.5px;
    letter-spacing: 0.04em;
    background: var(--grade-colour, var(--panel-border));
    /* The greens and yellows want dark text; the deep reds want light. One
       rule cannot serve both, so the reds override below. */
    color: #0b1016;
}
.attribution-grade .grade-part[data-letter="E"],
.attribution-grade .grade-part[data-letter="F"],
.attribution-grade .grade-part[data-number="3"],
.attribution-grade .grade-part[data-number="4"],
.attribution-grade .grade-part[data-number="5"],
.attribution-grade .grade-part[data-number="6"] { color: #fff2f0; }
.attribution-grade .grade-part:first-child { border-radius: 2px 0 0 2px; }
.attribution-grade .grade-part:last-child { border-radius: 0 2px 2px 0; }

/* Ungraded stays outside the scale: no colour claims nothing. */
.attribution-grade.ungraded {
    display: inline-block;
    padding: 1px 5px;
    background: transparent;
    border: 1px solid var(--panel-border);
    color: var(--ink-faint);
    font-weight: 600;
    font-size: 10.5px;
    border-radius: 2px;
}

/*
 * Deselect sits with the stepper but is not one of its arrows: it is a word
 * rather than a direction, and giving it the same square as the arrows would
 * invite it being pressed for one.
 */
#cursor-deselect,
#follow-readout-stop {
    width: auto;
    padding: 0 7px;
    font-size: 10px;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    /*
     * The top bar clips its readouts to share the width, which truncated the
     * word to "DESELE". A button whose label is cut in half is a button
     * somebody has to guess at, so these keep their size and the value beside
     * them gives up the room instead.
     */
    flex: none;
    white-space: nowrap;
}

/* The reference for a published field of regard, beside the shape it draws. */
.profile-citation {
    margin: 4px 0 0;
    font-size: 11px;
    line-height: 1.45;
    color: var(--ink-dim);
    border-left: 2px solid var(--grade-a);
    padding-left: 7px;
}

/* A sweep whose window the clock has left. Warning-coloured, one sentence. */
.area-window-note b { color: var(--accent-warm); }

/* ------------------------------------------------------------- welcome */

/*
 * The greeting at sign-in.
 *
 * Modal on purpose. Everything else on this page is dismissible furniture the
 * eye learns to skip, and somebody meeting a globe covered in dots for the
 * first time needs one thing that is unmistakably the way in.
 */
.welcome {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 10vh 16px 16px;
    background: rgba(4, 7, 12, 0.74);
    backdrop-filter: blur(3px);
    overflow-y: auto;
}

/* Hidden must win over the flex above, whatever the selector order. */
.welcome[hidden] { display: none; }

.welcome-panel {
    position: relative;
    width: min(30rem, 100%);
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-top: 3px solid var(--accent);
    border-radius: 8px;
    padding: 22px 24px 22px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.welcome-panel .close-button {
    position: absolute;
    top: 8px;
    right: 10px;
    float: none;
    margin: 0;
}

.welcome-eyebrow {
    margin: 0;
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-faint);
}

.welcome-panel h2 {
    margin: -8px 0 0;
    font-size: 26px;
    font-weight: 600;
    letter-spacing: 0.01em;
    padding-right: 28px;
}

.welcome-sub { margin: -8px 0 0; font-size: 13px; color: var(--accent); }

#welcome-lead { margin: 0; font-size: 13.5px; line-height: 1.6; color: var(--ink); }

.welcome-points {
    margin: 0;
    padding-left: 1.1em;
    display: flex;
    flex-direction: column;
    gap: 5px;
    font-size: 13px;
    color: var(--ink-dim);
}

.welcome-points b { color: var(--ink); font-weight: 600; }

.welcome-again { margin-top: 2px; }

/* The way in is the wide one; the way past is beside it, not hidden. */
.welcome .button-row { margin-top: 4px; }
.welcome .button-row .primary-button { text-decoration: none; text-align: center; }
