/* status — one stylesheet, two rooms.
 *
 * The visitor's page is drawn from two supplied designs: a 780-pixel column
 * of Schibsted Grotesk between a dark banded masthead and a light banded
 * foot, one row per service, ninety marks of history under each, and the
 * checks folded away behind a <details> that opens with no script. Colour is
 * spent on the levels and on nothing else, and never on its own — every dot
 * has its word printed beside it.
 *
 * The console is a different room in the same building: IBM Plex over a cool
 * blue-grey ground, one blue that is the only colour on the page that is not
 * ink, cards held by a line rather than by a shadow, radii by role. The
 * person reading it is signed in and looking for something specific, so the
 * rhythm is tighter and the type steps down.
 *
 * They share this sheet, the level vocabulary and the focus ring, and nothing
 * else. Each scopes its own palette — .visitor and .console — so neither can
 * repaint the other by changing a token, and the tokens at :root are what is
 * genuinely common between them.
 *
 * The history strip is the one place either design spends anything, and it is
 * the one thing that has to survive a photocopier: under @media print the
 * marks are redrawn as fills and hatches, so ninety days still read as a shape
 * when the hues are gone. */

/* Two families, vendored beside the two scripts rather than fetched from a
   font host: this page is opened at the moment the network everybody is on is
   least trustworthy, and it should owe nothing to a third party to draw
   itself. Their licences (SIL OFL, both, in fonts/) travel with the files and
   are served beside them, which is what makes carrying them here allowed. Only
   the latin subset of each is taken, so nothing here pays for alphabets these
   pages never set.

   Schibsted Grotesk is the visitor's page and IBM Plex is the console — one
   variable file each, plus the mono the console sets keys and addresses in.

   The URLs are the plain ones; the handler rewrites them to the hashed paths
   the files are actually served at. Every face names a real fallback stack and
   swaps, so a blocked or slow font degrades into system type rather than into
   nothing — a status page that renders blank while a font is fetched has
   failed at the only moment it matters. */
@font-face {
	font-family: "Schibsted Grotesk";
	src: url("/static/fonts/schibsted-grotesk-var-latin.4c8b93f431d4.woff2") format("woff2");
	font-weight: 400 600;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "IBM Plex Sans";
	src: url("/static/fonts/plex-sans-var-latin.e2291e842cf5.woff2") format("woff2");
	font-weight: 400 700;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "IBM Plex Mono";
	src: url("/static/fonts/plex-mono-400-latin.08949f728dc5.woff2") format("woff2");
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}

:root {
	color-scheme: light dark;

	/* The ground is cool and slightly blue, and the cards are white on it. A
	 * neutral grey ground under a blue accent turns the accent purple to the
	 * eye; this one agrees with it. */
	--paper: #eef2f6;
	--surface: #ffffff;
	--surface-sunk: #e6ecf3;

	--ink: #14202e;
	--ink-strong: #0c1723;
	--ink-soft: #4a5870;
	--ink-faint: #5f6d85;

	--rule: #d3dbe4;
	--rule-soft: #e2e8ee;

	/* The one rule on the page that is not a hairline. It closes the masthead
	 * and nothing else, so weight alone says where the document starts —
	 * which survives a photocopier, where a tint would not. It stays ink
	 * rather than becoming the accent for the same reason. */
	--rule-heavy: #14202e;

	/* A card is held by a line rather than by a shadow, and the line is thick
	 * enough to be seen by somebody who cannot see a hairline. */
	--line: 1.5px;

	/* The accent. It is spent on links, on the mark under the way the reader
	 * is standing on, on the console's rail, and on the button that is the
	 * primary action of a form. Nowhere else — and never on a level. */
	--accent: #0b5fa5;
	--accent-strong: #084a82;
	--accent-tint: #e4edf6;
	--accent-line: #b9d0e6;

	/* The focus ring is deliberately not the accent — a ring in the same blue
	 * as the links is a ring that has to be looked for, and on this page it
	 * would also be a ring the levels have to be told apart from. Orange
	 * because it is the one hue left that is neither a level nor the accent:
	 * 3.29:1 on the ground, 3.71:1 on a card and 3.12:1 on the sunk surface,
	 * where a focus indicator is asked for 3:1. */
	--focus: #bf7300;

	/* The band the console's rail is drawn on: this is the administration, and
	 * it is not the page. It is the one filled area the accent gets, and it is
	 * behind the guard where no level is being reported. */
	--band: #0c3d68;
	--band-here: #114f85;
	--band-ink: #e9f1f8;
	--band-soft: #a8c3dc;
	--band-line: #1e5183;

	/* The four levels and the two the strip adds. Their lightness differs as
	 * much as their hue, so a greyscale print still separates them: down is
	 * darkest, then maintenance, operational, unknown, degraded, and a day
	 * nothing is known about is the palest thing on the strip.
	 *
	 * Amber is two tokens, and that is not a decoration. To stay clear of
	 * green in greyscale it has to be light; to carry a thirteen-pixel word at
	 * 4.5:1 it has to be dark; no single amber is both. So --degraded draws the
	 * mark, where 3:1 is what is asked of a graphic, and --degraded-ink sets
	 * the word beside it at 5.0:1. They are the same signal, printed twice. */
	--operational: #2f6f4a;
	--degraded: #a97400;
	--degraded-ink: #8a5f00;
	--down: #a32d24;
	--unknown: #5f6b78;
	--maintenance: #4a6076;
	--nodata: #8b95a1;

	--tint-operational: #e4efe8;
	--tint-degraded: #f7efdb;
	--tint-down: #f9e8e6;

	--sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
	--mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, "JetBrains Mono", "DejaVu Sans Mono", Menlo, Consolas, monospace;

	/* Six sizes and two display steps, and every rule spends one of them. */
	--fs-xs: 0.75rem;
	--fs-sm: 0.8125rem;
	--fs-md: 0.9375rem;
	--fs-base: 1.0625rem;
	--fs-lg: 1.1875rem;
	--fs-xl: 1.4375rem;

	--verdict: clamp(1.9rem, 7vw, 2.9rem);

	/* The wordmark sits between the scale and the verdict: plainly the second
	 * largest thing on the page, and plainly not competing with the first. */
	--wordmark: clamp(1.3rem, 2.6vw, 1.6rem);

	/* Radii by role: what is pressed, what holds one thing, what holds a
	 * section of the page, and what should read as a pill. */
	--r-sm: 6px;
	--r: 10px;
	--r-lg: 16px;
	--r-pill: 999px;

	/* The rhythm. One number between the page's own blocks and one inside the
	 * things they are made of, so the page keeps a beat instead of each block
	 * choosing its own. */
	--rhythm: 1.5rem;
	--pad: 1.15rem;

	/* The smallest a thing you press may be. */
	--target: 44px;

	/* How wide the visitor's page is allowed to get, padding included. The
	 * design's proportions rest on this number: ninety marks across seven
	 * hundred and thirty-two pixels of column is what makes a day six pixels
	 * wide, which is the smallest a mark can be and still be a shape. */
	--width: 70em;
}

@media (prefers-color-scheme: dark) {
	:root {
		--paper: #0d141c;
		--surface: #16202b;
		--surface-sunk: #101922;

		--ink: #e3e9ef;
		--ink-strong: #f2f6fa;
		--ink-soft: #9aa9bd;
		--ink-faint: #8b9ab0;

		--rule: #2a3846;
		--rule-soft: #22303c;

		/* Ink itself would glare at two pixels across a dark page. This is
		 * still unmistakably heavier than the hairline above it. */
		--rule-heavy: #46586b;

		--accent: #7cb8ec;
		--accent-strong: #a6d0f7;
		--accent-tint: #223a50;
		--accent-line: #274156;

		/* 9:1 on the ground here, where the light theme's darker orange would
		 * be the one thing on a dark page nobody could see. */
		--focus: #f0a63a;

		--band: #0a121b;
		--band-here: #16222e;
		--band-ink: #e3e9ef;
		--band-soft: #9aa9bd;
		--band-line: #22303c;

		--operational: #58b07f;
		--degraded: #d8a53c;
		--degraded-ink: #d8a53c;
		--down: #e07a6f;
		--unknown: #8f9aa5;
		--maintenance: #7f9cb8;
		--nodata: #616b76;

		--tint-operational: #12261d;
		--tint-degraded: #29220f;
		--tint-down: #2a1a19;
	}
}

