/* ---------------------------------------------------------------------------
   jcarr.dev – instrument panel styling.

   Grounded in real monitoring tooling: dense, aligned, unornamented. Motion is
   reserved for state changes that actually happened; the only deliberate
   animation on the site is the Duty Log's new-entry settle.
   --------------------------------------------------------------------------- */

/* Reset. Previously inherited from the component library; now stated here, because relying on a
   dependency for it means padding silently starts adding to declared widths the day it is removed. */
*,
*::before,
*::after {
    box-sizing: border-box;
}

:root {
    --base: #0B0D0C;
    --surface: #161917;
    --text: #E8E4D8;
    --amber: #FFB000;
    --green: #3A9B6E;
    --red: #C4483C;
    --hairline: #2A2E2B;
    --muted: #8A8F88;

    --grid-unit: 4px;
    --pad-tight: 8px;
    --pad: 12px;
}

html, body {
    background: var(--base);
    color: var(--text);
    font-family: "IBM Plex Sans", system-ui, sans-serif;
    font-size: 14px;
    line-height: 1.5;
    margin: 0;
    -webkit-font-smoothing: antialiased;
}

/* Every data readout is mono with aligned digits – the whole point of the face here
   is to signal "this is a live measurement", so numerals must not jitter as they update. */
.readout, .mono {
    font-family: "IBM Plex Mono", ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

a { color: var(--text); text-decoration: none; border-bottom: 1px solid var(--hairline); }
a:hover { border-bottom-color: var(--amber); }

/* ---- Shell ---------------------------------------------------------------- */

.shell {
    max-width: 1240px;
    margin: 0 auto;
    padding: 0 16px 64px;
}

.masthead {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    padding: 16px 0;
    border-bottom: 1px solid var(--hairline);
    flex-wrap: wrap;
}

.masthead .wordmark {
    font-family: "IBM Plex Mono", monospace;
    font-size: 15px;
    letter-spacing: 0.06em;
    border: 0;
}

.masthead nav {
    display: flex;
    gap: 20px;
    font-family: "IBM Plex Mono", monospace;
    font-size: 12px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.masthead nav a { border: 0; color: var(--muted); padding-bottom: 2px; }
.masthead nav a:hover, .masthead nav a.active { color: var(--text); border-bottom: 1px solid var(--amber); }

.foot {
    margin-top: 40px;
    padding-top: 12px;
    border-top: 1px solid var(--hairline);
    color: var(--muted);
    font-size: 12px;
    display: flex;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

/* ---- Panels --------------------------------------------------------------- */

.grid {
    display: grid;
    gap: 12px;
    grid-template-columns: repeat(12, minmax(0, 1fr));
}

.col-3 { grid-column: span 3; }
.col-4 { grid-column: span 4; }
.col-5 { grid-column: span 5; }
.col-6 { grid-column: span 6; }
.col-7 { grid-column: span 7; }
.col-8 { grid-column: span 8; }
.col-12 { grid-column: span 12; }

@media (max-width: 960px) {
    .col-3, .col-4, .col-5, .col-6, .col-7, .col-8 { grid-column: span 12; }
}

.panel {
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: 2px;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: var(--pad-tight) var(--pad);
    border-bottom: 1px solid var(--hairline);
    font-family: "IBM Plex Mono", monospace;
    font-size: 11px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
}

.panel-body { padding: var(--pad); }
.panel-body.flush { padding: 0; }

/* ---- Metrics -------------------------------------------------------------- */

.metric-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 6px 0;
    border-bottom: 1px solid var(--hairline);
}

.metric-row:last-child { border-bottom: 0; }

.metric-label {
    font-family: "IBM Plex Mono", monospace;
    font-size: 11px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
    white-space: nowrap;
}

.metric-value {
    font-family: "IBM Plex Mono", monospace;
    font-variant-numeric: tabular-nums;
    font-size: 13px;
    text-align: right;
    white-space: nowrap;
}

.metric-value.large { font-size: 22px; letter-spacing: -0.01em; }
.metric-value .unit { color: var(--muted); font-size: 0.75em; margin-left: 2px; }

.unavailable { color: var(--muted); font-style: normal; }

/* The reason a reading is missing, set smaller than the value it stands in for – present for
   diagnosis, not competing with the numbers that are real. */
.unavailable .reason {
    font-size: 10px;
    letter-spacing: 0.08em;
    margin-left: 6px;
    opacity: 0.75;
}

/* A bar reflects a real value or it does not exist. No animated fill. */
.bar {
    height: 4px;
    background: #0B0D0C;
    border: 1px solid var(--hairline);
    margin-top: 6px;
    position: relative;
}

.bar > span { display: block; height: 100%; background: var(--muted); }
.bar > span.warn { background: var(--amber); }
.bar > span.crit { background: var(--red); }

/* Latency history. Each bar is one measured round trip; nothing here animates or fills for
   effect – a bar appears when a probe returns and its height is that probe's value. */
.spark {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 34px;
    margin-top: 8px;
    padding: 2px 0;
    border-bottom: 1px solid var(--hairline);
}

.spark > span {
    flex: 1;
    min-width: 2px;
    background: var(--muted);
}

.spark > span:last-child { background: var(--amber); }

/* Marks a value as configuration rather than measurement. The distinction is the whole point of
   the label, so it is set apart from the metric name rather than blended into it. */
.declared {
    font-size: 9px;
    letter-spacing: 0.1em;
    color: var(--amber);
    opacity: 0.75;
    margin-left: 6px;
    border: 1px solid var(--hairline);
    padding: 0 4px;
}

/* ---- Status markers: small squares, never glowing dots --------------------- */

.marker {
    display: inline-block;
    width: 7px;
    height: 7px;
    margin-right: 7px;
    vertical-align: baseline;
    background: var(--muted);
}

.marker.ok { background: var(--green); }
.marker.warn { background: var(--amber); }
.marker.bad { background: var(--red); }
.marker.unknown { background: transparent; border: 1px solid var(--muted); }
.marker.diamond { transform: rotate(45deg); width: 6px; height: 6px; }

.state-ok { color: var(--green); }
.state-warn { color: var(--amber); }
.state-bad { color: var(--red); }
.state-unknown { color: var(--muted); }

/* ---- Duty Log: the signature element --------------------------------------- */

.dutylog {
    font-family: "IBM Plex Mono", monospace;
    font-variant-numeric: tabular-nums;
    font-size: 12px;
    line-height: 1.6;
    max-height: 420px;
    overflow-y: auto;
}

.dutylog .entry {
    display: grid;
    grid-template-columns: 68px 92px 1fr;
    gap: 10px;
    padding: 5px var(--pad);
    border-bottom: 1px solid var(--hairline);
    align-items: baseline;
}

.dutylog .entry:last-child { border-bottom: 0; }

/* Handover entries carry a full date rather than a time, so they need a wider first column. */
.dutylog .entry.handover { grid-template-columns: 150px 96px 1fr; }
.dutylog .ts { color: var(--muted); }
.dutylog .tag {
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
    /* An unexpectedly long tag truncates rather than overlapping the column beside it. */
    overflow: hidden;
    text-overflow: ellipsis;
}
.dutylog .body { min-width: 0; overflow-wrap: anywhere; }
.dutylog .body .dim { color: var(--muted); }

/* The one deliberate motion moment: a new entry lands amber and settles over ~4s.
   It is a state signal, not decoration – a line that arrived while you were looking
   elsewhere is still identifiable as new. */
@keyframes settle {
    0%   { color: var(--amber); background: rgba(255, 176, 0, 0.06); }
    55%  { color: var(--amber); background: rgba(255, 176, 0, 0.04); }
    100% { color: var(--text); background: transparent; }
}

.dutylog .entry.new { animation: settle 4s ease-out forwards; }

@media (prefers-reduced-motion: reduce) {
    .dutylog .entry.new { animation: none; }
}

/* ---- Tables ---------------------------------------------------------------- */

table.data {
    width: 100%;
    border-collapse: collapse;
    font-family: "IBM Plex Mono", monospace;
    font-variant-numeric: tabular-nums;
    font-size: 12px;
}

table.data th {
    text-align: left;
    font-weight: 400;
    color: var(--muted);
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    padding: 6px var(--pad);
    border-bottom: 1px solid var(--hairline);
}

table.data td {
    padding: 5px var(--pad);
    border-bottom: 1px solid var(--hairline);
    white-space: nowrap;
}

table.data tr:last-child td { border-bottom: 0; }
table.data .num { text-align: right; }

/* Long values (ASN names) truncate rather than pushing the columns that matter off the panel. */
table.data .ellipsis {
    max-width: 150px;
    overflow: hidden;
    text-overflow: ellipsis;
}
table.data .wrap { white-space: normal; overflow-wrap: anywhere; }

.scroll-x { overflow-x: auto; }

/* ---- Prose ----------------------------------------------------------------- */

h1, h2, h3 { font-weight: 600; letter-spacing: -0.01em; margin: 0; }

.lede {
    font-size: 19px;
    line-height: 1.55;
    max-width: 62ch;
    margin: 0;
}

.prose { max-width: 68ch; }
.prose p { margin: 0 0 12px; }

.eyebrow {
    font-family: "IBM Plex Mono", monospace;
    font-size: 11px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
}

.hero { padding: 48px 0 32px; }
.hero h1 { font-size: clamp(28px, 4vw, 42px); line-height: 1.15; margin-bottom: 18px; max-width: 20ch; }

.section { margin-top: 40px; }
.section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    padding-bottom: 8px;
    margin-bottom: 12px;
    border-bottom: 1px solid var(--hairline);
    flex-wrap: wrap;
}

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

/* Hour-of-day activity profile. Each bar is a measured mean; nothing fills for effect. */
.profile {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 56px;
    border-bottom: 1px solid var(--hairline);
}

.profile > span {
    flex: 1;
    min-width: 2px;
    background: var(--muted);
    opacity: 0.65;
}

.profile > span.now { background: var(--amber); opacity: 1; }

.profile-axis {
    display: flex;
    justify-content: space-between;
    font-family: "IBM Plex Mono", monospace;
    font-size: 9px;
    color: var(--muted);
    margin-top: 3px;
}

/* Fused rows carry a source column ahead of the tag. */
.dutylog .entry.fused { grid-template-columns: 150px 60px 92px 1fr; }

.dutylog .src {
    font-size: 9px;
    letter-spacing: 0.08em;
    padding: 1px 4px;
    border: 1px solid var(--hairline);
    text-align: center;
    white-space: nowrap;
}

.dutylog .src-worker { color: var(--amber); }
.dutylog .src-edge { color: var(--text); }
.dutylog .src-operator { color: var(--muted); }

button.op.on { border-color: var(--amber); color: var(--amber); }

@media (max-width: 700px) {
    .dutylog .entry.fused { grid-template-columns: auto auto 1fr; }
}

/* ---- Evidence grading -------------------------------------------------------
   Facts and judgements are visually distinct, because the failure mode this
   guards against is a judgement being read as a fact.
   ---------------------------------------------------------------------------- */

.grade {
    display: inline-block;
    font-family: "IBM Plex Mono", monospace;
    font-size: 9px;
    letter-spacing: 0.1em;
    padding: 0 4px;
    margin-right: 6px;
    border: 1px solid var(--hairline);
    vertical-align: baseline;
    white-space: nowrap;
}

.grade-observed { color: var(--text); }
.grade-derived { color: var(--muted); }
.grade-inferred { color: var(--amber); border-color: rgba(255, 176, 0, 0.35); }
.grade-confirmed { color: var(--green); border-color: rgba(58, 155, 110, 0.35); }

.finding {
    display: flex;
    gap: 4px;
    align-items: baseline;
    padding: 6px 0;
    border-bottom: 1px solid var(--hairline);
    font-size: 13px;
    line-height: 1.5;
}

.finding:last-child { border-bottom: 0; }

/* ---- Explained terms --------------------------------------------------------
   Opens on hover, focus and tap. No JavaScript involved, so it works with scripts
   blocked and costs nothing to load.
   ---------------------------------------------------------------------------- */

.term {
    position: relative;
    cursor: help;
    outline: none;
}

/* A quiet marker rather than a dotted underline, which reads as a spelling error
   at this font size. */
.term-marker {
    font-size: 0.75em;
    color: var(--amber);
    opacity: 0.6;
    margin-left: 3px;
    vertical-align: super;
}

.term:hover .term-marker,
.term:focus .term-marker { opacity: 1; }

.term-pop {
    position: absolute;
    left: 0;
    bottom: calc(100% + 8px);
    z-index: 40;
    display: none;
    width: max-content;
    max-width: min(340px, 72vw);
    padding: 10px 12px;
    background: var(--base);
    border: 1px solid var(--hairline);
    border-left: 2px solid var(--amber);
    border-radius: 2px;
    font-family: "IBM Plex Sans", system-ui, sans-serif;
    font-size: 12.5px;
    line-height: 1.5;
    letter-spacing: 0;
    text-transform: none;
    text-align: left;
    white-space: normal;
    color: var(--text);
}

.term:hover .term-pop,
.term:focus .term-pop,
.term:focus-within .term-pop { display: block; }

.term-pop-title {
    display: block;
    font-family: "IBM Plex Mono", monospace;
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--amber);
    margin-bottom: 5px;
}

