/* VI Company design tokens, from the hand-off package.
   Wigrum for headings, Inter for body. Drop the font files into
   wwwroot/fonts/ and the @font-face rules below pick them up. */

@font-face {
  font-family: Wigrum;
  src: url('/fonts/wigrumweb-bold.woff') format('woff');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: Wigrum;
  src: url('/fonts/wigrumweb-regular.woff') format('woff');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: Inter;
  src: url('/fonts/Inter-VariableFont.ttf') format('truetype-variations');
  font-weight: 100 900; font-style: normal; font-display: swap;
}

:root {
  --navy: #212A3F;
  --pink: #FB5F61;
  --blue: #4A90D9;
  --grey: #C4C4C4;
  --border: #e6e6e6;
  --page-bg: #fafafa;
  --white: #fff;

  --op: #2f9e6b;
  --op-tint: #e8f5ef;
  --deg: #d9a23c;
  --deg-tint: #fdf3e2;
  --mnt: #4A90D9;
  --mnt-tint: #eaf2fb;
  --out: #FB5F61;

  /* The same colours as channel triples. box-shadow animates only when both
     keyframes are plain colours: with color-mix() the browser cannot
     interpolate the shadow and snaps between the two frames instead, which is
     what turned the live dot's breath into stop motion. */
  --op-rgb: 47 158 107;
  --deg-rgb: 217 162 60;
  --mnt-rgb: 74 144 217;
  --out-rgb: 251 95 97;
  --navy-rgb: 33 42 63;
  --grey-rgb: 196 196 196;
  --out-tint: #ffecec;

  --radius: 6px;
  --display: Wigrum, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --body: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--page-bg);
  color: var(--navy);
  font-family: var(--body);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--pink); }

.wrap { max-width: 720px; margin: 0 auto; padding: 32px 20px 64px; }

/* header */
.site-header {
  position: sticky; top: 0; z-index: 10;
  height: 64px;
  display: flex; align-items: center;
  background: rgba(255,255,255,.92);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
}
.site-header .inner {
  max-width: 720px; width: 100%; margin: 0 auto; padding: 0 20px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.logo { font-family: var(--display); font-weight: 700; font-size: 15px; color: var(--navy); text-decoration: none; }
/* The mark keeps its own corners; its size comes from the markup, so one file
   serves the back office and the favicon. Client pages carry no mark by
   decision — see the comment in _Layout.cshtml. */
.admin-brand img { display: block; border-radius: 4px; }
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--white); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 7px 12px; font: inherit; font-size: 13px; color: var(--navy);
  text-decoration: none; cursor: pointer;
}
.btn:hover { border-color: var(--grey); }
.btn-primary { background: var(--pink); border-color: var(--pink); color: #fff; font-weight: 500; }
.btn-primary:hover { filter: brightness(.95); }

/* Microsoft's sign-in button is a fixed asset, so this styles only what is
   around it: no underline from the link, a real focus ring for keyboard use,
   and a hover state the image cannot provide itself. Nothing here recolours or
   rescales the logo, which the branding guidelines forbid. */
.ms-signin { display: inline-block; line-height: 0; border-radius: 2px; }
.ms-signin img { display: block; max-width: 100%; height: auto; }
.ms-signin:hover { filter: brightness(.97); }
.ms-signin:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }

/* A submit button that has to read as the link it replaced. Sign-out is a POST
   so it cannot be triggered by following a link. */
.link-button {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: var(--pink); cursor: pointer; text-decoration: underline;
}
.link-button:hover { filter: brightness(.9); }

/* A secret that cannot be shown. Dots rather than an empty cell, so the column
   reads as "there is a value here, you just cannot have it". */