*,
*::before,
*::after {
	box-sizing: border-box;
}

html {
	-webkit-text-size-adjust: 100%;
}

body {
	margin: 0;
	background: var(--paper);
	color: var(--ink);
	font-family: var(--sans);
	font-size: var(--fs-base);
	line-height: 1.55;
	-webkit-font-smoothing: antialiased;
}

h1,
h2,
h3 {
	margin: 0;
	font-weight: 600;
	line-height: 1.2;
	letter-spacing: -0.01em;
	color: var(--ink-strong);
}

p {
	margin: 0;
	max-width: 62ch;
}

ul,
ol {
	margin: 0;
	padding: 0;
	list-style: none;
}

/* A link carries its underline at rest rather than only under the pointer.
 * Colour alone cannot be asked to do the job: the accent sits 2.5:1 from the
 * ink beside it in the light theme and 1.73:1 in the dark, where the standard
 * asks 3:1 before colour may identify a link on its own. Thin at rest so a
 * column of them does not read as ruled, solid under the pointer. What is left
 * bare is what is recognised by where it sits rather than by what it looks
 * like: the masthead, the trail, the rail, and anything drawn as a button. */
a {
	color: var(--accent);
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
}

a:hover {
	color: var(--accent-strong);
	text-decoration-thickness: 2px;
}

:focus-visible {
	outline: 3px solid var(--focus);
	outline-offset: 2px;
	border-radius: var(--r-sm);
}

/* Levels ------------------------------------------------------------------
 *
 * The vocabulary both rooms share: a shape, a word and a colour, in that order
 * of importance. It is drawn from tokens each room defines for itself, so the
 * same badge comes out in the console's palette in the console and in the
 * design's on the visitor's page — and it is a shape and a word before it is
 * a colour, so it survives a reader who cannot separate red from green and it
 * survives a photocopier. */

.shape {
	width: 0.85rem;
	height: 0.85rem;
	flex: none;
}

.level-operational .shape {
	fill: var(--operational);
}

.level-degraded .shape {
	fill: var(--degraded);
}

.level-down .shape {
	fill: var(--down);
}

.level-unknown .shape {
	fill: var(--unknown);
}

.level-maintenance .shape {
	fill: var(--maintenance);
}

.badge {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	font-size: var(--fs-sm);
	font-weight: 600;
	white-space: nowrap;
}

.level-operational.badge {
	color: var(--operational);
}

.level-degraded.badge {
	color: var(--degraded-ink);
}

.level-down.badge {
	color: var(--down);
}

.level-unknown.badge {
	color: var(--unknown);
}

.level-maintenance.badge {
	color: var(--maintenance);
}

/* The visitor's page ------------------------------------------------------
 *
 * Two supplied designs, implemented rather than reinterpreted: a 780-pixel
 * column between a dark band and a light one, the tab the reader is standing
 * on cut into the paper, one row per service with ninety marks under it, and
 * the checks folded away behind a <details> that opens with no script at all.
 *
 * The palette is the design's own, and it is scoped here rather than at the
 * root because the console is drawn from the same sheet in a different one —
 * a cool blue-grey ground under IBM Plex, where this is white paper under
 * Schibsted Grotesk. Two rooms, two grounds, one stylesheet.
 *
 * Colour is never the only signal. Every dot has its word printed beside it,
 * the dot for a level nobody has reported is hollow rather than merely pale,
 * and the strip is redrawn in fills and hatches under @media print — where
 * the page is photocopied into a post-mortem and the hues go with it. */

.visitor {
	--ink: #171b20;
	--ink-2: #565e67;
	--ink-3: #8a929b;

	--paper: #ffffff;
	--rule: #e3e6ea;
	--rule-soft: #eceff2;
	--wash: #f3f4f6;

	/* The band the masthead is drawn on, and the two inks that sit on it. It
	 * is a token of its own rather than --ink reused, because in the dark
	 * theme the ink is nearly white and a masthead painted in it would be a
	 * white bar across the top of a black page. */
	--band: #171b20;
	--on-band: #ffffff;
	--on-band-2: #b8bec5;

	/* Every level twice: the dot, and the mark on the strip. They differ
	 * because they are read at different sizes — a twelve-pixel dot with a
	 * word beside it can afford to be dark, and a six-pixel mark in a row of
	 * ninety has to hold its shape against the marks next to it. */
	--up: #1b6f44;
	--up-bar: #4db07e;
	--deg: #9c5a0a;
	--deg-bar: #e19a2e;
	--down: #a9271f;
	--down-bar: #d9534b;
	--none: #8e969e;
	--none-bar: #e6e9ed;

	/* Planned work is not a level and is not held against a service, so it is
	 * neither of the two colours that mean something is wrong. The design
	 * draws no such day; this is the one token added to its set. */
	--plan: #4a6076;
	--plan-bar: #93a8bb;

	/* What the shared level vocabulary resolves to in this room, so a badge on
	 * an incident's own page is drawn from the same palette as the dots. */
	--operational: var(--up);
	--degraded: var(--deg);
	--degraded-ink: var(--deg);
	--unknown: var(--none);
	--maintenance: var(--plan);

	/* The shared names the base rules reach for. */
	--ink-strong: var(--ink);
	--ink-soft: var(--ink-2);
	--ink-faint: var(--ink-3);
	--surface: var(--paper);
	--surface-sunk: var(--wash);

	/* One typeface, at three weights out of one variable file. The fallback is
	 * a real stack rather than a bare sans-serif: a blocked or slow font has
	 * to degrade into system type, because a status page that renders blank
	 * while a font is fetched has failed at the only moment it exists for. */
	--sans: "Schibsted Grotesk", system-ui, -apple-system, "Segoe UI", Roboto,
		"Helvetica Neue", Arial, sans-serif;

	/* The design's scale, in rem so a reader who has set a larger default gets
	 * one, and so @media print can take the whole page down with one number. */
	--fs-xs: 0.78125rem;
	--fs-sm: 0.8125rem;
	--fs-meta: 0.84375rem;
	--fs-md: 0.875rem;
	--fs-base: 0.9375rem;
	--fs-lg: 1.0625rem;
	--fs-xl: 1.3125rem;

	background: var(--paper);
	color: var(--ink);
	font-family: var(--sans);
	font-size: 1rem;
	line-height: 1.5;
}

/* Derived from the design's own tokens rather than invented beside them: the
 * lightness of each is turned over and the hue kept, so the page reads as the
 * same page with the lamp off. The two exceptions are the band, which cannot
 * be the ink here, and the strip's marks — on a dark ground the fills have to
 * be brighter than what is behind them rather than darker. */
@media (prefers-color-scheme: dark) {
	.visitor {
		--ink: #e6eaee;
		--ink-2: #a4adb7;
		--ink-3: #7d8792;

		--paper: #10151a;
		--rule: #262d35;
		--rule-soft: #1d232a;
		--wash: #171d23;

		--band: #070a0d;
		--on-band: #ffffff;
		--on-band-2: #b0b8c1;

		--up: #62c193;
		--up-bar: #3f9a70;
		--deg: #e2ab55;
		--deg-bar: #c1882c;
		--down: #ee8b82;
		--down-bar: #bf564e;
		--none: #7d8792;
		--none-bar: #282f37;

		--plan: #8fabc4;
		--plan-bar: #5c778f;
	}
}

/* Layout ------------------------------------------------------------------ */

.page {
	display: flex;
	flex-direction: column;

	/* So the footer sits at the bottom of the window rather than halfway up it
	 * on a page with one service on it. dvh after vh, so a phone's collapsing
	 * browser chrome does not leave a gap the size of the toolbar. */
	min-height: 100vh;
	min-height: 100dvh;
}

/* The column, and the only thing that decides how wide this page is. The bands
 * above and below run the full width; what is written on them lines up with
 * what is written between them. */
.visitor .wrap {
	width: 100%;
	max-width: var(--width);
	margin: 0 auto;
	padding: 0 24px;
}