.term-pop-body { display: block; color: var(--text); }

/* Near the right-hand edge the popover would overflow the panel, so anchor it to
   the right instead. */
.metric-value .term-pop,
.term.right .term-pop { left: auto; right: 0; }

@media (max-width: 700px) {
    .term-pop { max-width: 78vw; }
}

/* ---- Narrow screens --------------------------------------------------------
   Plenty of people will open this on a phone. The panels already stack below 960px;
   these rules stop the dense readouts from being the thing that breaks.
   ---------------------------------------------------------------------------- */

@media (max-width: 700px) {
    .shell { padding: 0 12px 48px; }

    .masthead { gap: 10px; }
    .masthead nav { gap: 14px; font-size: 11px; }

    .hero { padding: 32px 0 24px; }
    .hero h1 { max-width: none; }
    .lede { font-size: 17px; }

    /* Log lines drop the body onto its own row rather than squeezing it into a
       third of the width. Timestamp and tag stay together on the line above. */
    .dutylog .entry,
    .dutylog .entry.handover {
        grid-template-columns: auto 1fr;
        row-gap: 2px;
    }

    .dutylog .body { grid-column: 1 / -1; }

    /* A long label and a long value stop fighting for the same line. */
    .metric-row { flex-wrap: wrap; row-gap: 2px; }
    .metric-label { white-space: normal; }
    .metric-value { text-align: left; }
    .metric-value.large { font-size: 20px; }

    .section-head { gap: 6px; }
    .foot { flex-direction: column; gap: 6px; }
}

