/* ComcoMonitor — dashboard.
   Dark theme, tiled grid. State is NEVER carried by colour alone: every colour comes with a
   dot and a word, so it stays readable for colour-blind eyes and in black and white. */

:root {
    --bg: #0e1116;
    --panel: #161b22;
    --panel-2: #1c232c;
    --line: #262d38;
    --text: #e6edf3;
    --muted: #8b949e;
    --ok: #3fb950;
    --warn: #d29922;
    --crit: #f85149;
    --unknown: #6e7681;
    --accent: #58a6ff;
    --radius: 10px;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.wrap { max-width: 1440px; margin: 0 auto; padding: 0 20px 48px; }

/* ---------- header ---------- */

.top {
    border-bottom: 1px solid var(--line);
    /* Translucent, so the blur below actually has something to blur. Opaque plus a
       backdrop-filter is a filter that costs a compositing layer and shows nothing. */
    background: linear-gradient(180deg, rgba(20,26,34,.93), rgba(16,21,28,.93));
    margin-bottom: 18px;
    /* Sticky on anything wider than a phone. The services page is nine thousand pixels tall
       and getting back to the board should not be a scroll to the top. On a phone the bar is
       two rows and would eat a tenth of the screen permanently, so there it scrolls away —
       see the media query at the bottom. */
    position: sticky;
    top: 0;
    z-index: 20;
    backdrop-filter: blur(8px);
}

.top-inner {
    max-width: 1440px;
    margin: 0 auto;
    padding: 10px 20px;
    display: flex;
    align-items: center;
    gap: 10px 18px;
    flex-wrap: wrap;
}

.brand {
    display: flex; align-items: center; gap: 9px;
    font-weight: 650; letter-spacing: .2px; font-size: 16px;
    color: var(--text); white-space: nowrap;
}
.brand:hover { text-decoration: none; color: var(--text); }
.brand-sub { color: var(--muted); font-weight: 400; font-size: 13px; }

/* A quiet pulse, so a glance tells you the page is live rather than a screenshot of one that
   died an hour ago. */
.brand-mark {
    width: 9px; height: 9px; border-radius: 50%;
    background: var(--ok);
    box-shadow: 0 0 0 3px rgba(63,185,80,.15);
    animation: brand-pulse 3.6s ease-in-out infinite;
}
@keyframes brand-pulse {
    0%, 100% { box-shadow: 0 0 0 3px rgba(63,185,80,.15); }
    50%      { box-shadow: 0 0 0 5px rgba(63,185,80,.05); }
}
@media (prefers-reduced-motion: reduce) {
    .brand-mark { animation: none; }
}

/* Tabs rather than a row of bare words: the subjects are the spine of this dashboard, and a
   link that looks clickable saves the hover-and-guess. Wraps, because the number of subjects
   grows and an unwrapped row takes the page sideways on a phone. */
.nav {
    display: flex; flex-wrap: wrap;
    gap: 4px;
    margin-left: auto;
    align-items: center;
    font-size: 14px;
}
.nav-link {
    color: var(--muted);
    padding: 6px 11px;
    border-radius: 7px;
    border: 1px solid transparent;
    line-height: 1.2;
    transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}
.nav-link:hover {
    color: var(--text);
    background: rgba(255,255,255,.05);
    text-decoration: none;
}
.nav-link.is-active {
    color: var(--text);
    background: var(--panel-2);
    border-color: var(--line);
    /* The accent line is what carries "you are here" when the background is too subtle to
       register — a border colour alone does not survive a bright room. */
    box-shadow: inset 0 -2px 0 0 var(--accent);
}

.nav-logout { margin: 0; display: flex; }
.nav-logout button {
    display: inline-flex; align-items: center; gap: 6px;
    background: none;
    border: 1px solid transparent;
    border-radius: 7px;
    padding: 6px 10px;
    cursor: pointer;
    font: 14px/1.2 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    color: var(--muted);
    transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}
.nav-logout button:hover {
    color: var(--crit);
    background: rgba(248,81,73,.08);
    border-color: rgba(248,81,73,.3);
}
.nav-logout svg { flex: none; }

/* ---------- sign-in ----------
   Its own page rather than the browser's Basic prompt: the dashboard is published on
   mnt.comco24.com, and a Basic box cannot say what the site is, cannot be signed out of
   without closing the browser, and is handled badly by password managers on phones. */

.login-body { display: flex; min-height: 100vh; }
.login {
    margin: auto;
    width: 100%;
    max-width: 380px;
    padding: 32px 20px;
    display: flex;
    flex-direction: column;
    gap: 18px;
}
.login-brand { font-size: 18px; font-weight: 650; letter-spacing: .2px; text-align: center; }
.login-brand span { color: var(--muted); font-weight: 400; }

.login-card {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 22px 20px 20px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.login-card h1 {
    margin: 0 0 10px;
    font-size: 13px; font-weight: 600; letter-spacing: .4px;
    text-transform: uppercase; color: var(--muted);
}
.login-card label { font-size: 12px; color: var(--muted); margin-top: 8px; }
.login-card input {
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 8px;
    color: var(--text);
    /* 16px exactly: anything smaller and iOS Safari zooms the page on focus, which leaves the
       form half off-screen with no obvious way back. */
    font: 16px/1.4 var(--mono);
    padding: 10px 12px;
    width: 100%;
}
.login-card input:focus { outline: none; border-color: var(--accent); }
.login-card button {
    margin-top: 16px;
    background: var(--accent);
    border: 0;
    border-radius: 8px;
    color: #0b1017;
    font: 600 15px/1 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    padding: 12px;
    cursor: pointer;
}
.login-card button:hover { filter: brightness(1.08); }

.login-error {
    margin: 0 0 4px;
    font-size: 13px;
    color: var(--crit);
    background: rgba(248,81,73,.08);
    border: 1px solid rgba(248,81,73,.3);
    border-radius: 8px;
    padding: 8px 10px;
}

.login-foot { margin: 0; text-align: center; font-size: 12px; color: var(--muted); }

.headline {
    display: flex;
    align-items: center;
    /* Wraps for the same reason the nav does: the range picker on the host page is pushed
       right by margin-left:auto, and on a phone an unwrapped row takes the page with it. */
    flex-wrap: wrap;
    gap: 8px 12px;
    font-size: 20px;
    font-weight: 600;
    padding: 2px 0 16px;
}
.headline .sub { font-size: 13px; font-weight: 400; color: var(--muted); }

/* ---------- state markers ---------- */

.dot {
    width: 9px; height: 9px; border-radius: 50%;
    display: inline-block; flex: none;
    background: var(--unknown);
}
.lv-ok      .dot, .dot.lv-ok      { background: var(--ok); }
.lv-warn    .dot, .dot.lv-warn    { background: var(--warn); }
.lv-crit    .dot, .dot.lv-crit    { background: var(--crit); }
.lv-unknown .dot, .dot.lv-unknown { background: var(--unknown); }

.pill {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 4px 10px; border-radius: 999px;
    border: 1px solid var(--line); background: var(--panel-2);
    font-size: 13px; color: var(--muted);
    /* A wrapped pill turns into a two-line blob with the dot stranded on the first line. */
    white-space: nowrap;
}
.pill.lv-ok      { color: var(--ok);      border-color: rgba(63,185,80,.35); }
.pill.lv-warn    { color: var(--warn);    border-color: rgba(210,153,34,.35); }
.pill.lv-crit    { color: var(--crit);    border-color: rgba(248,81,73,.4); }
.pill.lv-unknown { color: var(--unknown); }

/* ---------- subject tiles (the front page) ----------
   One tile per subject, ordered worst-first by the server. The whole border carries the state,
   which is what makes the board readable from across the room — but never on its own: every
   tile also has a dot and the state written out, for colour-blind eyes and for print. */

.tiles {
    display: grid;
    /* Two columns at most, deliberately. The ordering is the feature — worst on top, green at
       the bottom — and at three columns the third tile lands top-right, next to the red ones,
       which is exactly the reading the ordering exists to prevent.
       min() rather than a bare 560px: on a 390 px phone a fixed minimum is wider than the
       viewport, and the whole page scrolls sideways. */
    grid-template-columns: repeat(auto-fill, minmax(min(560px, 100%), 1fr));
    gap: 14px;
    align-items: start;
}

.tile {
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: var(--panel);
    border: 2px solid var(--unknown);
    border-radius: var(--radius);
    padding: 14px 16px 12px;
    color: var(--text);
}
.tile:hover { text-decoration: none; background: var(--panel-2); }

.tile.lv-ok      { border-color: rgba(63,185,80,.55); }
.tile.lv-warn    { border-color: var(--warn); background: linear-gradient(180deg, rgba(210,153,34,.06), transparent 60%); }
.tile.lv-crit    { border-color: var(--crit); background: linear-gradient(180deg, rgba(248,81,73,.08), transparent 60%); }
.tile.lv-unknown { border-color: var(--unknown); }

.tile-head { display: flex; align-items: center; gap: 9px; }
.tile-title { font-size: 17px; font-weight: 650; letter-spacing: .2px; }
.tile-state {
    margin-left: auto; font-size: 12px; letter-spacing: .3px;
    text-transform: uppercase; color: var(--muted);
}
.tile.lv-ok   .tile-state { color: var(--ok); }
.tile.lv-warn .tile-state { color: var(--warn); }
.tile.lv-crit .tile-state { color: var(--crit); }

.tile-sub { color: var(--muted); font-size: 12.5px; margin-top: -4px; }

.tile-counts { display: flex; flex-wrap: wrap; gap: 6px 18px; }
.tile-count { font-size: 12px; color: var(--muted); }
.tile-count b { font-family: var(--mono); font-size: 15px; font-weight: 600; color: var(--text); margin-right: 4px; }

/* The few sentences that explain the colour. Anything beyond MAX_PROBLEMS becomes "and N more"
   rather than being dropped — a truncated list that does not say it is truncated reads as
   "that is everything". */
.tile-problems { list-style: none; margin: 2px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.tile-problems li {
    font-size: 13px; line-height: 1.35;
    padding-left: 14px; position: relative;
}
.tile-problems li::before {
    content: "›"; position: absolute; left: 2px; color: var(--muted);
}
.tile.lv-crit .tile-problems li { color: #ffb3ae; }
.tile.lv-warn .tile-problems li { color: #f0cf88; }

.tile-good { font-size: 13px; color: var(--ok); }

.tile-foot {
    margin-top: auto; padding-top: 8px;
    border-top: 1px solid var(--line);
    font-size: 12px; color: var(--muted);
}
.tile:hover .tile-foot { color: var(--accent); }

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

.panel {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 16px;
    margin-bottom: 16px;
}
.panel h2 {
    margin: 0 0 12px;
    font-size: 13px; font-weight: 600; letter-spacing: .4px;
    text-transform: uppercase; color: var(--muted);
}

.grid {
    display: grid;
    /* 390px, not 310, and it is the type that sets it: a tile narrower than about 380 cuts
       `38,9 / 78,4 GB` to an ellipsis, and RAM is the widest value on every host. Measured
       rather than guessed — at 310 the cut appeared at 1024, 1100 and again from 1440 up,
       where a fourth tile fits and takes the width back. 1280 happened to be fine, which is
       exactly why this was easy to miss. */
    grid-template-columns: repeat(auto-fill, minmax(min(390px, 100%), 1fr));
    gap: 12px;
    /* Each tile keeps its own height — otherwise a host with five mounts stretches every
       other card in the row and the saved space comes back as blank space. */
    align-items: start;
}

/* ---------- host tile ----------
   Deliberately dense: the overview is a wall of hosts, so every extra pixel is paid for on
   every machine at once. Anything that needs room lives on the host detail page. */

.host {
    background: var(--panel);
    border: 1px solid var(--line);
    border-left: 3px solid var(--unknown);
    border-radius: var(--radius);
    padding: 9px 12px 8px;
    display: flex; flex-direction: column; gap: 4px;
}
.host.lv-ok      { border-left-color: var(--ok); }
.host.lv-warn    { border-left-color: var(--warn); }
.host.lv-crit    { border-left-color: var(--crit); }
.host.lv-unknown { border-left-color: var(--unknown); }

.host-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 2px; }
.host-name { font-size: 17px; font-weight: 640; letter-spacing: .3px; }
.host-name a { color: var(--text); }
.host-role { color: var(--muted); font-size: 13px; margin-left: auto; font-family: var(--mono); }

/* One metric line: label | value | peak | bar. Fixed columns keep the bars aligned across
   CPU, RAM and every mount, which is what makes the tile scannable at a glance. */
.stat {
    display: grid;
    /* minmax(0, 1fr) is load-bearing: with a plain 1fr the nowrap value cannot shrink below
       its content, the row grows past the tile and the bar spills outside the card. */
    /* The label column grew with the type: at 13px `/boot/efi` and `inotify` no longer fit in
       the old 68px and were being cut to an ellipsis. */
    grid-template-columns: 82px minmax(0, 1fr) auto 52px;
    align-items: center;
    gap: 6px;
    font-size: 14px;
    line-height: 1.45;
}
.stat .k {
    color: var(--muted); font-size: 13px; letter-spacing: .3px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stat .v {
    font-family: var(--mono); font-size: 14px; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis;
}
.stat .peak { color: var(--muted); font-family: var(--mono); font-size: 13px; white-space: nowrap; }

.disks {
    display: flex; flex-direction: column; gap: 2px;
    border-top: 1px solid var(--line);
    padding-top: 4px; margin-top: 2px;
}

.bar { height: 5px; border-radius: 3px; background: var(--panel-2); overflow: hidden; }
.bar i { display: block; height: 100%; background: var(--ok); border-radius: 3px; }
.bar.lv-warn i { background: var(--warn); }
.bar.lv-crit i { background: var(--crit); }

.spark { width: 100%; height: 38px; }
.spark polyline { fill: none; stroke: var(--ok); stroke-width: 1.6; vector-effect: non-scaling-stroke; }
.spark--warn polyline { stroke: var(--warn); }
.spark--crit polyline { stroke: var(--crit); }
.spark--unknown polyline { stroke: var(--unknown); }

.reasons { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.reason {
    font-size: 12px; color: var(--warn);
    background: rgba(210,153,34,.10); border: 1px solid rgba(210,153,34,.28);
    padding: 1px 7px; border-radius: 999px;
}
.host.lv-crit .reason { color: var(--crit); background: rgba(248,81,73,.10); border-color: rgba(248,81,73,.3); }

.foot {
    display: flex; gap: 12px; color: var(--muted); font-size: 12px;
    border-top: 1px solid var(--line); padding-top: 6px; margin-top: 4px;
}

/* Below the breakpoint the tile goes back to the dense type it had before. A phone shows one
   card at a time and the screen is the constraint; a desk shows a wall of them and legibility
   is. Same markup, different problem — so the sizes above are the desktop ones and these put
   them back, rather than the other way round. */
@media (max-width: 720px) {
    .host-name { font-size: 15px; }
    .host-role { font-size: 12px; }
    .stat {
        grid-template-columns: 68px minmax(0, 1fr) auto 52px;
        font-size: 12px;
        line-height: 1.35;
    }
    .stat .k    { font-size: 11px; }
    .stat .v    { font-size: 12px; }
    .stat .peak { font-size: 11px; }
    .reason     { font-size: 11px; }
    .foot       { font-size: 11px; }
}

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

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
    text-align: left; font-size: 12px; text-transform: uppercase; letter-spacing: .3px;
    color: var(--muted); font-weight: 600; padding: 0 10px 8px; border-bottom: 1px solid var(--line);
}
td { padding: 7px 10px; border-bottom: 1px solid rgba(38,45,56,.6); }
tr:last-child td { border-bottom: 0; }
td.num, th.num { text-align: right; font-family: var(--mono); white-space: nowrap; }
td.nowrap { white-space: nowrap; }
/* The table scrolls inside its own box; the page body never scrolls sideways. */
.table-scroll { overflow-x: auto; }

/* An explanatory paragraph under a panel heading. */
.note { margin-top: -4px; font-size: 13px; }

details > summary {
    cursor: pointer; list-style: none;
    font-size: 13px; font-weight: 600; letter-spacing: .4px;
    text-transform: uppercase; color: var(--muted);
}
details > summary::-webkit-details-marker { display: none; }
details > summary::before { content: "▸ "; }
details[open] > summary::before { content: "▾ "; }
details > summary:hover { color: var(--text); }

.muted { color: var(--muted); }
.mono { font-family: var(--mono); }
.empty { color: var(--muted); padding: 6px 2px; }

/* A row that is deliberately not watched — visible, but never competing for attention with
   the rows that are. */
tr.is-dimmed td { opacity: .5; }

.tag {
    font-size: 11px; text-transform: uppercase; letter-spacing: .3px;
    color: var(--muted); border: 1px solid var(--line); background: var(--panel-2);
    padding: 1px 6px; border-radius: 999px; margin-left: 6px; white-space: nowrap;
}
.tag--crit { color: var(--crit); border-color: rgba(248,81,73,.35); }

/* ---------- incidents ----------
   Something that has already been mailed to a human. It sits above the tiles, because whatever
   the board says, this is the thing somebody is currently dealing with. */

.incident-banner {
    display: block;
    border: 2px solid var(--crit);
    border-radius: var(--radius);
    background: linear-gradient(180deg, rgba(248,81,73,.10), transparent 70%);
    color: var(--text);
    padding: 12px 16px 10px;
    margin-bottom: 14px;
}
.incident-banner:hover { text-decoration: none; background: rgba(248,81,73,.13); }
.incident-banner-title { font-weight: 650; font-size: 15px; margin-left: 8px; }
.incident-banner ul { list-style: none; margin: 6px 0 0; padding: 0 0 0 17px; }
.incident-banner li { font-size: 13.5px; color: #ffb3ae; line-height: 1.4; position: relative; }
.incident-banner li::before { content: "›"; position: absolute; left: -13px; color: var(--muted); }

.incident {
    border: 1px solid var(--line);
    border-left: 3px solid var(--unknown);
    border-radius: 8px;
    background: var(--panel-2);
    padding: 11px 14px;
    margin-bottom: 10px;
}
.incident.lv-crit { border-left-color: var(--crit); }
.incident.lv-warn { border-left-color: var(--warn); }
.incident.lv-ok   { border-left-color: var(--ok); }
/* A closed incident is history, not news — readable, but not competing with the open ones. */
.incident.is-resolved { opacity: .62; }

.incident-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.incident-summary { font-size: 15px; font-weight: 600; flex: 1 1 auto; min-width: 0; }

.incident-meta {
    display: flex; flex-wrap: wrap; gap: 3px 14px;
    margin-top: 6px; font-size: 12px; color: var(--muted);
}
.incident-detail { margin: 8px 0 0; font-size: 13px; color: var(--text); }

/* What actually went to the broker. `alert_log` is this service's own side of the truth. */
.incident-alerts { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.alert-chip {
    font-size: 11px; letter-spacing: .2px;
    color: var(--muted); background: var(--panel);
    border: 1px solid var(--line); border-radius: 999px;
    padding: 2px 9px; white-space: nowrap;
}
.alert-chip.is-failed { color: var(--crit); border-color: rgba(248,81,73,.35); }

/* ---------- muting ----------
   A muted finding keeps the colour it earned and gains the reason it was accepted. It is
   dimmed, never hidden: the tile above went green because of this decision, so the decision has
   to stay somewhere a person can find it and argue with it. */

.flash {
    margin: 0 0 14px;
    padding: 9px 12px;
    border-radius: 8px;
    font-size: 13.5px;
    border: 1px solid var(--line);
    background: var(--panel);
}
.flash--ok    { color: var(--ok);   border-color: rgba(63,185,80,.35);  background: rgba(63,185,80,.07); }
.flash--error { color: var(--crit); border-color: rgba(248,81,73,.35); background: rgba(248,81,73,.07); }

.panel--muted { border-style: dashed; }

.svc.is-muted, tr.is-muted td { opacity: .72; }
.svc.is-muted { border-left-style: dashed; }

/* The control is a disclosure so the reason field only appears when it is wanted; the page has
   no JavaScript build step and this needs none. */
.mute { margin-left: auto; }
/* In a table cell there is no flex row to be pushed to the end of, and a block-level <details>
   would stretch the pill across the whole column. */
td .mute { display: inline-block; margin-left: 8px; vertical-align: middle; }
td .mute-form { margin-top: 6px; }
.mute > summary {
    cursor: pointer; list-style: none;
    font-size: 11px; letter-spacing: .3px; text-transform: uppercase;
    color: var(--muted);
    border: 1px solid var(--line); border-radius: 999px;
    padding: 2px 9px;
    white-space: nowrap;
    /* Shrink to the word. A block-level summary takes the width of the open <details>, which is
       the width of the reason field — so opening the control stretched the pill into a bar. */
    display: inline-block;
}
.mute > summary::-webkit-details-marker { display: none; }
.mute > summary::before { content: none; }
.mute > summary:hover { color: var(--text); border-color: var(--muted); }

.mute-form {
    display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
    margin-top: 8px;
    /* Breaks out of the row it sits in, so the field gets the width of the card rather than
       the sliver left over next to the service name. */
    flex-basis: 100%;
}
/* Longhand, not the `font` shorthand: `font: 16px/1.4 inherit` is invalid CSS — the shorthand
   needs a real family and drops the whole declaration if it gets `inherit` — which silently
   left this field at the browser default and made iOS zoom on focus. */
.mute-form input[type=text] {
    flex: 1 1 200px; min-width: 0;
    background: var(--bg); border: 1px solid var(--line); border-radius: 6px;
    color: var(--text); padding: 6px 9px;
    font-family: inherit;
    /* 16px everywhere, not just on phones: iPads zoom on focus below that too, and a reason
       somebody has to type is the last field to make small. */
    font-size: 16px;
    line-height: 1.4;
}
.mute-form input[type=text]:focus { outline: none; border-color: var(--accent); }
.mute-form button {
    background: var(--panel-2); border: 1px solid var(--line); border-radius: 6px;
    color: var(--text); padding: 6px 12px; cursor: pointer;
    font-family: inherit; font-size: 13px; line-height: 1.2;
}
.mute-form button:hover { border-color: var(--accent); color: var(--accent); }

/* Deleting for good sits next to unmute and does something unmute cannot be taken back from, so
   it is the one control on this page drawn in the danger colour — closed as well as open. It is
   never the loud element in a card: a row worth deleting is a row nobody has to look at again. */
/* It follows whatever control it sits beside instead of being pushed to the far edge: a second
   `margin-left: auto` in the row left it stranded on a line of its own on the narrower cards. */
.forget { margin-left: 0; }
/* Opened, it takes a line of its own. Otherwise the confirmation button widens the control
   mid-row and pushes the pills either side of it onto different lines — the card rearranging
   itself under the cursor of somebody about to click "delete". */
.forget[open] { flex-basis: 100%; }
.forget > summary { color: var(--crit); border-color: rgba(248,81,73,.28); opacity: .75; }
.forget > summary:hover { color: var(--crit); border-color: rgba(248,81,73,.55); opacity: 1; }
.forget .mute-form button { color: var(--crit); border-color: rgba(248,81,73,.35); }
.forget .mute-form button:hover { border-color: var(--crit); color: var(--crit); }

.unmute { display: inline; margin: 0; }
.unmute button {
    background: none; border: 1px solid var(--line); border-radius: 999px;
    color: var(--muted); cursor: pointer;
    font-family: inherit; font-size: 11px; line-height: 1.4;
    letter-spacing: .3px; text-transform: uppercase;
    padding: 2px 9px; margin-left: 8px; white-space: nowrap;
}
.unmute button:hover { color: var(--ok); border-color: rgba(63,185,80,.4); }

.mute-note {
    display: block;
    flex-basis: 100%;
    margin-top: 5px;
    font-size: 12.5px;
    color: var(--muted);
}
.mute-note-text { color: var(--text); }
.mute-note-text::before { content: "“"; }
.mute-note-text::after  { content: "”"; }
.mute-note-by { margin-left: 6px; font-size: 11.5px; }

/* In the stacked mobile table the first cell is the card title; the reason belongs under it on
   its own line rather than trailing off the end of the name. */
@media (max-width: 720px) {
    .mute { margin-left: 0; }
    td .mute, td .unmute { display: inline-block; margin-top: 6px; }
    td .mute-note { margin-top: 6px; font-weight: 400; }
}

/* ---------- phones and tablets ----------
   The board is read from a phone at least as often as from a desk, usually by someone who has
   just been told something is wrong. Nothing here is a smaller version of the desktop layout;
   the parts that cannot survive a 320 px column are rebuilt. */

@media (max-width: 720px) {
    /* Forty pixels of side padding is an eighth of a 320 px screen. */
    .wrap { padding: 0 12px 32px; }
    .headline { font-size: 17px; }

    /* The bar is two rows here. Sticky it would hold a tenth of the screen hostage on every
       page, so on a phone it scrolls away like everything else. */
    .top { position: static; backdrop-filter: none; }
    .top-inner { padding: 10px 12px; gap: 8px 10px; }

    /* The brand keeps its dot and name; the tagline is desktop chrome. */
    .brand { font-size: 15px; }
    .brand-sub { display: none; }

    /* Two rows, not three. Source order is brand → tabs → sign-out, but on a phone the
       sign-out belongs in the empty space beside the brand; left in document order it wraps
       onto a line of its own below the tabs and sits there alone. */
    .brand      { order: 1; }
    .nav-logout { order: 2; margin-left: auto; }
    .nav        { order: 3; margin-left: 0; width: 100%; gap: 6px; font-size: 13.5px; }

    .nav-link { padding: 7px 10px; }
    .nav-logout button { font-size: 13.5px; padding: 6px 9px; }
    /* The word is a luxury at 320 px; the icon and its title attribute carry the meaning. */
    .nav-logout span { display: none; }

    /* A seven-column table on a phone is a horizontal scrollbar people do not find. Each row
       becomes a card and each cell carries its own label, so the data is read top to bottom
       like everything else on the page. The labels live in data-label on the <td>, which keeps
       the desktop table a plain table with no duplicated markup.
       One rule for the templates: a cell's value must be a SINGLE element or text node. The
       cell is a two-column grid here, so a second child becomes a third grid item and lands on
       the next row under the label rather than beside it. */
    .table-scroll { overflow-x: visible; }
    table, tbody, tr, td { display: block; width: 100%; }
    thead { display: none; }

    tbody tr {
        border: 1px solid var(--line);
        border-radius: 8px;
        background: var(--panel-2);
        padding: 8px 10px;
        margin-bottom: 8px;
    }
    tbody tr:last-child { margin-bottom: 0; }

    td {
        display: grid;
        grid-template-columns: 38% minmax(0, 1fr);
        align-items: baseline;
        /* Without this a pill is a grid item and stretches the full width of the column,
           turning a status badge into a banner. */
        justify-items: start;
        gap: 10px;
        padding: 3px 0;
        border-bottom: 0;
        /* A long container name or URL must wrap rather than push the card open. */
        overflow-wrap: anywhere;
    }
    td::before {
        content: attr(data-label);
        color: var(--muted);
        font-size: 11px;
        letter-spacing: .3px;
        text-transform: uppercase;
    }
    td.num, td.nowrap { text-align: left; white-space: normal; }

    /* An empty cell would otherwise contribute a label with nothing after it. The template
       marks these, because a `td` holding a Twig newline is not `:empty`. */
    td.is-blank { display: none; }

    /* The first cell of a card is its title: no label, and it carries the weight. */
    td:first-child { font-size: 15px; font-weight: 600; display: block; padding-bottom: 6px; }
    td:first-child::before { content: none; }

    .note { font-size: 12.5px; }
    .charts { gap: 10px; }
    .chart .spark { height: 52px; }
}

@media (max-width: 380px) {
    /* At this width the label column stops paying for itself. */
    td { grid-template-columns: 1fr; gap: 0; }
    td::before { font-size: 10.5px; }
}

/* ---------- service grid ---------- */

.group { margin-bottom: 16px; }
.group h3 {
    font-size: 13px; font-weight: 600; color: var(--muted);
    margin: 0 0 8px; letter-spacing: .3px;
}
.svc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); gap: 6px; }
.svc {
    display: flex; align-items: center; gap: 8px;
    background: var(--panel-2); border: 1px solid var(--line);
    border-radius: 8px; padding: 6px 9px; font-size: 13px;
    min-width: 0;
}
.svc .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.svc .state { margin-left: auto; color: var(--muted); font-size: 12px; font-family: var(--mono); }
.svc .since { color: var(--muted); font-size: 11px; white-space: nowrap; }
.svc.is-ignored { opacity: .45; }

/* A workload is not another pod. It sits in the same namespace group as the pods it made and,
   once the namespace prefix is dropped, its name is the pods' name minus a hash — which at a
   glance is no difference at all. The accent says which row is the durable thing: the one that
   keeps its name across every rollout, and the only one that can be muted or marked critical to
   any effect. Grey rather than a state colour, on purpose — this board never lets colour mean two
   things at once, and every colour here already means a state. */
.svc.is-workload { border-left: 3px solid var(--unknown); }
.svc.is-workload .name { font-weight: 600; }

/* Everything that is not green is lifted to the top of the list and given a bit more room:
   on a host with a hundred containers the stopped one must not be just another grey dot. */
/* Wider than the plain grid: these cards carry a state, a timestamp and — once something is
   muted — a sentence of prose. */
.svc-grid--wide { grid-template-columns: repeat(auto-fill, minmax(min(380px, 100%), 1fr)); }
.svc--big {
    font-size: 14px;
    padding: 9px 12px;
    gap: 6px 10px;
    border-left: 3px solid var(--unknown);
    /* The reason a service was muted, and the field for writing one, are full-width children.
       Without wrapping they do not go to a second line — they squeeze the name into an
       ellipsis instead, which is the one part of the card that has to stay readable. */
    flex-wrap: wrap;
}
.svc--big .name { flex: 1 1 auto; }
.svc--big .state { font-size: 13px; }
.svc--big.lv-warn { border-left-color: var(--warn); background: rgba(210,153,34,.07); }
.svc--big.lv-crit { border-left-color: var(--crit); background: rgba(248,81,73,.08); }

/* ---------- charts on the detail page ---------- */

.charts { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); gap: 14px; }
.chart { background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; }
.chart .title { display: flex; align-items: baseline; gap: 8px; font-size: 13px; margin-bottom: 6px; }
.chart .title .now { margin-left: auto; font-family: var(--mono); font-size: 14px; }
.chart .scale { font-size: 11px; color: var(--muted); }
.chart .spark { height: 60px; }

.range { display: flex; gap: 8px; margin-left: auto; }
.range a {
    font-size: 12px; color: var(--muted); border: 1px solid var(--line);
    padding: 3px 9px; border-radius: 6px;
}
.range a.is-active { color: var(--text); border-color: var(--accent); }

/* ---------- classifying a job in place ----------
   The verdict is posted in the background, so the reader keeps their place in a list that is a
   hundred and fifty rows long. The row is dimmed rather than removed: a row vanishing from above
   the pointer shifts the next one under it mid-click. */

tr.is-classified td { opacity: .55; }
.classified-as { font-size: 12.5px; color: var(--ok); white-space: nowrap; }
.classified-as.is-ignored { color: var(--muted); }
.classify-error { margin-top: 6px; font-size: 12.5px; color: var(--crit); }
form.is-pending { opacity: .6; }

/* ---------- software updates ----------
   The odd page on this board: nothing here is broken, so the layout is a stack of small
   documents rather than a table of states. Each product answers "what, how far behind, where
   does it run, and what do I do about it" in that order — and the last of those is a block of
   text somebody copies, not a list of links they then have to chase. */

.sw {
    border: 1px solid var(--line);
    border-left: 3px solid var(--line);
    border-radius: 8px;
    background: var(--panel-2);
    padding: 12px 14px;
    margin-bottom: 12px;
}
.sw--warn { border-left-color: var(--warn); background: rgba(210,153,34,.06); }
.sw--crit { border-left-color: var(--crit); background: rgba(248,81,73,.07); }

.sw-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.sw-head h3 { margin: 0; font-size: 16px; }
.sw-head .mute, .sw-head .unmute { margin-left: auto; }

.sw-versions { margin: 8px 0 4px; font-size: 14px; display: flex; flex-wrap: wrap; gap: 4px 8px; }
.sw-why { margin: 4px 0 8px; font-size: 14px; }

.sw-where { list-style: none; margin: 8px 0 0; padding: 0; font-size: 13px; }
.sw-where li {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
    padding: 4px 0; border-top: 1px solid var(--line);
}
.sw-host { font-weight: 600; min-width: 74px; }
.sw-name { color: var(--muted); }
/* Image names and compose paths are long, unbroken and full of slashes. Without this they are
   the one thing on the page that pushes a 320 px column sideways. */
.sw-where code { font-family: var(--mono); font-size: 12px; color: var(--text); word-break: break-all; }
/* The path is the longest thing on the line and the least urgent, so it goes last and takes a
   row of its own rather than pushing the version off the end. */
.sw-path { font-family: var(--mono); font-size: 11.5px; color: var(--muted); flex-basis: 100%; word-break: break-all; }

.tag--warn { color: var(--warn); border-color: rgba(210,153,34,.35); }

/* ---------- the update prompt ---------- */

details.prompt { margin-top: 10px; }
details.prompt > summary { color: var(--accent); font-size: 13.5px; }
details.prompt > summary:hover { color: var(--accent); text-decoration: underline; }

/* In a service card the prompt is a full-width child like the mute note, not a pill in the row:
   the row is the name and the state, and forty lines of commands squeezed beside them would push
   the name into an ellipsis — the one part of the card that has to stay readable. */
.svc--big details.prompt { flex-basis: 100%; margin-top: 8px; }
.svc--big details.prompt > summary { font-size: 12.5px; }
/* And opened, the card takes the whole row. The grid cell is 380 px wide, and forty lines of
   commands soft-wrapped into a 380 px column is a work order nobody can follow — the one thing
   this text must not be. Firefox has had :has() since 121; without it the prompt still opens,
   just in the narrow cell. */
.svc-grid--wide .svc--big:has(details.prompt[open]) { grid-column: 1 / -1; }

.prompt-body { position: relative; margin-top: 8px; }
.prompt-body pre {
    margin: 0;
    /* pre-wrap, never a horizontal scroller. This text exists to be selected and copied, and a
       block that scrolls sideways is one somebody copies half of. */
    white-space: pre-wrap;
    word-break: break-word;
    font-family: var(--mono);
    font-size: 12px;
    line-height: 1.5;
    color: var(--text);
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 12px 14px;
    max-height: 460px;
    overflow-y: auto;
}
.prompt-body .js-copy {
    position: absolute; top: 8px; right: 8px;
    font: inherit; font-size: 12px;
    color: var(--muted); background: var(--panel-2);
    border: 1px solid var(--line); border-radius: 6px;
    padding: 4px 10px; cursor: pointer;
}
.prompt-body .js-copy:hover { color: var(--accent); border-color: var(--accent); }

@media (max-width: 720px) {
    .sw-head .mute, .sw-head .unmute { margin-left: 0; }
    .sw-host { min-width: 0; }
    /* Overlapping the first line of a 12 px block is worse than a button on its own row. */
    .prompt-body .js-copy { position: static; display: block; margin-bottom: 6px; }
}