.visitor main {
	flex: 1;
	padding-bottom: 40px;
}

/* The masthead ------------------------------------------------------------
 *
 * A dark band with the installation's name at one end and the two destinations
 * at the other, standing on its lower edge. The one the reader is on is drawn
 * in the paper's own colour with its top corners turned, so it reads as the
 * top of the sheet below rather than as a highlighted link — which is the
 * whole reason the band is dark. */

.visitor .masthead {
	background: var(--band);
	color: var(--on-band);
}

.visitor .masthead .wrap {
	display: flex;
	align-items: stretch;
	justify-content: space-between;
	gap: 1rem;
	height: 60px;
}

.visitor .wordmark {
	display: flex;
	align-items: center;
	color: var(--on-band);
	font-size: var(--fs-lg);
	font-weight: 600;
	letter-spacing: -0.01em;
	text-decoration: none;
	white-space: nowrap;
}

.visitor .wordmark:hover {
	color: var(--on-band);
	text-decoration: underline;
	text-underline-offset: 4px;
}

.visitor .ways {
	display: flex;
	align-items: flex-end;
}

.visitor .ways ul {
	display: flex;
	gap: 4px;
}

.visitor .ways a {
	display: block;
	padding: 11px 16px;
	color: var(--on-band-2);
	font-size: var(--fs-base);
	text-decoration: none;
	border-radius: 8px 8px 0 0;
}

.visitor .ways a:hover {
	color: var(--on-band);
}

.visitor .ways a[aria-current="page"] {
	background: var(--paper);
	color: var(--ink);
}

.visitor .ways a:focus-visible {
	outline-offset: -3px;
}

/* Type --------------------------------------------------------------------
 *
 * One line of small grey type says when the page was last built and, once the
 * script has read the reader's own clock, how long ago that was. */

.visitor .meta {
	margin: 24px 0 8px;
	font-size: var(--fs-meta);
	color: var(--ink-3);
}

.visitor .empty {
	margin: 24px 0;
	color: var(--ink-2);
}

/* Times are set in the page's own face with tabular figures rather than in a
 * monospace: the console prints columns of them and wants the second, this
 * page prints them inside sentences and wants the first. */
.visitor time {
	font-family: inherit;
	font-size: inherit;
	font-variant-numeric: tabular-nums;
	color: inherit;
}

/* The relative reading the script adds, and only where the markup asked for
 * one. It carries its own comma, so the sentence reads as a sentence with the
 * script and as a sentence without it. */
.visitor .ago {
	margin-left: 0;
	font-size: inherit;
	color: inherit;
}

.visitor .ago::before {
	content: ", ";
}

.visitor a {
	color: var(--ink);
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
}

.visitor a:hover {
	color: var(--ink);
	text-decoration-thickness: 2px;
}

/* Levels ------------------------------------------------------------------
 *
 * A dot and a word, always both. The dot for a level nobody has reported is
 * hollow rather than a pale fill, so the one state that means *we do not know*
 * is the one state with a hole in it. */

.visitor .dot {
	display: inline-block;
	flex: none;
	width: 12px;
	height: 12px;
	border-radius: 50%;
	background: var(--none);
}

.visitor .dot-operational {
	background: var(--up);
}

.visitor .dot-degraded {
	background: var(--deg);
}

.visitor .dot-down {
	background: var(--down);
}

.visitor .dot-maintenance {
	background: var(--plan);
}

.visitor .dot-unknown {
	background: none;
	border: 2px solid var(--none);
}

.visitor .state {
	display: flex;
	align-items: center;
	gap: 9px;
	font-size: var(--fs-base);
	font-weight: 400;
	letter-spacing: 0;
	color: var(--ink-2);
	white-space: nowrap;
}

/* An open incident --------------------------------------------------------
 *
 * The design does not draw this state, so it is drawn in the design's own
 * language: the footer's band, a rule down the leading edge in the level's own
 * colour, and the service head's type. There is no card here because there are
 * no cards on this page. */

.visitor .open {
	margin: 18px 0 6px;
	padding: 14px 16px;
	background: var(--wash);
	border-left: 3px solid var(--none);
	border-radius: 0 6px 6px 0;
}

.visitor .open-operational {
	border-left-color: var(--up-bar);
}

.visitor .open-degraded {
	border-left-color: var(--deg-bar);
}

.visitor .open-down {
	border-left-color: var(--down-bar);
}

.visitor .open-name {
	margin: 0;
	font-size: var(--fs-lg);
	font-weight: 500;
	letter-spacing: -0.01em;
	color: var(--ink);
}

.visitor .open-name a {
	color: inherit;
	text-decoration-color: var(--ink-3);
}

.visitor .open-state {
	margin: 5px 0 0;
	font-size: var(--fs-md);
	color: var(--ink-2);
}

.visitor .open-state .dot {
	vertical-align: -1px;
	margin-right: 7px;
}

.visitor .open-said {
	margin: 11px 0 0;
	white-space: pre-wrap;
	color: var(--ink);
}

.visitor .open-when {
	margin: 4px 0 0;
	font-size: var(--fs-sm);
	color: var(--ink-3);
}

/* One service -------------------------------------------------------------
 *
 * The head, the strip and the figure are the answer; the checks are there for
 * whoever needs the next question answered, and they fold.
 *
 * It is a <details>, so it needs no script. Everything that has to stay
 * visible while it is closed lives inside the <summary>, because that is the
 * only part of a closed <details> a browser draws — which is why the summary
 * is a grid rather than a row. The cost is that the strip is part of what the
 * summary announces; the gain is a page that is complete with nothing loaded,
 * which is the bargain this page is built on. */

.visitor .svc {
	padding: 20px 0 12px;
}

.visitor .svc + .svc {
	border-top: 1px solid var(--rule);
}

.visitor .svc-head {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto auto 16px;
	align-items: center;
	column-gap: 14px;
	padding: 8px 10px;
	margin: 0 -10px;
	border-radius: 6px;
	cursor: pointer;
	list-style: none;
}

.visitor .svc-head::-webkit-details-marker {
	display: none;
}

.visitor .svc-head::marker {
	content: "";
}

.visitor .svc-head:hover {
	background: var(--wash);
}

.visitor .svc-name {
	grid-column: 1;
	grid-row: 1;
	margin: 0;
	font-size: var(--fs-xl);
	font-weight: 500;
	letter-spacing: -0.01em;
	color: var(--ink);
	min-width: 0;
}

.visitor .svc-head .state {
	grid-column: 2;
	grid-row: 1;
}

.visitor .hint {
	grid-column: 3;
	grid-row: 1;
	margin-top: 0;
	font-size: var(--fs-sm);
	color: var(--ink-3);
	white-space: nowrap;
}

.visitor .chev {
	grid-column: 4;
	grid-row: 1;
	width: 16px;
	height: 16px;
	color: var(--ink-3);
	transition: transform 0.15s;
}

.visitor details[open] > .svc-head .chev,
.visitor details[open] > .inc-head .chev {
	transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
	.visitor .chev {
		transition: none;
	}
}

/* Planned work that has not started, on the row under the name — inside the
 * summary, which is the only part of a closed fold a browser draws. A visitor
 * scanning the page on a Tuesday is told the service goes down on Thursday
 * without opening anything.
 *
 * It is deliberately not a level. No dot: a dot on this page means a state,
 * and the state beside it is what the service actually is — planned work does
 * not make a service degraded and must not be drawn as though it did. The mark
 * is the strip's own square in the planned colour, which is the mark this work
 * will leave on the history when the day comes, and the words carry it with
 * the colour switched off. */

.visitor .ahead {
	grid-column: 1 / -1;
	grid-row: 2;
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: 0 7px;
	margin-top: 8px;
	font-size: var(--fs-sm);
	color: var(--plan);
}

.visitor .ahead-mark {
	flex: none;
	align-self: center;
	width: 8px;
	height: 8px;
	border-radius: 2px;
	background: var(--plan-bar);
}

.visitor .svc-head .bar {
	grid-column: 1 / -1;
	grid-row: 3;
	margin: 14px 0 8px;
}