/* ---- Controls -------------------------------------------------------------- */

button.op {
    font-family: "IBM Plex Mono", monospace;
    font-size: 11px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text);
    background: transparent;
    border: 1px solid var(--hairline);
    border-radius: 2px;
    padding: 7px 14px;
    cursor: pointer;
}

button.op:hover:not(:disabled) { border-color: var(--amber); color: var(--amber); }
button.op:disabled { color: var(--muted); cursor: not-allowed; }

input.op, select.op, textarea.op {
    font-family: "IBM Plex Mono", monospace;
    font-size: 12px;
    color: var(--text);
    background: var(--base);
    border: 1px solid var(--hairline);
    border-radius: 2px;
    padding: 6px 8px;
    width: 100%;
}

input.op:focus, select.op:focus, textarea.op:focus { outline: none; border-color: var(--amber); }

/* Blazor's default error bar, restyled to match. */
#blazor-error-ui {
    background: var(--red);
    color: var(--text);
    bottom: 0;
    display: none;
    left: 0;
    padding: 8px 12px;
    position: fixed;
    width: 100%;
    z-index: 1000;
    font-family: "IBM Plex Mono", monospace;
    font-size: 12px;
}

#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 12px; top: 8px; }

/* ---- Origin plot -----------------------------------------------------------------------------
   An instrument, not an infographic. The grid is a coordinate reference so a bare marker can be
   read as a position; without it the dots would be abstract. Nothing here animates on a loop –
   a marker exists because a request arrived. */