.masked { color: #b9bec7; letter-spacing: 2px; user-select: none; }

/* Data old enough that a reader should not take it as current. Said in colour
   as well as words: somebody scanning the page during an outage reads the
   headline and little else. */
.checked-stale { color: var(--deg); font-weight: 500; }

/* Components grouped by hosting location, each group tickable in one go.
   The group has to read as a heading rather than as another component, or the
   two checkboxes look like peers and ticking the wrong one is silent. */
/* A part, in the picker: indented under the component it belongs to, so
   ticking the right one does not depend on reading names carefully. */
.checks label.pick-part { margin-left: 18px; font-size: 13px; color: #55607a; }

.pick-group { border-top: 1px solid var(--border); padding-top: 10px; margin-top: 10px; }
.pick-group:first-child { border-top: 0; padding-top: 0; margin-top: 0; }

.pick-head { margin-bottom: 8px; }
.pick-all { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.pick-all span, .pick-only {
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .07em;
  color: #7b8290;
}
.pick-all:hover span { color: var(--navy); }

/* Indented under its heading, so the shape of the list says which box belongs
   to the platform and which to a component. */
.pick-group .checks { padding-left: 24px; }

@media (max-width: 640px) {
  .pick-group .checks { padding-left: 16px; }
}

/* A table that holds a password is wider than a phone. Scroll it in its own
   box rather than letting it push the page sideways. */
.table-scroll { overflow-x: auto; margin-bottom: 12px; }
.table-scroll table { margin-bottom: 0; }

/* Issuing another password: two fields and a button. Styled here rather than
   inline, because an inline align-items beats the media query below and left
   the stacked fields aligned right and sized to their content. */
.issue-row {
  display: flex; align-items: flex-end; gap: 8px; margin-top: 4px;
}
.issue-row .field { flex: 2; margin: 0; }

/* Three controls on one line is fine on a desktop and unusable on a phone:
   the recipient field ends up a few characters wide. */
@media (max-width: 640px) {
  .issue-row { flex-direction: column; align-items: stretch; }
  .issue-row .btn { width: 100%; justify-content: center; }
}

h1 { font-family: var(--display); font-weight: 700; font-size: 32px; letter-spacing: -0.015em; margin: 0 0 24px; }
h2 { font-family: var(--display); font-weight: 700; font-size: 20px; margin: 0; }

.card { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius); }
.card-pad { padding: 24px; }

.overall { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 24px; }
.overall .left { display: flex; align-items: center; gap: 12px; }
.stamp { font-size: 12px; color: #7b8290; }

.dot { width: 12px; height: 12px; border-radius: 50%; flex: none; }
.d-Operational { background: var(--op); }
.d-Degraded { background: var(--deg); }
/* Maintenance is planned work, not a fault, so it gets a pause mark instead of
   another coloured dot. It reads as "deliberately stopped" at a glance, which
   separates it from the amber/red family without anyone having to read the
   label. Drawn with a gradient rather than an icon font or an SVG file: two
   bars is about the least that can be drawn, and it inherits the colour. */
.d-Maintenance {
  background: linear-gradient(to right,
    var(--mnt) 0 33.333%, transparent 33.333% 66.667%, var(--mnt) 66.667% 100%);
  border-radius: 1px;
}

/* The same mark inside a pill, in whatever colour the pill already uses. */
.pill.p-Maintenance::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 8px;
  margin-right: 5px;
  vertical-align: -1px;
  border-radius: 1px;
  background: linear-gradient(to right,
    currentColor 0 33.333%, transparent 33.333% 66.667%, currentColor 66.667% 100%);
}
.d-Outage { background: var(--out); }
/* Nothing measured — the same grey a day with no data wears, and for the same
   reason: an unwatched site is not making a claim, and green would be one. */
.d-None { background: #cdd3da; }

/* component rows */
.comp { padding: 20px 24px; border-bottom: 1px solid var(--border); }
.comp:last-child { border-bottom: 0; }
.comp-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.comp-name { font-family: var(--display); font-size: 17px; }

/* The name takes the slack; the marks travel together on the right and wrap
   as one, rather than the percentage staying beside the name and the status
   dropping to a line of its own. */
.comp-head .comp-name { margin-right: auto; }
.comp-marks { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* The terms the figure was measured over. A percentage whose terms are hidden
   is a percentage nobody can check, and a tooltip is no use on a phone. */
.sla-terms { font-size: 11px; color: #9aa0ac; margin-top: 6px; }
.pill {
  border-radius: 999px; padding: 3px 10px; font-size: 12px; font-weight: 500; white-space: nowrap;
}
.p-Operational { background: var(--op-tint); color: var(--op); }
.p-Degraded { background: var(--deg-tint); color: var(--deg); }
.p-Maintenance { background: var(--mnt-tint); color: var(--mnt); }
/* Same blue, but no maintenance meaning — for pills that are merely
   informational. Kept separate because p-Maintenance now carries a pause mark. */
.p-Info { background: var(--mnt-tint); color: var(--mnt); }

/* What an incident was, beside the pill saying it is over. Outlined rather
   than tinted: a tinted pill is how this platform says something is happening
   now, and this one is about something that has finished. The dot keeps the
   colour that says how far it went, and the word stays in ink so the pill does
   not read as a live state. */
.p-Was {
  background: var(--white); border: 1px solid var(--border);
  display: inline-flex; align-items: center; gap: 6px;
}
.p-Was::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; flex: none;
}
.p-Was > span { color: var(--navy); }

/* The month's availability. Deliberately colourless: green would be this page
   vouching that the figure meets the agreement, and the agreement's target is
   not something this platform holds. It is a measurement sitting next to a
   verdict, and it should not be mistaken for a second verdict. */
.p-Uptime {
  background: var(--page-bg); color: var(--navy);
  border: 1px solid var(--border); font-variant-numeric: tabular-nums;
}
.p-Outage { background: var(--out-tint); color: var(--out); }


/* status history strip — what we published, not a measurement */
.strip { display: flex; gap: 2px; margin-top: 14px; }
.strip span { flex: 1; height: 28px; border-radius: 2px; background: var(--op); }
.strip .s-Degraded { background: var(--deg); }
.strip .s-Maintenance { background: var(--mnt); }
.strip .s-Outage { background: var(--out); }
.strip-legend { display: flex; justify-content: space-between; font-size: 11px; color: #9aa0ac; margin-top: 6px; }

.section-head { display: flex; align-items: baseline; justify-content: space-between; margin: 40px 0 12px; }

/* Cards and fields are tighter here too. The client pages keep their air —
   they are read once, in a hurry, by somebody who is worried. */
.admin .card-pad { padding: 16px 18px; }
.admin .field { margin-bottom: 12px; }
.admin .field span { margin-bottom: 4px; }
.admin .field input[type=text], .admin .field input[type=password],
.admin .field input[type=email], .admin .field input[type=datetime-local],
.admin .field select, .admin .field textarea { padding: 7px 10px; font-size: 13px; }
.admin .field textarea { min-height: 84px; }
.admin .stamp { font-size: 12px; }

/* The back office is denser than a client page: it is scanned and worked in,
   not read. One rhythm for every screen in it — the settings page already
   used this and the others were half again as tall for the same content. */
.admin .section-head { margin: 22px 0 8px; }
.admin .section-head h2 { font-size: 15px; }
.section-head h2 { font-size: 17px; font-weight: 400; }
.ext { font-size: 13px; font-weight: 500; text-decoration: none; }

/* The timeline: one rail down the left, with a dot per event hung on it. The
   rail stops at the last entry rather than running to the foot of the section,
   because a line continuing past the end suggests there is more below. */
.timeline { list-style: none; margin: 8px 0 0; padding: 0 0 0 26px; position: relative; }
.timeline::before {
  content: ""; position: absolute; left: 4px; top: 10px; bottom: 9px;
  width: 2px; background: var(--border);
}

/* Without a beginning to draw, the rail has no business being capped: it stops
   at the last entry shown, which is not the same as stopping at the start. */
.timeline.tl-open::before { bottom: 12px; }
.tl-month {
  font-size: 13px; color: #9aa0ac; margin: 20px 0 6px; position: relative;
  text-transform: uppercase; letter-spacing: .06em;
}
.tl-month:first-child { margin-top: 4px; }
.tl-entry { position: relative; padding: 10px 0; border-bottom: 1px solid var(--border); }
.tl-entry:last-child { border-bottom: 0; }
.tl-entry > a { text-decoration: none; color: inherit; display: block; }
.tl-entry > a:hover .tl-title { color: var(--pink); }

/* Sits on the rail, in the colour of what it did. The white ring is what keeps
   it readable where a dot lands next to the line rather than on it.

   Every dot is filled: the line reads as a row of events, which is what it is.
   What separates them is movement — anything still running pulses. */
.tl-dot {
  position: absolute; left: -26px; top: 15px; width: 10px; height: 10px;
  border-radius: 50%; background: currentColor; color: var(--grey);
  --c-rgb: var(--grey-rgb);
}

/* Still running. A halo in the dot's own colour that breathes, so the live
   thing on the page is the one the eye goes to.

   The first version read as blinking, and the reason was the shape of the
   animation rather than its speed: the halo faded to nothing by 70%, held
   empty, then snapped back to a solid ring at the loop. This one starts and
   ends on the same frame, so there is no jump, and it never fades out — the
   halo is always there, only wider or tighter. */
.tl-dot.is-live { animation: tl-pulse 3.2s ease-in-out infinite; }

@keyframes tl-pulse {
  0%, 100% { box-shadow: 0 0 0 3px rgb(var(--c-rgb) / .30); }
  50%      { box-shadow: 0 0 0 8px rgb(var(--c-rgb) / .10); }
}

/* Somebody who has asked for less movement still gets the emphasis, held
   still. A status page is read by people who are already worried, and for some
   of them the motion is the problem rather than the point. */
@media (prefers-reduced-motion: reduce) {
  .tl-dot.is-live, .tl-item.is-live::before {
    animation: none;
    box-shadow: 0 0 0 4px rgb(var(--c-rgb) / .24);
  }
}
/* The foot of the rail: where the record begins. Set in the same muted caps as
   the month headings and aligned with them, so it reads as part of the line's
   own structure rather than as an entry with a missing title — nothing
   happened that day, and captioning it would invent an event to hang on it.

   It wears the same dot an incident wears, so the line ends the way it runs —
   in the plain grey the dot falls back to, because a start is not degraded, an
   outage or maintenance, and borrowing one of those colours would say it was. */
.tl-start {
  position: relative; font-size: 13px; color: #9aa0ac; margin-top: 14px;
  text-transform: uppercase; letter-spacing: .06em;
}
/* Filled like the events above it. Its grey and the date beside it are what
   say it is the end of the line rather than something that happened. */
.tl-start .tl-dot { top: 2px; }

.tl-title { display: block; font-size: 15px; font-weight: 500; }
.tl-meta {
  font-size: 12px; color: #7b8290; display: flex; gap: 10px;
  align-items: center; flex-wrap: wrap; margin-top: 3px;
}
/* What an incident that is over was. Plain words rather than a pill, and a
   shade darker than the rest of the meta so it still reads first. */
.tl-was { color: #55607a; font-weight: 500; }
/* Says there is more behind the row, so it must not look like a status. Plain
   muted text with a rule under it — the reader is already on a link. */
.tl-pm { color: #7b8290; border-bottom: 1px solid var(--border); }
.tl-entry > a:hover .tl-pm { color: var(--pink); border-bottom-color: currentColor; }
.tl-more { margin-top: 14px; }

/* The dot on the timeline, in the same colours the component pills use — an
   incident is coloured by what it did, not by a grade. */
.b-Degraded { color: var(--deg); --c-rgb: var(--deg-rgb); }
.b-Outage { color: var(--out); --c-rgb: var(--out-rgb); }
.b-Maintenance { color: var(--mnt); --c-rgb: var(--mnt-rgb); }

/* incident detail timeline */
.back { font-size: 13px; color: #7b8290; text-decoration: none; display: inline-block; margin-bottom: 20px; }
.metarow { display: flex; gap: 32px; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); padding: 16px 0; margin: 20px 0 32px; flex-wrap: wrap; }
.metarow .k { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: #9aa0ac; }
.metarow .v { font-size: 14px; }

.tl { position: relative; padding-left: 24px; }
/* One colour per stage, held in a custom property so the dot, the label and
   the pulse all take it from one place — and so the pseudo-element can set
   `color` and let currentColor do the rest. */
.tl-item { position: relative; padding-bottom: 28px; --c: var(--grey); --c-rgb: var(--grey-rgb); }
.tl-item::before {
  content: ""; position: absolute; left: -24px; top: 5px;
  width: 10px; height: 10px; border-radius: 50%;
  color: var(--c); background: currentColor;
}
.tl-item:not(:last-child)::after { content: ""; position: absolute; left: -20px; top: 19px; bottom: 0; width: 1px; background: var(--border); }
.tl-item.Investigating { --c: var(--pink); --c-rgb: var(--out-rgb); }
.tl-item.Identified { --c: var(--deg); --c-rgb: var(--deg-rgb); }
.tl-item.Monitoring { --c: var(--blue); --c-rgb: var(--mnt-rgb); }
.tl-item.Resolved { --c: var(--op); --c-rgb: var(--op-rgb); }
/* Planned work reads in its own words: scheduled, under way, done. Without
   these the timeline fell back to grey for every maintenance entry while
   incidents were colour-coded. */
.tl-item.Completed { --c: var(--op); --c-rgb: var(--op-rgb); }
.tl-item.Scheduled { --c: var(--mnt); --c-rgb: var(--mnt-rgb); }
.tl-item.Inprogress { --c: var(--mnt); --c-rgb: var(--mnt-rgb); }
/* Statuspage's own stage between the work and the sign-off. Only ever arrives
   through an import; this platform has no state for it. */
.tl-item.Verifying { --c: var(--mnt); --c-rgb: var(--mnt-rgb); }
/* The write-up. Ink rather than one of the state colours: it is not a state
   the incident passed through, it is what was said about it afterwards. */
.tl-item.Postmortem { --c: var(--navy); --c-rgb: var(--navy-rgb); }
.tl-item.Postmortem .tl-body + .tl-body { margin-top: 10px; }

.tl-state { font-size: 13px; font-weight: 700; color: var(--c); }

/* Where it stands, still running. The same pulse the status page timeline
   uses, so "this is happening now" looks the same on both. */
.tl-item.is-live::before { animation: tl-pulse 3.2s ease-in-out infinite; }

/* A stage this has not reached yet. Hollow, like every other thing on this
   platform that has not happened, and grey whatever stage it is — colouring it
   would claim it as done. */
.tl-item.is-ahead::before {
  background: var(--white); border: 2px solid var(--grey); box-sizing: border-box;
}
.tl-item.is-ahead { padding-bottom: 18px; }
.tl-item.is-ahead .tl-state { color: #9aa0ac; font-weight: 500; }
.tl-item.is-ahead .tl-time { font-style: italic; }

.tl-time { font-size: 12px; color: #7b8290; }
.tl-body { font-size: 15px; line-height: 1.6; margin-top: 4px; }

/* Follows vicompany.nl, whose footer is light and quiet: small grey legal
   links over the page's own ground, no slab of navy and no logo. A status page
   is read during an outage — the last thing it needs is a heavy block competing
   with the thing the reader came for. */
footer.site {
  margin-top: 56px;
  border-top: 1px solid var(--border);
  background: transparent;
}
footer.site .inner {
  max-width: 720px; margin: 0 auto; padding: 18px 20px 40px;
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 10px 24px; flex-wrap: wrap;
  font-size: 12px; color: #8d939e;
}
footer.site a { color: #8d939e; text-decoration: none; }
footer.site a:hover { color: var(--navy); text-decoration: underline; }
.foot-links { display: flex; gap: 18px; flex-wrap: wrap; }


/* admin */
.admin { max-width: 900px; margin: 0 auto; padding: 24px 20px 56px; }
.admin table, .admin-main table { width: 100%; border-collapse: collapse; background: var(--white); border: 1px solid var(--border); border-radius: var(--radius); }
.admin th, .admin-main th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: #9aa0ac; padding: 7px 12px; border-bottom: 1px solid var(--border); }
.admin td, .admin-main td { padding: 9px 12px; border-bottom: 1px solid var(--border); font-size: 13px; }
.admin tr:last-child td, .admin-main tr:last-child td { border-bottom: 0; }
/* Our own display rules outrank the browser's [hidden] — .field sets
   display:block, so a field hidden from script stayed on screen. Stated here
   once, so toggling .hidden works everywhere rather than in the places that
   happen not to set display. */
[hidden] { display: none !important; }

.field { display: block; margin-bottom: 16px; }
.field span { display: block; font-size: 12px; font-weight: 500; margin-bottom: 5px; }
/* The rule above is for a field's own label, but it catches every nested span
   too — a pill inside a field would render as a full-width bar. */
.field .pill { display: inline-block; margin-bottom: 0; }
.field td span { display: inline; margin-bottom: 0; }
.field input[type=text], .field input[type=password], .field textarea, .field select {
  width: 100%; padding: 9px 11px; border: 1px solid var(--border); border-radius: var(--radius);
  font: inherit; font-size: 14px; background: var(--white); color: var(--navy);
}
.field textarea { min-height: 110px; resize: vertical; }
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.checks { display: flex; gap: 16px; flex-wrap: wrap; font-size: 14px; }
.checks label { display: inline-flex; gap: 6px; align-items: center; }

/* A site owns a card, and its components are the rows inside it. Containment
   is the card edge rather than an indent: a heading over a list leaves the
   reader guessing how far the grouping reaches, and "which components does this
   platform outage cover" is exactly what must not be guesswork. */
.site-card + .site-card { margin-top: 14px; }

/* Header band. Tinted so it reads as the card's lid rather than as the first
   row of content. */
.site-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 11px 18px;
  background: var(--page-bg);
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
}
.site-head .dot { width: 9px; height: 9px; }
.site-name {
  font-size: 12px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: #7b8290;
}
/* Present only when something is wrong, so it can be plain text rather than a
   box competing with the components under it. */
.site-state { font-size: 12px; color: var(--out); }

/* A site in trouble tints its own band, so which card is affected is visible
   before a word is read. */
.site-head.is-down { background: var(--out-tint); border-bottom-color: var(--out); }
.site-head.is-partial { background: var(--deg-tint); border-bottom-color: var(--deg); }
.site-head.is-partial .site-state { color: #9a6f16; }
.site-head.is-down .site-name, .site-head.is-partial .site-name { color: var(--navy); }

@media (max-width: 560px) {
  .site-head { padding: 10px 14px; }
}

/* A facet of the component above it — the same row, set in from the edge and
   a shade quieter, so the eye reads it as belonging to what it sits under
   rather than as another service. */
.comp-part { padding-left: 34px; }
.comp-part .comp-name { font-size: 14px; font-weight: 500; color: #55607a; }
.comp-part .comp-name::before {
  content: ""; display: inline-block; width: 10px; height: 1px;
  background: var(--border); vertical-align: middle; margin-right: 8px;
}
@media (max-width: 560px) {
  .comp-part { padding-left: 22px; }
}

/* Row actions: laid out so Delete cannot be hit while aiming for Edit, and
   coloured so it is read as the one that does not come back. */
.row-actions { white-space: nowrap; }
.row-actions a { margin-right: 12px; }
.row-actions a:last-child { margin-right: 0; }
.row-actions a.danger { color: var(--out); }

/* A guess at which check a component means. Quiet — it is a suggestion, and
   the operator is the one who knows. */
.suggestion {
  margin-bottom: 10px; padding: 9px 12px; font-size: 13px;
  background: var(--mnt-tint); border: 1px solid var(--mnt); border-radius: var(--radius);
}
.suggestion .link-button { margin-left: 6px; }

/* A select you can type into. The list is absolutely positioned so it covers
   whatever follows rather than pushing the form down as you type — a field
   that reflows under the cursor is hard to aim at. */
.combo { position: relative; }
.combo-input { width: 100%; }
.combo-list {
  position: absolute; z-index: 20; left: 0; right: 0; top: 100%;
  margin: 2px 0 0; padding: 4px; list-style: none;
  max-height: 240px; overflow-y: auto;
  background: var(--white); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: 0 6px 18px rgb(33 42 63 / .10);
}
.combo-option {
  padding: 6px 8px; font-size: 13px; border-radius: 4px; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Keyboard and mouse land on the same highlight, so arrowing down and hovering
   cannot disagree about what Enter would pick. */
.combo-option:hover, .combo-option.is-at { background: #f1f3f6; }
.combo-option.is-chosen { font-weight: 600; }
.combo-none { padding: 6px 8px; font-size: 13px; color: #9aa0ac; }

/* The headline figures, across the top. Four at most, because a strip of
   numbers stops being read at about five. */
.stat-strip { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; }
.stat {
  flex: 1 1 140px; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--white);
}
.stat strong { display: block; font-size: 19px; font-variant-numeric: tabular-nums; }
.stat span { font-size: 11px; color: #7b8290; text-transform: uppercase; letter-spacing: .06em; }

/* A count sits against the right edge so the column of them can be read down
   rather than hunted for on each line. */
.count-row { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.count-row strong { font-size: 14px; }
.count { font-size: 15px; font-variant-numeric: tabular-nums; }

/* The engine page. Prose rather than a form, so it gets a reading measure
   instead of the back office's usual density. */
.engine { max-width: 820px; }
.engine .lead { font-size: 15px; line-height: 1.65; }
.engine p { max-width: 68ch; line-height: 1.65; font-size: 14px; }
.engine-figure { margin: 26px 0; padding: 0; }
.engine-figure svg { width: 100%; height: auto; color: var(--navy); }
.engine-figure figcaption {
  font-size: 12px; color: #7b8290; margin-top: 8px; text-align: center;
}
.engine-notes p + p { margin-top: 14px; }
.engine h2 { font-size: 16px; margin: 28px 0 10px; }

/* Settings: a narrow column of rows. Six short values do not need six
   full-width cards, and reading them should not need scrolling. */
.settings { max-width: 760px; }
.settings-group { margin-top: 20px; }
.settings-group h2 { font-size: 15px; margin: 0 0 8px; }
/* A row inside the group's block, not a block of its own. The divider is what
   separates one setting from the next; the border belongs to the group. */
.setting { padding: 14px 16px; }
.setting + .setting { border-top: 1px solid var(--border); }
.setting-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.setting-head strong { font-size: 14px; }
.setting-head .pill { font-size: 11px; }
.setting-what { font-size: 12px; color: #7b8290; margin: 4px 0 8px; }
.setting-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.setting-row + .setting-row { margin-top: 6px; }
.setting-row input {
  flex: 1; min-width: 220px; padding: 7px 10px;
  border: 1px solid var(--border); border-radius: var(--radius);
  font: inherit; font-size: 13px; background: var(--white); color: var(--navy);
}
.setting-row .btn { padding: 6px 12px; font-size: 13px; }

/* A short banner above the settings, rather than a card for one sentence. */
.note { padding: 10px 12px; border-radius: var(--radius); font-size: 13px; margin-bottom: 12px; }
.note-good { background: var(--op-tint); border: 1px solid var(--op); color: var(--op); }
.note-bad { background: var(--out-tint); border: 1px solid var(--out); color: var(--out); }
.note-info { background: var(--mnt-tint); border: 1px solid var(--mnt); color: #2f5f8f; }

/* An editor laid out as groups rather than one long card. The group's own line
   carries the reasoning the fields under it would otherwise each repeat, so it
   sits tight under the heading and the fields keep a single line apiece. */
.form-groups { max-width: 760px; }
.form-groups .settings-group h2 { margin-bottom: 3px; }
.form-groups .what { font-size: 12px; color: #7b8290; margin: 0 0 9px; }
.form-groups .card-pad .field:last-child { margin-bottom: 0; }

@media (max-width: 560px) {
  .setting-row input { min-width: 100%; }
}

/* The page's own header row: its name, and the one thing a reader might want
   to do with the page. The button wraps under the name on a narrow screen
   rather than squeezing a 26px title into two lines beside it. */
.page-top {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; flex-wrap: wrap; margin-bottom: 24px;
}
.page-top h1 { margin: 0; }

/* Asking to be told. Opened from the header, so it sits above the status
   rather than a scroll below it. */
.subscribe { margin-bottom: 20px; }
.subscribe input[type=email] {
  padding: 9px 11px; border: 1px solid var(--border); border-radius: var(--radius);
  font: inherit; font-size: 14px; background: var(--white); color: var(--navy);
}

/* The unusable window, when it differs from how long the incident ran. Set
   apart from the meta row because it is the number a client is looking for. */
.unusable {
  margin: 14px 0 0; padding: 10px 12px;
  background: var(--out-tint); border-radius: var(--radius);
  font-size: 14px;
}
.unusable .stamp { display: inline; }

/* The write-up after the fact. Set as prose rather than as another timeline
   entry, because it is read rather than scanned. */

/* Tables scroll inside their own container rather than pushing the page sideways. */
.scroll-x { overflow-x: auto; }
.scroll-x table { min-width: 560px; }

/* A cell that can hold a long list stays one line and ends in an ellipsis,
   with the whole of it on hover. Thirty-one component names printed in full
   made a table row taller than the screen. */
.admin td.clip, .admin-main td.clip { max-width: 260px; }
.admin td.clip, .admin td.clip > div,
.admin-main td.clip, .admin-main td.clip > div {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* The tables that genuinely carry many columns get a floor, below which they
   scroll sideways rather than squeezing every cell into four wrapped lines.
   Opt-in: applying it to every table pushed the actions off a four-column one
   that had room to spare. */
.admin .scroll-x.wide table, .admin-main .scroll-x.wide table { min-width: 820px; }

/* Publishing happens from a phone at 03:00, so the three incident actions have
   to work on a small screen. Touch targets grow, forms go single column, and
   the action row sticks to the bottom so "Post update" is always reachable. */
@media (max-width: 640px) {
  .wrap, .admin { padding: 20px 16px 48px; }
  h1 { font-size: 26px; }

  .overall { flex-direction: column; align-items: flex-start; gap: 8px; }
  .comp { padding: 16px; }
  .comp-head { flex-wrap: wrap; gap: 8px; }
  .strip span { height: 34px; }

  .field input[type=text], .field input[type=password], .field textarea, .field select,
  .field input[type=datetime-local] { padding: 12px; font-size: 16px; }
  .checks { flex-direction: column; gap: 12px; }
  .checks label { padding: 4px 0; }
  .checks input[type=checkbox] { width: 20px; height: 20px; }

  .btn { padding: 11px 16px; font-size: 15px; }

  form .row {
    position: sticky; bottom: 0;
    background: var(--white);
    border-top: 1px solid var(--border);
    padding: 12px 0; margin: 0 -16px; padding-left: 16px; padding-right: 16px;
  }
  form .row .btn { flex: 1; justify-content: center; }

  .metarow { gap: 16px; }
  .site-header .inner { padding: 0 16px; }
}

/* Daily downtime, from the reporting tool's own outage records. One bar per
   day, ninety of them, so a bad week is visible at a glance and the tooltip
   says what happened. */
.days { display: flex; gap: 2px; margin-top: 14px; align-items: stretch; }
.day { flex: 1; height: 28px; border-radius: 2px; background: var(--op); min-width: 2px; }
/* One variable per level, so the day in progress can borrow its colour for an
   outline instead of a fill. */
.day.d-ok   { --lvl: var(--op);  background: var(--lvl); }
.day.d-part { --lvl: var(--deg); background: var(--lvl); }
.day.d-out  { --lvl: var(--out); background: var(--lvl); }
.day.d-none { --lvl: #cdd3da;    background: #e9ecef; }
/* Before the service existed. Fainter than "no data": one is a gap in what we
   know, the other is a day there was nothing to know. */
.day.d-pre  { --lvl: #eef1f4;    background: #f4f6f8; }
.key-dot.d-pre { background: #f4f6f8; border: 1px solid var(--border); }

/* Down, but none of it inside the agreed hours. Green, because the client was
   owed nothing through it and their month is untouched — a lighter green,
   because a day something happened on must not be drawn exactly like a day
   nothing happened on. The panel underneath says "Down for 41m", and an
   identical bar would make that read as a contradiction. */
.day.d-off { --lvl: #8ed4b4; background: var(--lvl); }

/* Today has not finished, so it is drawn as edges only — whatever it ends up
   being, it is not that yet. */
.day.is-today {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--lvl);
}
.day:hover { outline: 1px solid var(--navy); outline-offset: 1px; }
.days-legend {
  display: flex; justify-content: space-between;
  font-size: 11px; color: #9aa0ac; margin-top: 6px;
}

/* The tail of days the reporting tool has not published yet. Saying so beats
   leaving a grey stretch that reads as "we stopped looking". */
.days-legend .pending { color: #b0b6c0; font-style: italic; }

/* Days are buttons, not decoration — clicking one opens what happened. */
.day { border: 0; padding: 0; cursor: pointer; font: inherit; }
.day:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.day.is-open { outline: 2px solid var(--navy); outline-offset: 1px; }

.day-detail {
  margin-top: 10px; padding: 14px 16px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: #fbfcfd;
}
.day-detail .dd-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; flex-wrap: wrap; margin-bottom: 2px;
}
.day-detail .dd-date { font-family: var(--display); font-weight: 700; font-size: 14px; }

/* The day's verdict, on its own line under the date rather than floating
   between the date and the Close button with nothing to align to. Coloured by
   what the agreement covered, so it cannot read red above an amber bar. */
.day-detail .dd-verdict { font-size: 13px; font-weight: 500; margin-top: 1px; }
.day-detail .dd-verdict.lvl-out { color: var(--out); }
.day-detail .dd-verdict.lvl-part { color: var(--deg); }
.day-detail .dd-verdict.lvl-ok { color: var(--op); }
/* Down, but none of it owed. The same green as its bar; the clause after the
   figure is what carries the nuance. */
.day-detail .dd-verdict.lvl-off { color: var(--op); }
.day-detail .dd-verdict.lvl-none { color: #9aa0ac; font-style: italic; font-weight: 400; }

/* The rest of the same sentence: what the agreed hours made of it. Quieter
   than the figure it qualifies, but on the same line, because they are one
   fact about the day and not a claim with a footnote. */
.day-detail .dd-agreed {
  color: #7b8290; font-weight: 400; font-style: italic;
}
.day-detail .dd-agreed::before { content: " — "; font-style: normal; }

.day-detail .dd-note { font-size: 12px; color: #7b8290; margin-top: 2px; }

/* Wraps at any width. The note saying what the agreed hours made of an outage
   is longer than the label that used to sit here, and a nowrap badge in a
   nowrap row simply ran off the right-hand edge. */
.dd-event {
  display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline;
  padding: 8px 0; border-top: 1px solid var(--border); font-size: 13px;
}
.dd-event:first-of-type { margin-top: 8px; }
.dd-time { font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--navy); font-weight: 500; }
.dd-dur { font-size: 12px; color: #7b8290; white-space: nowrap; }
.dd-msg { color: #495260; flex: 1 1 140px; min-width: 0; }
.dd-offhours {
  font-size: 11px; color: #9aa0ac; font-style: italic;
  white-space: nowrap; flex: none; margin-left: auto;
}
.dd-close {
  background: none; border: 0; cursor: pointer; color: #9aa0ac;
  font: inherit; font-size: 12px; padding: 0;
}
.dd-close:hover { color: var(--navy); }

/* A flappy day can have thirty-odd separate outages. Scroll the list rather
   than pushing the rest of the page off the screen. */
.dd-events { max-height: 216px; overflow-y: auto; padding-right: 6px; }
.dd-events .dd-event:first-of-type { margin-top: 0; border-top: 0; }

/* Month markers beneath the graph, so a bar can be placed on the calendar
   without hovering every one of them. */
.day-months { display: flex; gap: 2px; margin-top: 4px; }
.dm {
  flex: 1; min-width: 2px; position: relative;
  font-size: 10px; color: #b0b6c0; white-space: nowrap; overflow: visible;
  height: 12px; line-height: 12px;
}
.dm:not(:empty)::before {
  content: ""; position: absolute; top: -4px; left: 0;
  width: 1px; height: 4px; background: #d6dae0;
}

/* The middle of the legend doubles as a readout: scanning the bars shows each
   date in place, rather than waiting on a tooltip that never appears on touch. */
.days-readout { min-height: 1.2em; transition: color .1s; }
.days-readout.is-hovering { color: var(--navy); font-weight: 500; }

/* ---------- mobile ---------- */

/* The graph scrolls rather than shrinking. At 375px, ninety bars across the
   screen are two pixels wide — impossible to hit with a thumb, and there is no
   hover on a touch screen to fall back on. */
.days-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.narrow-only { display: none; }

@media (max-width: 640px) {
  /* Thirty days rather than ninety. Ninety bars across a phone are two pixels
     wide — untappable, and there is no hover to fall back on. Thirty fit at a
     width a thumb can hit, and the recent month is the more useful window
     anyway. */
  .day.older, .dm.older { display: none; }
  .day { height: 34px; }
  .wide-only { display: none; }
  .narrow-only { display: inline; }

  .days-legend {
    flex-wrap: wrap; gap: 2px 10px;
    font-size: 10.5px;
  }
  .days-legend .days-mid { order: 3; flex-basis: 100%; }

  /* Stop the section heading and its buttons fighting over one line. */
  .section-head { flex-wrap: wrap; gap: 8px; }
  .section-head h2 { flex-basis: 100%; }

  .comp-head { align-items: flex-start; }

  /* The detail panel is the only way into a day on touch, so give it room. */
  .day-detail { margin-left: -4px; margin-right: -4px; padding: 12px; }
  .dd-event { flex-wrap: wrap; gap: 2px 10px; padding: 10px 0; }
  .dd-msg { flex-basis: 100%; font-size: 12px; }
  .dd-events { max-height: 260px; }
  /* Close sits in the corner rather than taking a line of its own. */
  .day-detail .dd-head { position: relative; padding-right: 52px; }
  .dd-close { position: absolute; top: 0; right: 0; padding: 2px 0 8px 12px; }

  .metarow { gap: 12px 20px; }
  .overall { align-items: flex-start; }
}

/* Planned work reads as information: blue, quiet, and never in the red/amber
   language the page uses for faults. */
.notice {
  display: flex; gap: 12px; align-items: flex-start;
  background: var(--mnt-tint);
  border: 1px solid #cfe0f5;
  border-radius: var(--radius);
  padding: 14px 18px;
  margin-bottom: 24px;
}
.notice .dot { margin-top: 6px; }
.notice strong { font-size: 15px; color: var(--mnt); }
.notice-body { font-size: 13px; color: #4a5568; margin-top: 2px; }

/* ---------- back office shell ---------- */

/* The head of the back office: brand and account on one line, one rule under
   the full width. The sidebar and content column both start below it, so the
   grid reads as one thing rather than a bar laid across two. */
.admin-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; height: 56px; padding: 0 32px 0 20px;
  background: var(--white);
  border-bottom: 1px solid var(--border);
}

.admin-shell { display: flex; min-height: calc(100vh - 57px); align-items: stretch; }

.admin-nav {
  flex: 0 0 220px; width: 220px;
  background: var(--white);
  border-right: 1px solid var(--border);
  padding: 24px 0 32px;
  display: flex; flex-direction: column; gap: 4px;
}
.admin-brand {
  font-family: var(--display); font-weight: 700; font-size: 15px;
  color: var(--navy); text-decoration: none;
  /* No padding of its own: the band already pads 20px on the left, which is
     where the navigation items below start. Adding to it pushed the brand
     20px right of everything it is meant to line up with. */
  padding: 0;
  white-space: nowrap;
  /* The mark sits beside the word, so the anchor has to lay them out. */
  display: inline-flex; align-items: center; gap: 8px;
}
.nav-heading {
  font-size: 10px; text-transform: uppercase; letter-spacing: .08em;
  color: #9aa0ac; padding: 14px 20px 6px; font-weight: 600;
}
.nav-list { list-style: none; margin: 0; padding: 0; }
.nav-list li { margin: 0; }
.nav-list a {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 20px; font-size: 14px;
  color: var(--navy); text-decoration: none;
  border-left: 3px solid transparent;
}
.nav-list a:hover { background: #f7f8fa; }

/* A section that has nothing to work with yet. Faded rather than removed: it
   says the feature exists and is not set up, where hiding it would have an
   operator hunting for a menu entry that is deliberately absent. Still a link,
   because the page behind it names the missing key. */
.nav-list a.is-idle { opacity: .45; }
.nav-list a.is-idle:hover { opacity: .7; }
.nav-list a.is-active {
  border-left-color: var(--pink);
  background: #fff5f5; font-weight: 500;
}
.nav-icon {
  width: 16px; height: 16px; flex: none; border-radius: 3px;
  background: currentColor; opacity: .28;
}
.nav-list a.is-active .nav-icon { opacity: 1; background: var(--pink); }
.nav-list a.is-active .i-pause {
  background: linear-gradient(to right,
    var(--pink) 0 33.333%, transparent 33.333% 66.667%, var(--pink) 66.667% 100%);
}
/* Simple geometric marks rather than an icon dependency. */
.i-home { border-radius: 2px; }
.i-alert { clip-path: polygon(50% 0, 100% 100%, 0 100%); border-radius: 0; }
.i-calendar { border-radius: 2px; clip-path: inset(14% 0 0 0); }
.i-page { border-radius: 2px; clip-path: inset(0 18% 0 0); }
.i-component { border-radius: 50%; }
/* A site holds components, so it is the outline the round one sits inside. */
/* A key: bit at one end, shaft at the other. */
.i-key { border-radius: 2px; clip-path: polygon(0 25%, 55% 25%, 55% 0, 75% 0, 75% 25%, 100% 25%, 100% 75%, 75% 75%, 75% 100%, 55% 100%, 55% 75%, 0 75%); }
/* An arrow into a box: something arriving from elsewhere. */
.i-import { border-radius: 2px; clip-path: polygon(0 40%, 40% 40%, 40% 0, 100% 50%, 40% 100%, 40% 60%, 0 60%); }
.i-site { border-radius: 3px; clip-path: polygon(0 22%, 50% 0, 100% 22%, 100% 100%, 0 100%); }
/* A rising step: how much there is. */
.i-stats {
  border-radius: 1px;
  clip-path: polygon(0 62%, 30% 62%, 30% 100%, 0 100%, 0 62%,
                     35% 31%, 65% 31%, 65% 100%, 35% 100%, 35% 31%,
                     70% 0, 100% 0, 100% 100%, 70% 100%, 70% 0);
}
/* Ruled lines: a list of what happened. */
.i-log {
  border-radius: 1px;
  clip-path: polygon(0 0, 100% 0, 100% 18%, 0 18%, 0 0,
                     0 41%, 100% 41%, 100% 59%, 0 59%, 0 41%,
                     0 82%, 70% 82%, 70% 100%, 0 100%, 0 82%);
}
/* Bars, like the day strip the records are drawn as. */
.i-bars {
  border-radius: 1px;
  clip-path: polygon(0 35%, 22% 35%, 22% 100%, 0 100%, 0 35%,
                     39% 0, 61% 0, 61% 100%, 39% 100%, 39% 0,
                     78% 55%, 100% 55%, 100% 100%, 78% 100%, 78% 55%);
}
/* A clock face: the agreement is about hours. */
.i-clock { border-radius: 50%; clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 0 0, 45% 45%, 55% 10%, 45% 45%, 90% 55%, 45% 45%); }
/* Pause, matching the mark maintenance carries on the public pages. Two bars
   cannot be cut from a single polygon, so this paints them instead of clipping
   — which means the active-state colour has to be restated below. */
.i-pause {
  border-radius: 1px;
  background: linear-gradient(to right,
    currentColor 0 33.333%, transparent 33.333% 66.667%, currentColor 66.667% 100%);
}
.i-download { clip-path: polygon(30% 0, 70% 0, 70% 55%, 100% 55%, 50% 100%, 0 55%, 30% 55%); border-radius: 0; }

.nav-foot { margin-top: auto; padding: 20px 20px 0; font-size: 12px; }
.nav-foot a { color: #7b8290; text-decoration: none; }
.nav-foot a:hover { color: var(--navy); }

.admin-main { flex: 1; min-width: 0; padding: 24px 28px 56px; }

/* The account row. Sits on the content column's own grid, above the page
   heading, and quiet enough that the heading is still read first. Replaces the
   full-width public header, which duplicated the sidebar's brand and cut
   across the sidebar/content grid instead of lining up with it. */
.admin-account {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 8px; min-width: 0;
  font-size: 12px; color: #7b8290;
}
.admin-account form { display: inline; }
/* An address can be long and the band is a fixed height, so the name gives way
   rather than wrapping the band open or pushing the sign-out off the edge. */
.admin-account-who,
.admin-account-open {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.admin-account-who { color: var(--navy); font-weight: 500; }
.admin-account-sep { color: var(--border); }
.admin-account-open { color: var(--deg); }
.admin-main .admin { max-width: 900px; margin: 0; padding: 0; }

/* The page heading and its actions, one row. */
.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 24px;
}
.page-head h1 { font-size: 22px; margin: 0; }
.page-head .sub { font-size: 13px; color: #7b8290; margin-top: 4px; }
.page-actions { display: flex; gap: 8px; flex-wrap: wrap; }

.empty {
  border: 1px dashed var(--border); border-radius: var(--radius);
  padding: 28px; text-align: center; color: #7b8290; font-size: 14px;
}
.empty strong { display: block; color: var(--navy); font-size: 15px; margin-bottom: 6px; }

@media (max-width: 860px) {
  /* The sidebar becomes a scrolling strip above the content. No hamburger to
     build, nothing hidden behind a tap. */
  .admin-shell { display: block; }
  .admin-nav {
    /* One scrolling row, not one row per group: display:contents lets the
       list items join the nav's own flex line. Not sticky — the header height
       it would need to clear is not always there. */
    width: auto; flex: none; border-right: 0;
    border-bottom: 1px solid var(--border);
    padding: 0; gap: 0;
    flex-direction: row; overflow-x: auto;
  }
  .nav-list { display: contents; }
  .nav-heading { display: none; }
  .nav-list a {
    border-left: 0; border-bottom: 3px solid transparent;
    padding: 10px 12px; white-space: nowrap;
  }
  .nav-list a.is-active { border-bottom-color: var(--pink); background: none; }
  .nav-icon { display: none; }
  .nav-foot { display: none; }
  .admin-main { padding: 20px 16px 48px; }
  /* The head stays a head on a phone. Only the account text shrinks, and the
     brand is not hidden here the way the old in-sidebar one was — it is the
     only thing naming where you are. */
  .admin-top { height: 52px; padding: 0 16px; }
  .admin-account { font-size: 11px; }
  /* The sign-out must survive the squeeze, so only the text gives way. */
  .admin-account form { flex: none; }
}

/* What the colours mean. Once per page: repeating it under every graph would
   be noise, and the bars are read as a set anyway. */
/* One line at reading width. Five entries and a note do not fit at 12px with
   18px gutters, and a key that wraps reads as a paragraph rather than
   something to glance at. */
.key {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 6px 12px; margin-top: 14px;
  font-size: 11px; color: #7b8290;
}
.key-item { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.key-dot { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.key-dot.d-ok { background: var(--op); }
.key-dot.d-part { background: var(--deg); }
.key-dot.d-out { background: var(--out); }
.key-dot.d-none { background: #e9ecef; }
.key-dot.d-off { background: #8ed4b4; }
.key-note { color: #9aa0ac; margin-left: auto; }

@media (max-width: 640px) {
  /* Wrapping is fine on a phone — the line simply is not wide enough, and
     shrinking the type further would cost legibility for nothing. */
  .key { gap: 5px 10px; font-size: 11px; }
  .key-note { flex-basis: 100%; margin-left: 0; }
}