.visitor .svc-head .axis {
	grid-column: 1 / -1;
	grid-row: 4;
}

/* The strip ---------------------------------------------------------------
 *
 * Ninety marks, one a day, oldest first — rendered here rather than assembled
 * in the browser, each carrying its own day and what happened on it. A solid
 * run with one notch in it says more about reliability than any paragraph
 * would, which is why this is the one graphic on the page. */

.visitor .bar {
	display: flex;
	gap: 2px;
	height: 16px;
}

.visitor .mark {
	flex: 1;
	min-width: 1px;
	border-radius: 2px;
	background: var(--none-bar);
}

.visitor .mark-operational {
	background: var(--up-bar);
}

.visitor .mark-degraded {
	background: var(--deg-bar);
}

.visitor .mark-down {
	background: var(--down-bar);
}

.visitor .mark-maintenance {
	background: var(--plan-bar);
}

/* The window at one end, today at the other, and the figure between them —
 * which is the only place the figure appears, so it can never disagree with
 * the marks it is written under. */
.visitor .axis {
	display: flex;
	justify-content: space-between;
	gap: 12px;
	font-size: var(--fs-xs);
	color: var(--ink-3);
}

.visitor .axis .figure {
	color: var(--ink-2);
}

/* A check's axis carries one thing — its figure — so there are no two ends for
 * it to sit between. It reads under the start of the bar it belongs to, as a
 * caption does, rather than at the far right where it looks like a number that
 * escaped from the row above.
 *
 * display:block rather than the flex row the service's axis needs, and a full
 * width with it. A flex container is already block-level, so this changes
 * nothing on its own — it is here so that the figure takes its own line for a
 * reason that is stated rather than inherited, and would go on doing so if
 * anything above it ever became a flex or grid row. */
.visitor .axis-one {
	display: block;
	width: 100%;
	text-align: left;
}

/* The checks ---------------------------------------------------------------
 *
 * Under the fold, held by a rule down their left rather than by a box. Each
 * carries its own strip and its own figure: the service's bar answers whether
 * this was usable, and a check's answers which piece was not. */

.visitor .checks {
	margin: 18px 0 6px;
	padding-left: 18px;
	border-left: 2px solid var(--rule);
}

/* Two columns: what the check is on the left, how it has been on the right.
 *
 * The name, its state and when it last reported are read; the bar is looked at.
 * Putting the looking on one side means the eye runs down a column of bars
 * comparing them to each other rather than hunting for each one under its own
 * paragraph. The bar and its figure travel together — the figure is a caption
 * on those marks and belongs under them, not under the name. */
.visitor .check {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
	align-items: start;
	column-gap: 18px;
	padding: 10px 0 14px;
}

/* The head and the bar share the first row, so the name, the word and the marks
 * read as one line across the row — which is the line a reader scans. What the
 * check has said since goes underneath on the left, and the figure underneath
 * on the right, as a caption on the marks above it. */
.visitor .check-head {
	grid-column: 1;
	grid-row: 1;
}

.visitor .check .bar {
	grid-column: 2;
	grid-row: 1;

	/* Centred against the head's line rather than sat on its top edge: a bar
	 * ten pixels tall beside a name twenty high is level with neither its top
	 * nor its bottom, and only the middle reads as the same line. */
	align-self: center;
	margin: 0;
}

.visitor .check .note,
.visitor .check .said {
	grid-column: 1;
}

.visitor .check .axis-one {
	grid-column: 2;
	grid-row: 2;
}

.visitor .check + .check {
	border-top: 1px solid var(--rule);
}

.visitor .check-head {
	display: flex;
	align-items: center;
	gap: 14px;
}

.visitor .check-name {
	flex: 1;
	min-width: 0;
	font-size: var(--fs-base);
	font-weight: 500;
}

.visitor .check .state {
	font-size: var(--fs-md);
}

.visitor .check .dot {
	width: 9px;
	height: 9px;
}

.visitor .check .bar {
	height: 10px;
}

/* The reporter's own words, and when they last said anything. The design draws
 * neither; both are kept, because a note is the only place on this page where
 * whoever is measuring gets to say what they saw, and silence has to read as
 * silence rather than as an absence of news. They are set quiet enough that
 * the row is still the name, the word and the bar. */
.visitor .note,
.visitor .said {
	margin: 4px 0 0;
	font-size: var(--fs-sm);
	color: var(--ink-3);
}

.visitor .note {
	white-space: pre-wrap;
	color: var(--ink-2);
}

/* The schedule ------------------------------------------------------------
 *
 * Under the checks, still inside the fold: the work running now and the work
 * ahead, soonest first. Planned work opens no incident, so this is the only
 * place a visitor is told about it before it starts.
 *
 * It is held by a rule down its left in the planned colour, the way the checks
 * are held by one in the ordinary rule colour — the two sections under a fold
 * are told apart by their edge rather than by a box, because there are no
 * boxes on this page. */

.visitor .plan {
	margin: 14px 0 6px;
	padding-left: 18px;
	border-left: 2px solid var(--plan-bar);
}

.visitor .plan-head {
	margin: 0 0 8px;
	font-size: var(--fs-sm);
	font-weight: 500;
	color: var(--plan);
}

.visitor .plan-item + .plan-item {
	margin-top: 10px;
	padding-top: 10px;
	border-top: 1px solid var(--rule-soft);
}

.visitor .plan-name {
	margin: 0;
	font-size: var(--fs-base);
	font-weight: 500;
	color: var(--ink);
}

.visitor .plan-when {
	margin: 2px 0 0;
	font-size: var(--fs-sm);
	color: var(--ink-2);
}

.visitor .plan-about {
	margin: 4px 0 0;
	font-size: var(--fs-sm);
	white-space: pre-wrap;
	color: var(--ink-2);
}

.visitor .plan-checks {
	margin: 2px 0 0;
	font-size: var(--fs-xs);
	color: var(--ink-3);
}

/* Past incidents ----------------------------------------------------------
 *
 * Grouped by the month they started in, each one a line — severity, date,
 * name, how long it lasted — that opens to its updates. Same fold, same rules,
 * same absence of script. */

.visitor .month {
	padding: 22px 0 8px;
}

.visitor .month + .month {
	border-top: 1px solid var(--rule);
}

.visitor .month h2 {
	margin: 0 0 4px;
	font-size: var(--fs-base);
	font-weight: 500;
	color: var(--ink-2);
}

.visitor .inc {
	padding: 8px 0 12px;
}

.visitor .inc + .inc {
	border-top: 1px solid var(--rule);
}

.visitor .inc-head {
	display: grid;
	grid-template-columns: 12px 56px minmax(0, 1fr) auto 16px;
	align-items: center;
	column-gap: 14px;
	padding: 8px 10px;
	margin: 0 -10px;
	border-radius: 6px;
	cursor: pointer;
	list-style: none;
}

.visitor .inc-head::-webkit-details-marker {
	display: none;
}

.visitor .inc-head::marker {
	content: "";
}

.visitor .inc-head:hover {
	background: var(--wash);
}

/* The severity mark is drawn in the strip's colours rather than the dot's: it
 * stands alone here with no word beside it, and the date, the name and the
 * summary line under them are what say what happened. */
.visitor .sev {
	grid-column: 1;
	grid-row: 1;
	width: 12px;
	height: 12px;
	border-radius: 50%;
	background: var(--none-bar);
}

.visitor .sev-operational {
	background: var(--up-bar);
}

.visitor .sev-degraded {
	background: var(--deg-bar);
}

.visitor .sev-down {
	background: var(--down-bar);
}

.visitor .inc-head time {
	grid-column: 2;
	grid-row: 1;
	font-size: var(--fs-md);
	color: var(--ink-2);
	white-space: nowrap;
}

.visitor .inc-name {
	grid-column: 3;
	grid-row: 1;
	margin: 0;
	min-width: 0;
	font-size: var(--fs-lg);
	font-weight: 500;
	letter-spacing: -0.01em;
	color: var(--ink);
}

.visitor .dur {
	grid-column: 4;
	grid-row: 1;
	font-size: var(--fs-md);
	color: var(--ink-2);
	white-space: nowrap;
	font-variant-numeric: tabular-nums;
}