.origin-map {
    width: 100%;
    height: auto;
    display: block;
    margin: 4px 0 10px;
    border: 1px solid var(--line);
    /* Coastlines are a static file: 60kB of unchanging path data belongs in the browser cache
       rather than in every render sent down the circuit. */
    background-color: #0d0f11;
    background-image: url('/world.svg');
    background-size: 100% 100%;
    background-repeat: no-repeat;
}

.origin-map .graticule {
    stroke: var(--line);
    stroke-width: 0.1;
    opacity: 0.28;
}

.origin-map .equator {
    stroke-width: 0.22;
    opacity: 0.8;
}

.origin-map .origin-dot {
    fill-opacity: 0.9;
    stroke-width: 0.4;
    stroke-opacity: 0.3;
}

.origin-map .origin-halo {
    fill: none;
    stroke-width: 0.16;
    stroke-opacity: 0.3;
}

.origin-map .origin-label {
    font-family: var(--mono);
    font-size: 2.1px;
    fill: var(--text-dim);
    letter-spacing: 0.04em;
}

/* ---- Hero with the origin plot alongside -----------------------------------------------------
   Two columns on a wide screen, stacked on a narrow one. The copy leads in both cases: on a phone
   the claim is read before the evidence, which is the order the argument needs. */
.hero-split {
    display: flex;
    gap: 32px;
    align-items: flex-start;
}

.hero-split .hero-copy { flex: 1 1 52%; min-width: 0; }
.hero-split .hero-aside { flex: 1 1 44%; min-width: 0; }

@media (max-width: 900px) {
    .hero-split { flex-direction: column; gap: 20px; }
    .hero-split .hero-copy,
    .hero-split .hero-aside { flex: 1 1 auto; width: 100%; }
}

/* ---- Origin plot: motion caused by real events ------------------------------------------------
   Nothing here loops. Every animation below runs once, triggered by a detection actually arriving,
   and then the element is removed. With no traffic the panel is completely still – which is itself
   information, and the opposite of a threat map that churns whether or not anything happened. */

/* The arc from a source to this host. Drawn on rather than faded in: stroke-dashoffset animates the
   line into existence from the source end, so the eye is led in the direction the request came. */
.origin-map .origin-arc {
    fill: none;
    stroke: var(--amber);
    stroke-width: 0.28;
    stroke-linecap: round;
    stroke-dasharray: 120;
    stroke-dashoffset: 120;
    opacity: 0;
    animation: arc-draw 4s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}

@keyframes arc-draw {
    0%   { stroke-dashoffset: 120; opacity: 0; }
    12%  { opacity: 0.85; }
    55%  { stroke-dashoffset: 0;   opacity: 0.85; }
    100% { stroke-dashoffset: 0;   opacity: 0; }
}

/* The expanding ring at the source. One pulse per detection, then gone. */
.origin-map .origin-ping {
    fill: none;
    stroke: var(--amber);
    stroke-width: 0.4;
    r: 0.5;
    opacity: 0;
    animation: ping-out 4s ease-out forwards;
    transform-box: fill-box;
    transform-origin: center;
}

@keyframes ping-out {
    0%   { r: 0.5; opacity: 0; stroke-width: 0.5; }
    8%   { opacity: 0.9; }
    100% { r: 7;   opacity: 0; stroke-width: 0.1; }
}

/* This host. The only declared position on the plot, so it is drawn differently from the sources
   it is being contacted by – a different colour, and a ring that marks it as the destination. */