.visitor .inc-meta {
	grid-column: 3 / -1;
	grid-row: 2;
	font-size: var(--fs-md);
	color: var(--ink-2);
}

/* The updates. The tag on the first and the last is derived from the incident
 * itself and is never stored: opening is Investigating, closing is Resolved,
 * and an administrator's message in between is its time and its words with no
 * tag at all — a stored status could sit on an incident contradicting it, and
 * this page would repeat the lie. */
.visitor .updates {
	margin: 14px 0 4px 96px;
	padding: 2px 0 2px 18px;
	border-left: 2px solid var(--rule);
}

.visitor .updates li {
	display: grid;
	grid-template-columns: 44px 96px minmax(0, 1fr);
	gap: 12px;
	padding: 6px 0;
	font-size: var(--fs-md);
}

.visitor .updates time {
	color: var(--ink-3);
}

.visitor .updates .st {
	font-weight: 500;
	color: var(--ink);
}

.visitor .updates .tx {
	color: var(--ink-2);
	white-space: pre-wrap;
}

.visitor .corrected {
	margin-left: 0.4em;
	color: var(--ink-3);
}

.visitor .updates li.more {
	grid-template-columns: 152px minmax(0, 1fr);
	padding-top: 10px;
}

.visitor .updates li.more a {
	grid-column: 2;
	font-size: var(--fs-sm);
	color: var(--ink-2);
}

.visitor .pager {
	display: flex;
	justify-content: space-between;
	gap: 1rem;
	margin-top: 22px;
	padding-top: 14px;
	border-top: 1px solid var(--rule);
	font-size: var(--fs-md);
	color: var(--ink-3);
}

/* One incident's own record, and the pages that say one thing ------------- */

.visitor .title {
	margin: 24px 0 4px;
	font-size: var(--fs-xl);
	font-weight: 500;
	letter-spacing: -0.01em;
	text-wrap: balance;
}

.visitor .lede {
	margin: 10px 0 0;
	font-size: var(--fs-base);
	color: var(--ink-2);
}

.visitor .facts {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 6px 20px;
	margin: 16px 0 0;
	padding: 14px 0;
	border-top: 1px solid var(--rule);
	border-bottom: 1px solid var(--rule);
	font-size: var(--fs-md);
}

.visitor .facts dt {
	color: var(--ink-3);
}

.visitor .facts dd {
	margin: 0;
	color: var(--ink-2);
}

.visitor .timeline {
	display: flex;
	flex-direction: column;
	gap: 14px;
	margin: 20px 0 0;
	padding: 2px 0 2px 18px;
	border-left: 2px solid var(--rule);
}

.visitor .timeline .who {
	font-size: var(--fs-sm);
	color: var(--ink-3);
}

.visitor .timeline .body {
	margin-top: 2px;
	font-size: var(--fs-base);
	color: var(--ink-2);
	white-space: pre-wrap;
}

.visitor .timeline .change {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 8px;
	margin-top: 2px;
	font-size: var(--fs-base);
}

/* The foot of the page ----------------------------------------------------
 *
 * A light band with two quiet sentences on it, each with one solid thing set
 * inside it. Provenance and contact, and nothing else: what a stranger who has
 * found this page wants to identify, and where to tell somebody it is wrong. */

.footer {
	/* Takes up whatever height is left, which is what pins it down. On a page
	 * taller than the window this is zero and nothing moves. */
	margin-top: auto;
	background: var(--wash);
	border-top: 1px solid var(--rule);
	font-size: var(--fs-md);
	color: var(--ink-2);
}

.visitor .footer .wrap {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: 24px;
	padding: 20px 24px;
}

.visitor .footer-line {
	max-width: 42ch;
}

.visitor .footer a {
	color: var(--ink);
}

/* The provider's name where it is not a link. It is the solid thing in the
 * sentence either way, and without an underline to do it, weight does. */
.visitor .footer-strong {
	color: var(--ink);
	font-weight: 500;
}

/* Print -------------------------------------------------------------------
 *
 * The page is photocopied into post-mortems, so it has to survive a machine
 * that prints no backgrounds and no colour. Every size on the page is in rem,
 * so one number takes the whole of it down to something that fits on a sheet.
 *
 * The strip is the part that would otherwise be lost: on screen the marks are
 * told apart by hue, and in grey they are five shades of the same bar. Here
 * they are told apart by fill and by hatch instead — an outage solid, degraded
 * hatched one way, planned work hatched the other, an ordinary day empty and a
 * day nobody measured dotted. */