.origin-map .origin-host {
    fill: var(--green, #6ee7a8);
    fill-opacity: 0.95;
}

.origin-map .origin-host-ring {
    fill: none;
    stroke: var(--green, #6ee7a8);
    stroke-width: 0.22;
    stroke-opacity: 0.5;
}

/* Respect the accessibility preference: someone who has asked for reduced motion gets the marker
   and the arc, without either animating in. The information is identical. */
@media (prefers-reduced-motion: reduce) {
    .origin-map .origin-arc {
        animation: none;
        stroke-dashoffset: 0;
        opacity: 0.5;
    }
    .origin-map .origin-ping { animation: none; opacity: 0; }
}

/* ---- Origin plot key --------------------------------------------------------------------------
   Country codes were drawn on the plot itself and overlapped badly across Europe, where most
   scanning infrastructure is rented: four markers within a few pixels cannot carry four labels.
   The map now gives position, this gives the numbers, and the shared colour ties them together. */
.origin-key {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
    margin: 2px 0 10px;
    font-family: var(--mono);
    font-size: 11px;
    line-height: 1.6;
}

.origin-key-item {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    white-space: nowrap;
}

.origin-key-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    align-self: center;
    flex: 0 0 auto;
}

.origin-key-code { color: var(--text); letter-spacing: 0.04em; }
.origin-key-name { color: var(--text-dim); }
.origin-key-count { color: var(--text); font-variant-numeric: tabular-nums; }

/* ---- Row alignment ----------------------------------------------------------------------------
   Panels sitting side by side were up to 300px different in height, so every row ended on a jagged
   edge. The content was fine; the row simply had nothing telling it to line up.

   Grid tracks already stretch by default – the panels were not filling them because a grid item
   containing a flex column still sizes to its content unless told otherwise. Stretching the panel
   to the track and letting its body take the slack gives every row a straight bottom edge without
   touching a single word of what is inside it. */
.grid > [class*="col-"] {
    display: flex;
    min-width: 0;
}

.grid > [class*="col-"] > .panel {
    flex: 1 1 auto;
    width: 100%;
}

/* The body absorbs the extra height, so panel headers stay aligned across a row and short panels
   gain space at the bottom rather than stretching their rows apart. */
.panel > .panel-body {
    flex: 1 1 auto;
}

/* ---- Mobile navigation ------------------------------------------------------------------------
   Seven links wrapped onto three lines on a phone and pushed the page content below the fold. A
   checkbox and a label do the whole job: no JavaScript, nothing for the CSP to permit, and the
   links remain in the markup at every width so navigation never depends on script running.

   The toggle exists only below the breakpoint; on a wide screen it is hidden and the nav is a row,
   exactly as before. */
.nav-button { display: none; }

@media (max-width: 720px) {
    .masthead {
        align-items: center;
        gap: 8px;
        position: relative;
    }

    .nav-button {
        display: flex;
        flex-direction: column;
        justify-content: center;
        gap: 4px;
        /* 44px: the smallest target that is reliably hittable with a thumb. */
        width: 44px;
        height: 44px;
        margin-left: auto;
        cursor: pointer;
        padding: 10px;
    }

    .nav-bar {
        display: block;
        height: 1.5px;
        width: 100%;
        background: var(--text);
        transition: transform 0.18s ease, opacity 0.18s ease;
    }

    /* Collapses to a cross when open, so the control says what it will do next. */
    .nav-toggle:checked ~ .nav-button .nav-bar:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
    .nav-toggle:checked ~ .nav-button .nav-bar:nth-child(2) { opacity: 0; }
    .nav-toggle:checked ~ .nav-button .nav-bar:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

    .masthead nav {
        display: none;
        flex-direction: column;
        gap: 0;
        width: 100%;
        order: 3;
    }

    .nav-toggle:checked ~ nav { display: flex; }

    .masthead nav a {
        padding: 12px 2px;
        border-bottom: 1px solid var(--hairline);
        font-size: 13px;
    }

    .masthead nav a:last-child { border-bottom: 0; }

    .masthead nav a:hover,
    .masthead nav a.active {
        border-bottom: 1px solid var(--amber);
        color: var(--text);
    }
}

/* Keyboard users must be able to see the control they have focused. */
.nav-toggle:focus-visible ~ .nav-button { outline: 1px solid var(--amber); outline-offset: 2px; }

/* ---- Section rhythm ---------------------------------------------------------------------------
   Seven sections all carried the same weight, so a long page gave no sense of where you were or
   what mattered. Nothing here changes a word: the gap BETWEEN sections is now clearly larger than
   the gaps within one, and each heading gets a rule above it. Grouping is what makes density
   scannable – the fix for a dense page is structure, not less information. */
.section {
    margin-top: 56px;
    padding-top: 28px;
    border-top: 1px solid var(--hairline);
}

.section:first-of-type {
    margin-top: 32px;
    padding-top: 0;
    border-top: 0;
}

.section-head {
    margin-bottom: 16px;
}

.section-head h2 {
    font-size: 19px;
    letter-spacing: -0.01em;
}

/* Introductory copy under a heading: readable measure, held away from the panels below it. */
.section > .lede {
    max-width: 68ch;
    margin-bottom: 20px;
}

/* ---- Dense caption blocks ---------------------------------------------------------------------
   Panel captions were doing three jobs in one grey paragraph: stating a figure, qualifying what it
   means, and disclosing what was excluded. The first sentence is the one everybody needs; the rest
   matters only to someone actually interrogating the number.

   <details> is native, needs no script, is keyboard accessible and is readable by screen readers
   when closed. The words are unchanged – they are just no longer all shouting at once. */
.note details {
    margin-top: 6px;
}

.note summary {
    cursor: pointer;
    color: var(--muted);
    list-style: none;
}

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

.note summary::before {
    content: "+ ";
    color: var(--amber);
    font-family: var(--mono);
}

details[open] > summary::before { content: "− "; }

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

.note details > *:not(summary) {
    margin-top: 6px;
    display: block;
}

/* ---- Campaign rows: expand in place -----------------------------------------------------------
   The row was a dead end – the reasoning lived only on another page. It now opens to reveal the
   graded findings, so the section shows the analysis rather than only naming a verdict. */
.campaigns .campaign-row { cursor: pointer; }

.campaigns .campaign-row:hover td { background: rgba(255, 255, 255, 0.02); }

.campaigns .campaign-row.open td { background: rgba(255, 255, 255, 0.03); }

.campaigns .chevron {
    text-align: right;
    font-family: var(--mono);
    color: var(--amber);
    width: 1.5em;
}

.campaigns .campaign-detail td {
    padding: 0 12px 12px;
    background: rgba(255, 255, 255, 0.03);
    /* Overrides table.data td, which is nowrap so the summary columns stay aligned. The findings
       are full sentences and must wrap, or they are forced onto one line and clipped by scroll-x. */
    white-space: normal;
}

.campaigns .findings {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px 0 4px;
    border-top: 1px solid var(--hairline);
}

.campaigns .finding {
    display: grid;
    grid-template-columns: 92px minmax(0, 1fr);
    gap: 12px;
    align-items: baseline;
    font-size: 12.5px;
    line-height: 1.5;
    white-space: normal;
    overflow-wrap: anywhere;
}

.campaigns .finding .grade {
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: 0.06em;
    text-align: right;
}

/* ---- Data tables on mobile --------------------------------------------------------------------
   A six-column table cannot fit a phone: the columns crush, every cell wraps onto three or four
   lines, and each row becomes a 150px block of stacked fragments — words breaking mid-character.
   Two treatments below.

   1. The campaign table becomes a card per source: identifier and expand toggle on the first line,
      the verdict on the second, compact stats on the third. It reads like a list, not a spreadsheet
      squeezed through a letterbox. */
@media (max-width: 720px) {
    .campaigns thead { display: none; }
    .campaigns,
    .campaigns tbody { display: block; width: 100%; }

    .campaigns tr.campaign-row {
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        gap: 3px 12px;
        padding: 11px 12px;
        border-bottom: 1px solid var(--hairline);
    }

    .campaigns tr.campaign-row > td { border: 0; padding: 0; white-space: nowrap; }

    .campaigns tr.campaign-row td:nth-child(1) { order: 1; flex: 1 1 auto; font-size: 13px; }
    .campaigns tr.campaign-row td.chevron { order: 2; margin-left: auto; width: auto; }
    .campaigns tr.campaign-row td.wrap {
        order: 3; flex-basis: 100%; white-space: normal; font-size: 12px; color: var(--text);
    }
    /* Stats line: country, requests, days, with the units the hidden header used to carry. */
    .campaigns tr.campaign-row td:nth-child(2) { order: 4; color: var(--text-dim); font-size: 11px; }
    .campaigns tr.campaign-row td:nth-child(3) { order: 5; color: var(--text-dim); font-size: 11px; }
    .campaigns tr.campaign-row td:nth-child(3)::after { content: " req"; }
    .campaigns tr.campaign-row td:nth-child(4) { order: 6; color: var(--text-dim); font-size: 11px; }
    .campaigns tr.campaign-row td:nth-child(4)::after { content: " day(s)"; }

    .campaigns .campaign-detail td { display: block; width: 100%; white-space: normal; }

    /* 2. Every other data table stays a table but scrolls as one unit rather than wrapping. Forcing
          the normally-wrapping columns onto a single line keeps each row one line high; the row is
          swiped, not stacked. A scroll cue sits under the panel so the gesture is discoverable. */
    .scroll-x { -webkit-overflow-scrolling: touch; }
    .scroll-x .data:not(.campaigns) td,
    .scroll-x .data:not(.campaigns) th { white-space: nowrap; }
    .scroll-x .data:not(.campaigns) .wrap { max-width: 60vw; }
}

/* ---- Private dashboard ------------------------------------------------------------------------
   The gate is deliberately bare: a single token box, centred in the viewport, nothing else on the
   page and no site chrome around it (it uses BlankLayout). Once through, .dash frames the content. */
.gate {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

.gate-box {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
}

.gate-title {
    margin: 0;
    font-family: var(--mono);
    font-size: 13px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
}

.gate-input {
    width: min(300px, 90vw);
    text-align: center;
    letter-spacing: 0.18em;
}

.dash {
    max-width: 1100px;
    margin: 0 auto;
    padding: 28px 16px 64px;
}

/* The select-and-delete strip above the questions table. */
.dash-toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 8px 12px;
    border-bottom: 1px solid var(--hairline);
    min-height: 34px;
}
.dash-toolbar .op { padding: 4px 12px; }

/* A destructive action reads red rather than amber, so a delete never looks like a routine button. */
button.op.danger { color: var(--red); border-color: var(--red); }
button.op.danger:hover:not(:disabled) { color: #fff; background: var(--red); border-color: var(--red); }

table.data tr.row-selected td { background: rgba(255, 176, 0, 0.06); }

/* Live preview of the streamed CV. A light card so it reads like the document it will become, rather
   than terminal text, and it scrolls within itself so a long CV does not run the panel off the page. */
.cv-preview {
    margin-top: 14px;
    max-height: 520px;
    overflow-y: auto;
    background: #fff;
    color: #16191c;
    border: 1px solid var(--hairline);
    border-radius: 2px;
    padding: 22px 26px;
}
.cv-preview h1 { font-size: 22px; margin: 0 0 2px; }
.cv-preview h2 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.06em; color: #444;
    border-bottom: 1px solid #e2e2e2; padding-bottom: 4px; margin: 18px 0 8px; }
.cv-preview h3 { font-size: 13px; margin: 12px 0 2px; }
.cv-preview p, .cv-preview li { font-size: 13px; line-height: 1.5; }
.cv-preview ul { margin: 6px 0 6px 18px; padding: 0; }
.cv-preview a { color: #16191c; }

/* ---- Ask terminal -----------------------------------------------------------------------------
   An honest AI panel: it says what it is, streams over the circuit, and carries its own guards.
   Styled to sit inside the site rather than cosplay a hacker terminal. */
.ask .ask-intro { margin: 0 0 12px; }

/* Centred, narrower placement that mimics a chat app: a comfortable column with the input as the
   focus, rather than a full-width bar stretched across the whole page. Reused on the home and about
   pages so the assistant reads the same wherever it appears. */
.ask-centered { max-width: 720px; margin: 0 auto; }
.ask-centered .ask-title { text-align: center; margin: 0 0 6px; }
.ask-centered .ask-sub { text-align: center; margin: 0 0 16px; }

.ask-log {
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-height: 340px;
    overflow-y: auto;
    padding: 4px 0;
}

.ask-turn {
    display: grid;
    grid-template-columns: 68px 1fr;
    gap: 10px;
    align-items: baseline;
    font-size: 13px;
    line-height: 1.55;
}

.ask-who {
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-dim);
    text-align: right;
}

.ask-turn.from-user .ask-text { color: var(--text); }
.ask-turn.from-ai .ask-text { color: var(--text-dim); }

.ask-thinking::after {
    content: "";
    animation: ask-dots 1.2s steps(4, end) infinite;
}
@keyframes ask-dots {
    0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; }
}

.ask-input {
    display: flex;
    gap: 8px;
    margin-top: 12px;
}

.ask-input input {
    flex: 1 1 auto;
    min-width: 0;
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid var(--line);
    color: var(--text);
    font-family: var(--mono);
    font-size: 13px;
    padding: 9px 11px;
    border-radius: 2px;
}
.ask-input input:focus { outline: 1px solid var(--amber); outline-offset: 0; border-color: var(--amber); }

.ask-input button {
    flex: 0 0 auto;
    background: var(--amber);
    color: #0d0f11;
    border: 0;
    font-family: var(--mono);
    font-size: 12px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 0 16px;
    border-radius: 2px;
    cursor: pointer;
}
.ask-input button:disabled { opacity: 0.5; cursor: default; }

@media (max-width: 560px) {
    .ask-turn { grid-template-columns: 1fr; gap: 2px; }
    .ask-who { text-align: left; }
}