@media print {
	html {
		font-size: 12px;
	}

	.visitor {
		background: #fff;
		color: #000;
	}

	.visitor .page {
		min-height: 0;
	}

	.visitor .masthead {
		background: none;
		color: #000;
		border-bottom: 2px solid #000;
	}

	.visitor .masthead .wrap {
		height: auto;
		padding: 8px 0;
	}

	.visitor .wordmark {
		color: #000;
	}

	.visitor .ways,
	.visitor .chev,
	.visitor .pager,
	.visitor .updates li.more {
		display: none;
	}

	/* Paper does not fold. Both spellings are here because browsers disagree
	 * about how a closed <details> hides what is under it, and a printed page
	 * with the checks missing is a printed page with the answer missing. */
	.visitor details:not([open]) > *:not(summary) {
		display: block;
	}

	.visitor details::details-content {
		content-visibility: visible;
		block-size: auto;
	}

	.visitor a,
	.visitor .footer-strong,
	.visitor .footer a {
		color: #000;
	}

	.visitor .footer {
		background: none;
		border-top: 1px solid #999;
		color: #333;
	}

	.visitor .open {
		background: none;
		border-left: 3px solid #333;
	}

	.visitor .bar,
	.visitor .mark {
		-webkit-print-color-adjust: exact;
		print-color-adjust: exact;
	}

	.visitor .mark {
		border: 0.4pt solid #555;
		border-radius: 0;
		background: #fff;
	}

	.visitor .mark-operational {
		background: #fff;
	}

	.visitor .mark-degraded {
		background: repeating-linear-gradient(45deg, #000 0 1px, #fff 1px 3px);
	}

	.visitor .mark-down {
		background: #000;
	}

	.visitor .mark-maintenance {
		background: repeating-linear-gradient(0deg, #000 0 1px, #fff 1px 3px);
	}

	.visitor .mark-nodata {
		background: #fff;
		border-style: dotted;
	}

	/* Planned work on paper. The words say it; what is left to keep is the
	 * mark, which takes the same hatch the strip's planned days take, and the
	 * rule down the schedule, which a machine printing no colour would
	 * otherwise drop to nothing. */
	.visitor .ahead {
		color: #000;
	}

	.visitor .ahead-mark {
		border: 0.4pt solid #555;
		border-radius: 0;
		background: repeating-linear-gradient(0deg, #000 0 1px, #fff 1px 3px);
		-webkit-print-color-adjust: exact;
		print-color-adjust: exact;
	}

	.visitor .plan {
		border-left: 2px solid #333;
	}

	.visitor .plan-head,
	.visitor .plan-when,
	.visitor .plan-about {
		color: #000;
	}

	/* A dot keeps its word beside it, so what it needs on paper is an edge
	 * rather than a hue — and the hollow one stays hollow. */
	.visitor .dot,
	.visitor .sev {
		background: #fff;
		border: 1px solid #000;
		-webkit-print-color-adjust: exact;
		print-color-adjust: exact;
	}

	.visitor .dot-down,
	.visitor .sev-down {
		background: #000;
	}

	.visitor .dot-degraded,
	.visitor .sev-degraded {
		background: repeating-linear-gradient(45deg, #000 0 1px, #fff 1px 3px);
	}
}

/* At a phone's width ------------------------------------------------------
 *
 * The name takes a line of its own and everything under it stays where it was.
 * Nothing is hidden: this page is read one-handed by somebody who is already
 * annoyed, and a strip dropped on a narrow screen is the answer dropped. */

/* On a check's line the distance is the whole answer.
 *
 * "Reported 10 September 2026, 09:10 UTC, 3 minutes ago" says one thing twice,
 * and the half a reader wants is the half at the end: whether this is current.
 * So where the script has painted a distance the absolute reading steps aside
 * and the label with it — "3 minutes ago", or "No report since 3 hours ago"
 * where the line was already saying something about silence.
 *
 * The absolute time is still in the markup and still machine-readable; it is
 * hidden, not dropped. With no script nothing is painted, no rule below
 * matches, and the line reads as it always did — which is the only reason it is
 * safe to hide it at all. */
.visitor .said.painted time,
.visitor .said.painted .label-plain {
	display: none;
}

.visitor .said.painted .ago::before {
	content: none;
}

@media (max-width: 32.5rem) {
	/* One column again: there is no room for two, and the bar is hidden here
	 * anyway. A check's ninety marks at this width are ninety marks two pixels
	 * wide — a smear that says nothing and takes a line to say it. The
	 * service's own bar stays, because that is the shape somebody came to read,
	 * and the check keeps its figure, which is the part still legible this
	 * small. */
	.visitor .check {
		grid-template-columns: minmax(0, 1fr);
	}

	.visitor .check-head,
	.visitor .check .bar,
	.visitor .check .axis-one {
		grid-column: 1;
		grid-row: auto;
	}

	.visitor .check .bar {
		display: none;
	}

	.visitor .ways a {
		padding: 10px 12px;
		font-size: var(--fs-md);
	}

	.visitor .svc-head {
		grid-template-columns: minmax(0, 1fr) auto 16px;
		row-gap: 8px;
	}

	.visitor .svc-name {
		grid-column: 1 / -1;
		grid-row: 1;
		font-size: 1.1875rem;
	}

	.visitor .svc-head .state {
		grid-column: 1;
		grid-row: 2;
	}

	.visitor .hint {
		grid-column: 2;
		grid-row: 2;
	}

	.visitor .chev {
		grid-column: 3;
		grid-row: 2;
	}

	.visitor .ahead {
		grid-row: 3;
		margin-top: 4px;
	}

	.visitor .svc-head .bar {
		grid-row: 4;
		margin: 6px 0 8px;
	}

	.visitor .svc-head .axis {
		grid-row: 5;
	}

	.visitor .inc-head {
		grid-template-columns: 12px minmax(0, 1fr) auto 16px;
		row-gap: 6px;
	}

	.visitor .inc-head time {
		grid-column: 2;
	}

	.visitor .dur {
		grid-column: 3;
	}

	.visitor .inc-head .chev {
		grid-column: 4;
		grid-row: 1;
	}

	.visitor .inc-name {
		grid-column: 1 / -1;
		grid-row: 2;
	}

	.visitor .inc-meta {
		grid-column: 1 / -1;
		grid-row: 3;
	}

	.visitor .updates {
		margin-left: 0;
	}

	.visitor .updates li {
		grid-template-columns: 44px minmax(0, 1fr);
	}

	.visitor .updates .st,
	.visitor .updates .tx {
		grid-column: 2;
	}

	.visitor .updates li.more {
		grid-template-columns: 44px minmax(0, 1fr);
	}
}

/* The console -------------------------------------------------------------
 *
 * The same language, taken denser: the person reading it is signed in and
 * looking for something specific, so the rhythm tightens, the type steps down,
 * the radii come in a notch and the lines go back to a hairline. Nothing else
 * changes — one sheet, one palette, one level vocabulary — which is what stops
 * the two rooms drifting apart.
 *
 * 36px rather than 44px for what is pressed: a console is a table of rows with
 * two tools on each, and it clears the 24px floor comfortably. The visitor's
 * page keeps 44, because it is read one-handed by somebody who is annoyed. */

.console {
	--rhythm: 1rem;
	--pad: 0.85rem;
	--line: 1px;
	--target: 36px;

	--fs-base: 0.9375rem;
	--fs-lg: 1.0625rem;
	--fs-xl: 1.25rem;

	--r-lg: 10px;
	--r: 8px;
	--r-sm: 4px;

	background: var(--surface-sunk);
}

.shell {
	display: flex;
	min-height: 100vh;
	min-height: 100dvh;
	align-items: stretch;
}

/* The rail is the one filled band of accent anywhere in this stylesheet, and
 * it is here rather than on the visitor's page for a reason: behind the guard
 * nothing is reporting a level, so there is nothing for it to shout over. It
 * says, in a colour, that this is the administration and not the page. */
.rail {
	flex: none;
	width: 14rem;
	padding: 1.25rem 1rem;
	background: var(--band);
	color: var(--band-ink);
	border-right: 1px solid var(--band-line);
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	gap: 2rem;

	/* This band is dark in both themes, so the two tokens that are picked for
	 * a light ground are picked again here. The light theme's ring reads
	 * 3.01:1 on this navy, which is the bar rather than clear of it, and its
	 * deep accent reads 1.07:1 — a marker nobody would find. The dark theme's
	 * values are 5.4:1 and 5.2:1 on the same navy. */
	--focus: #f0a63a;
	--accent-strong: #a6d0f7;
}

.rail .brand {
	display: block;
	font-weight: 640;
	letter-spacing: -0.01em;
	color: #ffffff;
	text-decoration: none;
	margin-bottom: 1.25rem;
	font-size: var(--fs-lg);
}

.rail nav li + li {
	margin-top: 0.15rem;
}

.rail nav a {
	display: flex;
	align-items: center;
	min-height: var(--target);
	padding: 0.3rem 0.6rem;
	border-radius: var(--r-sm);
	color: var(--band-soft);
	text-decoration: none;
}

.rail nav a:hover {
	background: var(--band-here);
	color: var(--band-ink);
}

.rail nav a[aria-current="page"] {
	background: var(--band-here);
	color: #ffffff;
	font-weight: 600;
	box-shadow: inset 3px 0 0 var(--accent-strong);
}

.rail-foot {
	font-size: var(--fs-sm);
	color: var(--band-soft);
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
	align-items: flex-start;
}

/* On the band, a link is drawn out of the band's own light rather than in the
 * accent, which on a deep blue is a link hiding in it. */
.rail a.quiet,
.rail button.quiet {
	color: var(--band-ink);
}

.work {
	flex: 1;
	min-width: 0;
	padding: 1.5rem 1.5rem 3rem;
	display: flex;
	flex-direction: column;
	gap: var(--rhythm);
}

.crumbs ol {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem;
	font-size: var(--fs-sm);
	color: var(--ink-faint);
}

.crumbs a {
	color: var(--ink-faint);
	text-decoration: none;
}

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

.crumbs li + li::before {
	content: "/";
	margin-right: 0.4rem;
	color: var(--rule);
}

.notice {
	padding: 0.6rem 0.8rem;
	border-radius: var(--r);
	border: var(--line) solid var(--rule);
	font-size: var(--fs-md);
}

.notice-ok {
	border-color: var(--operational);
	background: var(--tint-operational);
	color: var(--operational);
}

.notice-bad {
	border-color: var(--down);
	background: var(--tint-down);
	color: var(--down);
}

.gate {
	min-height: 100vh;
	min-height: 100dvh;
	display: grid;
	place-items: center;
	padding: 1.5rem;
	background: var(--paper);
}

.gate .card {
	width: min(24rem, 100%);
	background: var(--surface);
	border: var(--line) solid var(--rule);
	border-radius: var(--r-lg);
	padding: 1.5rem;
	display: flex;
	flex-direction: column;
	gap: 0.85rem;
}

/* Buttons ----------------------------------------------------------------- */

button,
.button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.4rem;
	min-height: var(--target);
	font: inherit;
	font-size: var(--fs-md);
	font-weight: 600;
	padding: 0.35rem 0.9rem;
	border: var(--line) solid var(--rule);
	border-radius: var(--r);
	background: var(--surface);
	color: var(--ink);
	cursor: pointer;
	text-decoration: none;
}

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

/* What the form is for. A submit is the primary action of the thing it stands
 * in, and it is the second place the accent is spent — as a fill, because
 * there is one of them and it should be found without being hunted for. */
button[type="submit"] {
	border-color: var(--accent);
	background: var(--accent);
	color: var(--surface);
}

button[type="submit"]:hover {
	border-color: var(--accent-strong);
	background: var(--accent-strong);
	color: var(--surface);
}

/* A tool, not an action: the movers, sign out, cancel, the record. Drawn as
 * the text it is, so a row of them does not read as a row of buttons — but
 * still given the 24 pixels the standard asks of anything pressed, which a
 * bare line of type does not have. */
button.quiet,
.button.quiet {
	border: none;
	background: none;
	padding: 0 0.15rem;
	min-height: 1.5rem;
	color: var(--accent);
	font-size: inherit;
	font-weight: 400;
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
}

button.quiet:hover,
.button.quiet:hover {
	color: var(--accent-strong);
	text-decoration-thickness: 2px;
}

input,
select,
textarea {
	font: inherit;
	font-size: var(--fs-base);
	width: 100%;
	min-height: var(--target);
	padding: 0.4rem 0.6rem;
	border: var(--line) solid var(--rule);
	border-radius: var(--r);
	background: var(--surface);
	color: var(--ink);
}

textarea {
	line-height: 1.5;
}

input:hover,
select:hover,
textarea:hover {
	border-color: var(--accent-line);
}

label {
	display: block;
	font-size: var(--fs-sm);
	color: var(--ink-soft);
	margin-bottom: 0.25rem;
}

table {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--fs-md);
}

th {
	text-align: left;
	font-weight: 600;
	color: var(--ink-faint);
}

th,
td {
	padding: 0.45rem 0.6rem 0.45rem 0;
	border-bottom: 1px solid var(--rule-soft);
	vertical-align: baseline;
}

/* The working area --------------------------------------------------------
 *
 * Tables carry their state in structure rather than in prose, and the only
 * colour is still the levels. */

.page-head {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	justify-content: space-between;
	gap: 0.75rem 1.5rem;
}

.page-head h1 {
	font-size: var(--fs-xl);
}

.page-head .lede,
.sheet > .lede {
	margin-top: 0.3rem;
	font-size: var(--fs-md);
	color: var(--ink-soft);
}

.actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem;
}

/* A sheet is one bounded thing: a table, a list, a group of tools. Where a
 * page has several, the boundary is what separates one question from the
 * next — and it is a line, not a shadow. */

.sheet {
	background: var(--surface);
	border: var(--line) solid var(--rule);
	border-radius: var(--r-lg);
	padding: var(--pad) 1.1rem;
	overflow-x: auto;
}

.sheet-head {
	font-size: var(--fs-base);
	margin-bottom: 0.15rem;
}

.sheet table {
	margin-top: 0.7rem;
}

.sheet tbody tr:last-child th,
.sheet tbody tr:last-child td {
	border-bottom: none;
}

.mono {
	font-family: var(--mono);
	font-size: var(--fs-sm);
}

.addr {
	font-family: var(--mono);
	font-size: var(--fs-sm);
	word-break: break-all;
}

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

/* A reporter's own words in a console table. Scoped, because the visitor's
 * rows carry .said too and mean their own thing by it — one stylesheet serves
 * both sides, so a rule that does not say which side it means reaches both. */
.console .said {
	color: var(--ink-soft);
	white-space: pre-wrap;
}

/* The relay's own answer, on the page that asked it a question. Scoped like
 * .said above, and drawn as a machine's words rather than the console's: this
 * is the one place an adapter's error text is shown, and it should look like
 * what it is. */
.console .relay-said {
	font-family: var(--mono);
	font-size: var(--fs-md);
	margin: 0 0 var(--rhythm);
	padding: 0.7rem 0.8rem;
	background: var(--surface-sunk);
	border: var(--line) solid var(--rule);
	border-radius: var(--r);
	overflow-wrap: anywhere;
	white-space: pre-wrap;
}

/* A pill states a fact about a row that the row's other columns do not: turned
 * off, merged, pinned. It is deliberately colourless — colour is the levels'
 * and nothing else's. */

.pill {
	display: inline-block;
	font-size: var(--fs-xs);
	line-height: 1.3;
	padding: 0.05rem 0.5rem;
	margin-left: 0.35rem;
	border: 1px solid var(--rule);
	border-radius: var(--r-pill);
	color: var(--ink-faint);
	white-space: nowrap;
}

.row-tools {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.6rem;
	white-space: nowrap;
}

/* The same 24 pixels for the links in a row's tools: they are pressed as often
 * as anything on the page and they sit in the densest part of it. */
.row-tools a {
	display: inline-flex;
	align-items: center;
	min-height: 1.5rem;
}

.mover {
	display: flex;
	gap: 0.25rem;
}

.mover button {
	font-family: var(--mono);
	min-width: 1.5rem;
	text-decoration: none;
}

.mover button[disabled] {
	color: var(--rule);
	cursor: default;
	text-decoration: none;
}

/* The switch between two listings of the same thing. */

.switch {
	display: flex;
	gap: 0.9rem;
	font-size: var(--fs-md);
}

.switch a[aria-current="page"] {
	color: var(--ink-strong);
	font-weight: 600;
	text-decoration: underline;
	text-decoration-color: var(--accent);
	text-decoration-thickness: 2px;
	text-underline-offset: 5px;
}

/* The overview grid — every service's level at a glance, one tile each. The
 * level is stated by shape and word as well as by the edge's colour, so the
 * grid reads the same printed. */

.tiles {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
	gap: 0.75rem;
}

.tile {
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
	padding: 0.85rem 0.95rem;
	background: var(--surface);
	border: var(--line) solid var(--rule);
	border-left-width: 4px;
	border-radius: var(--r-lg);
	color: var(--ink);
	text-decoration: none;
}

.tile:hover {
	border-color: var(--accent-line);
}

.tile h2 {
	font-size: var(--fs-lg);
}

.tile-mark {
	display: flex;
	align-items: center;
	gap: 0.35rem;
	font-size: var(--fs-sm);
	font-weight: 600;
}

.tile-foot {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.5rem;
	font-size: var(--fs-sm);
	color: var(--ink-faint);
}

.tile-said {
	font-size: var(--fs-sm);
	color: var(--ink-faint);
}

.tile.level-operational {
	border-left-color: var(--operational);
}

.tile.level-operational .tile-mark {
	color: var(--operational);
}

.tile.level-degraded {
	border-left-color: var(--degraded);
}

.tile.level-degraded .tile-mark {
	color: var(--degraded-ink);
}

.tile.level-down {
	border-left-color: var(--down);
}

.tile.level-down .tile-mark {
	color: var(--down);
}

.tile.level-unknown {
	border-left-color: var(--unknown);
}

.tile.level-unknown .tile-mark {
	color: var(--unknown);
}

/* A tile is a card with a level on it, so hovering must not repaint the edge
 * the level owns. */
.tile:hover.level-operational {
	border-left-color: var(--operational);
}

.tile:hover.level-degraded {
	border-left-color: var(--degraded);
}

.tile:hover.level-down {
	border-left-color: var(--down);
}

.tile:hover.level-unknown {
	border-left-color: var(--unknown);
}

/* The checklist a fresh installation gets. Each step ticks itself, and the
 * whole list goes away once something has reported. */

.checklist ol {
	display: flex;
	flex-direction: column;
	gap: 0.9rem;
	margin-top: 0.8rem;
}

.checklist li {
	display: grid;
	grid-template-columns: 1.75rem 1fr auto;
	gap: 0.75rem;
	align-items: start;
	padding-bottom: 0.9rem;
	border-bottom: 1px solid var(--rule-soft);
}

.checklist li:last-child {
	padding-bottom: 0;
	border-bottom: none;
}

.checklist h3 {
	font-size: var(--fs-base);
}

.checklist p {
	font-size: var(--fs-md);
	color: var(--ink-soft);
	margin: 0.15rem 0 0.5rem;
}

/* The steps are numbered because they are a sequence: nothing reports until a
 * service, a check and a key exist, in that order. */
.step-number {
	display: grid;
	place-items: center;
	width: 1.75rem;
	height: 1.75rem;
	border: 1px solid var(--rule);
	border-radius: var(--r-pill);
	font-family: var(--mono);
	font-size: var(--fs-sm);
	color: var(--ink-faint);
}

.step-state {
	font-size: var(--fs-sm);
	color: var(--ink-faint);
	white-space: nowrap;
}

.checklist li.done .step-number {
	border-color: var(--operational);
	color: var(--operational);
}

.checklist li.done .step-state {
	color: var(--operational);
}

/* The settings grid, and the picker the recipients page opens with. */

.options {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
	gap: 0.75rem;
}

.option {
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
	padding: 0.95rem 1rem;
	background: var(--surface);
	border: var(--line) solid var(--rule);
	border-radius: var(--r-lg);
	color: var(--ink);
	text-decoration: none;
}

.option:hover {
	border-color: var(--accent);
	background: var(--accent-tint);
}

.option h3 {
	font-size: var(--fs-lg);
}

.option p {
	font-size: var(--fs-md);
	color: var(--ink-soft);
}

.option .count {
	margin-top: auto;
	padding-top: 0.4rem;
	font-size: var(--fs-sm);
	color: var(--ink-faint);
}

/* One incident's tools, each with what it does written beside it rather than
 * hidden behind an icon. */

.tool-row {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
	gap: 1rem;
	margin-top: 0.8rem;
}

.tool {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 0.4rem;
}

.tool h3 {
	font-size: var(--fs-base);
}

.tool p {
	font-size: var(--fs-md);
	color: var(--ink-soft);
}

/* Forms ------------------------------------------------------------------- */

.stack {
	display: flex;
	flex-direction: column;
	gap: 0.85rem;
}

.field {
	display: flex;
	flex-direction: column;
}

.hint {
	margin-top: 0.25rem;
	font-size: var(--fs-sm);
	color: var(--ink-faint);
}

/* A checkbox and its label, in the console.
 *
 * It is .checkbox and not .check, and that is not a preference. A service is
 * made of checks, and the visitor's rows carry class="check" — so an unscoped
 * .check rule here reaches them too, and this one laid every check's bar and
 * its figure side by side because a row became a flex row. Two different things
 * cannot share one name in one stylesheet, and the one that means "a tick box"
 * is the one that had to move. */
.checkbox {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	min-height: var(--target);
}

.checkbox input {
	width: 1.1rem;
	height: 1.1rem;
	min-height: 0;
	margin: 0;
	accent-color: var(--accent);
}

.checkbox label {
	margin: 0;
	color: var(--ink);
	font-size: var(--fs-base);
}

.choices {
	border: var(--line) solid var(--rule);
	border-radius: var(--r);
	padding: 0.7rem 0.85rem;
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
	min-width: 0;
}

.choices legend {
	font-size: var(--fs-sm);
	color: var(--ink-soft);
	padding: 0 0.3rem;
}

.choices .hint {
	margin: 0 0 0.2rem;
}

/* One row of the key/value editor a check's properties are written in. It is
 * two boxes side by side and nothing else: the console works with JavaScript
 * off, so rows are not added by a script — a few blank spares are drawn and the
 * empty ones are dropped on save. Narrow enough to stack on a phone. */
.pair {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
	gap: 0.4rem;
}

@media (max-width: 30rem) {
	.pair {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* The sign-in code, read off a mail and typed in. Mono and spaced out, because
 * the one thing it has to survive is being read a character at a time. */
.code {
	font-family: var(--mono);
	font-size: var(--fs-xl);
	letter-spacing: 0.3em;
	text-align: center;
}

/* Destructive, and drawn as it. Declared after the submit above, which it
 * shares specificity with and has to beat: a delete button that came out in
 * the accent would be the primary action of the page it is on. */
button.danger,
.button.danger,
button[type="submit"].danger {
	border-color: var(--down);
	background: var(--surface);
	color: var(--down);
}

button.danger:hover,
.button.danger:hover,
button[type="submit"].danger:hover {
	border-color: var(--down);
	background: var(--tint-down);
	color: var(--down);
}

/* The one moment a key exists in the clear. It is selectable, wrapped, and
 * nowhere else — the page does not redirect away from it, because navigating
 * away is what makes the key unrecoverable and that must be a decision. */

.dump {
	font-family: var(--mono);
	font-size: var(--fs-md);
	margin: 0;
	padding: 0.7rem 0.8rem;
	background: var(--surface-sunk);
	border: var(--line) solid var(--rule);
	border-radius: var(--r);
	overflow-wrap: anywhere;
	white-space: pre-wrap;
	user-select: all;
}

.detail {
	display: flex;
	flex-direction: column;
	gap: 0.2rem;
	font-size: var(--fs-md);
	color: var(--ink-soft);
}

/* Dialogs ----------------------------------------------------------------
 *
 * Every one of these has an ordinary page behind the same address. With htmx
 * the fragment is swapped over the page; without it the link is a navigation
 * and the dialog was never involved. Nothing here needs a script to open or to
 * close. */

.backdrop {
	position: fixed;
	inset: 0;
	display: grid;
	place-items: center;
	padding: 1.5rem;
	background: rgb(6 14 24 / 55%);
	overflow-y: auto;
}

.modal {
	width: min(34rem, 100%);
	max-height: 90vh;
	overflow-y: auto;
	background: var(--surface);
	border: var(--line) solid var(--rule);
	border-radius: var(--r-lg);
	padding: 1.3rem 1.4rem;
	display: flex;
	flex-direction: column;
	gap: 0.85rem;
}

.modal h2 {
	font-size: var(--fs-lg);
}

.modal .lede {
	font-size: var(--fs-md);
	color: var(--ink-soft);
}

/* What the console used to inherit from the visitor's page ----------------
 *
 * These were one set of rules over both rooms while both rooms were drawn the
 * same way. The visitor's page now has a design of its own and its own version
 * of each, so what is left here is the console's half, scoped to it — rather
 * than a shared rule the two would have to keep agreeing about. */

.console .empty,
.console .lede {
	color: var(--ink-soft);
}

.console .pager {
	justify-content: space-between;
}

.console .facts {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 0.35rem 1.25rem;
	padding: var(--pad) 1.35rem;
	background: var(--surface);
	border: var(--line) solid var(--rule);
	border-radius: var(--r-lg);
	font-size: var(--fs-md);
}

.console .facts dt {
	color: var(--ink-faint);
}

.console .facts dd {
	margin: 0;
}

.console .timeline {
	display: flex;
	flex-direction: column;
	gap: 1.1rem;
	padding-left: 1rem;
	border-left: 2px solid var(--rule);
}

.console .timeline > li {
	display: flex;
	flex-direction: column;
	gap: 0.2rem;
}

.console .timeline .who {
	font-size: var(--fs-sm);
	color: var(--ink-faint);
}

.console .timeline .body {
	white-space: pre-wrap;
}

.console .timeline .change {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.45rem;
	font-size: var(--fs-md);
}

.console .timeline .when {
	font-size: var(--fs-sm);
	color: var(--ink-faint);
}

.console .pager {
	display: flex;
	flex-wrap: wrap;
	gap: 0.9rem;
	font-size: var(--fs-md);
	color: var(--ink-faint);
}

.console .empty {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 0.7rem;
	font-size: var(--fs-md);
}

/* Narrow ------------------------------------------------------------------
 *
 * The rail lies down across the top, and everything that was a column becomes
 * a row. Nothing is hidden: a console that drops a tool on a phone is a
 * console somebody has to find a laptop for. */

@media (max-width: 46rem) {
	.shell {
		display: block;
	}

	.rail {
		width: auto;
		border-right: none;
		border-bottom: 1px solid var(--band-line);
		flex-direction: row;
		align-items: center;
		flex-wrap: wrap;
		gap: 0.75rem 1.5rem;
	}

	.rail nav ul {
		display: flex;
		flex-wrap: wrap;
		gap: 0.25rem;
	}

	.rail .brand {
		margin-bottom: 0;
	}

	.work {
		padding: 1.25rem 1rem 2.5rem;
	}

	.checklist li {
		grid-template-columns: 1.75rem 1fr;
	}

	.step-state {
		grid-column: 2;
	}
}

/* Nothing here moves far, and a reader who has asked for less of it gets
 * none. */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		transition-duration: 0.01ms !important;
		animation-duration: 0.01ms !important;
	}
}
