/*
	BellTower operator dashboard — the design direction's token system.

	`docs/design/dashboard-directions.md` is the authority for everything
	in this file, and the shape it asks for is a single layout expressed
	three ways: THREE THEMES, ONE LAYOUT, and every difference between
	them a token. Ocean is the default (cool slate, sans labels, rounded
	cards), Earth is the wall-display instrument panel (warm near-black,
	all-mono, hairlines instead of cards), and Frost is the airy
	near-neutral one that reads best at a distance.

	Three rules govern edits here, and none of them is decoration:

	  - EVERY FIGURE IS SET IN `--mono`, IN EVERY THEME. That is the one
	    rule the direction says never varies: figures are tabular and
	    comparable, and only the labels take the theme's voice.
	  - GEOMETRY IS NOT THEMEABLE. The sidebar width, the column
	    structure and the chart canvas geometry are fixed across all
	    three themes and live in the `:root` block below, deliberately
	    out of reach of the per-theme blocks. A theme that moved the
	    sidebar would stop being a theme.
	  - THE CARD TREATMENT IS A TOKEN, NOT A PARALLEL CLASS TREE. Earth's
	    sidebar sections are transparent with a single top hairline;
	    Ocean's and Frost's are real cards. That difference is carried by
	    `--side-bg` / `--side-border` / `--side-radius` / `--side-top`, so
	    one `.card` rule renders both. `data-treatment` on an individual
	    card overrides the theme's default where a card has to disagree
	    with it; there is no second class hierarchy.

	Dark only. Light is not designed, and if it is ever wanted it is a
	fourth entry in the same table rather than a new mechanism.
*/

/*
	The theme-independent half: the font stacks every theme draws its
	`--font` / `--label-font` from, the geometry that is fixed across all
	three, and the motion timings.

	NOTHING IN THIS BLOCK MAY MOVE INTO A THEME. The 400px sidebar and the
	chart canvas geometry in particular are what make the three themes one
	dashboard rather than three.
*/
:root {
	color-scheme: dark;

	/*	No external assets: system stacks only. */
	--sans:               ui-sans-serif, system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif;
	--mono:               ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

	/*	Fixed geometry — see the rule above. */
	--nav-height:         54px;
	--nav-pad:            24px;
	--page-pad:           20px;
	--body-gap:           20px;
	--sidebar-width:      400px;
	--chart-height:       120px;
	--chart-height-stack: 104px;
	--chart-value:        24px;
	--chart-value-stack:  20px;
	--stack-pad:          14px 2px 16px;

	/*	Motion, from the direction's motion inventory. Four animations,
		and nothing else moves. */
	--softpulse-dot:      3.2s;
	--softpulse-bar:      1.8s;
	--alarmpulse-rail:    1.6s;
	--alarmpulse-lost:    1.2s;
}

/*
	Ocean — the default. Also the fallback for a document that carries no
	`data-theme` at all, which is why it is written as a selector list
	rather than as a fourth copy of the same values.
*/
:root,
:root[data-theme="ocean"] {
	--bg:              #0d1015;
	--chrome:          #111721;
	--panel:           #141922;
	--border:          #1e2632;
	--hair:            #171e28;
	--text:            #dfe6ef;
	--mid:             #a9b6c6;
	--dim:             #7d8a9c;
	--faint:           #5b697c;
	--ghost:           #3b4756;
	--accent:          #6b95e8;
	--ok:              #3fbf8f;
	--alarm:           #e8657a;
	--alarm-bg:        #1e1218;
	--alarm-border:    #45222f;
	--alarm-dim:       #a5788a;
	--caution:         #e8a34f;
	--track:           #1a212c;
	--grid-a:          #1a212c;
	--grid-b:          #26303e;
	--font:            var(--sans);
	--label-font:      var(--sans);
	--label-tracking:  .02em;
	--label-case:      none;
	--label-weight:    600;
	--radius:          6px;
	--panel-radius:    9px;
	--row-pad:         8px 18px;
	--card-pad:        16px 18px 14px;
	--side-pad:        16px 18px;
	--side-gap:        16px;
	--side-bg:         #141922;
	--side-border:     #1e2632;
	--side-top:        #1e2632;
	--side-radius:     9px;
	--col-gap:         16px;
}

/*
	Earth — warm near-black, all-mono, hairlines instead of cards, tight.
	Its sidebar sections are transparent with near-zero horizontal padding
	so they sit flush in the column, which is what makes it read as a
	panel of gauges rather than a recoloured Ocean.
*/
:root[data-theme="earth"] {
	--bg:              #0c0c0b;
	--chrome:          #0f0f0d;
	--panel:           #111110;
	--border:          #201f1c;
	--hair:            #171614;
	--text:            #e8e5dd;
	--mid:             #b8b4aa;
	--dim:             #8a877e;
	--faint:           #57544c;
	--ghost:           #3b3934;
	--accent:          #e0a84f;
	--ok:              #4fc4d6;
	--alarm:           #e0523f;
	--alarm-bg:        #1a0f0c;
	--alarm-border:    #3a1c14;
	--alarm-dim:       #9a6b5e;
	--caution:         #e0a84f;
	--track:           #171614;
	--grid-a:          #171614;
	--grid-b:          #232320;
	--font:            var(--mono);
	--label-font:      var(--mono);
	--label-tracking:  .14em;
	--label-case:      uppercase;
	--label-weight:    500;
	--radius:          3px;
	--panel-radius:    5px;
	--row-pad:         6px 18px;
	--card-pad:        14px 16px 12px;
	--side-pad:        14px 2px 16px;
	--side-gap:        0px;
	--side-bg:         transparent;
	--side-border:     transparent;
	--side-top:        #171614;
	--side-radius:     0px;
	--col-gap:         14px;
}

/*
	Frost — near-neutral cold grey, violet accent, square corners, airy.
	The most legible of the three at distance.
*/
:root[data-theme="frost"] {
	--bg:              #0e1013;
	--chrome:          #12151a;
	--panel:           #171b21;
	--border:          #232830;
	--hair:            #1b1f26;
	--text:            #e6e9ee;
	--mid:             #b3bac4;
	--dim:             #868e9a;
	--faint:           #616874;
	--ghost:           #444b56;
	--accent:          #8d8ce8;
	--ok:              #57c2a8;
	--alarm:           #e2708f;
	--alarm-bg:        #1b141c;
	--alarm-border:    #3f2436;
	--alarm-dim:       #a08094;
	--caution:         #c9a25e;
	--track:           #1e232b;
	--grid-a:          #1a1e25;
	--grid-b:          #282e38;
	--font:            var(--sans);
	--label-font:      var(--sans);
	--label-tracking:  .11em;
	--label-case:      uppercase;
	--label-weight:    600;
	--radius:          0px;
	--panel-radius:    0px;
	--row-pad:         11px 20px;
	--card-pad:        20px 20px 18px;
	--side-pad:        20px;
	--side-gap:        20px;
	--side-bg:         #171b21;
	--side-border:     #232830;
	--side-top:        #232830;
	--side-radius:     0px;
	--col-gap:         20px;
}



/*==============================[  ELEMENTS  ]===============================*/

* {
	box-sizing: border-box;
}

html, body {
	margin: 0;
	padding: 0;
	min-height: 100%;
	background: var(--bg);
	color: var(--text);
	font-family: var(--font);
	font-size: 13px;
	line-height: 1.45;
}

/*
	Every figure on screen, in every theme. The `.mono` class is what the
	views mark a figure with; `dd`, `.numeric` and the tail rows are
	figures by construction.
*/
code, pre, .mono, .numeric, .field-list dd {
	font-family: var(--mono);
	font-variant-numeric: tabular-nums;
}

button, input {
	font: inherit;
	color: inherit;
}

button {
	background: transparent;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	color: var(--mid);
	padding: 5px 12px;
	font-size: 12px;
	cursor: pointer;
}

button:hover {
	color: var(--text);
}

input {
	background: var(--panel);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	color: var(--mid);
	padding: 0 12px;
	height: 36px;
	width: 100%;
	font-size: 12.5px;
}

input:focus {
	border-color: var(--accent);
	outline: none;
}

/*
	Labels take the theme's voice; figures never do — see the header.
*/
.label {
	font-family: var(--label-font);
	font-weight: var(--label-weight);
	letter-spacing: var(--label-tracking);
	text-transform: var(--label-case);
}



/*===============================[  MOTION  ]================================*/

/*
	The whole motion inventory: four animations, and nothing else moves.
	Tail rows deliberately have no entrance animation — several a second
	sliding in is unreadable, so they simply exist.
*/

/*
	The chart series group's continuous drift. Applied by the chart pass,
	which is what wraps the polylines in this group and adds the extra
	point past the right-hand edge that covers the gap the translate
	opens. Declared here because the motion inventory — and the
	reduced-motion rule below — is the token system's business, not the
	chart's.
*/
@keyframes chartscroll {
	from { transform: translateX(0); }
	to   { transform: translateX(-6px); }
}

@keyframes softpulse {
	0%, 100% { opacity: .35; }
	50%      { opacity: 1; }
}

@keyframes alarmpulse {
	0%, 100% { opacity: .25; }
	50%      { opacity: .85; }
}

.chart-series {
	animation: chartscroll 1s linear infinite;
}

/*
	Reduced motion lives at the END of this file, not here — see the
	"REDUCED MOTION" section below. It has to beat compound selectors
	like `.status-dot.offline`, which it cannot do from up here.
*/



/*================================[  SHELL  ]================================*/

.app-shell {
	display: flex;
	flex-direction: column;
	min-height: 100vh;
}

.top-nav {
	display: flex;
	align-items: center;
	gap: 32px;
	height: var(--nav-height);
	padding: 0 var(--nav-pad);
	background: var(--chrome);
	border-bottom: 1px solid var(--border);
}

.wordmark {
	display: flex;
	align-items: center;
	gap: 10px;
}

/*	The only mark on the page: a ring, drawn as a box. */
.brand-mark {
	width: 9px;
	height: 9px;
	border: 2px solid var(--accent);
	border-radius: 50%;
	flex: none;
}

.top-nav .brand {
	font-size: 14px;
	font-weight: 600;
}

.tier-chip {
	background: var(--panel);
	border-radius: var(--radius);
	padding: 1px 7px;
	color: var(--dim);
	font-family: var(--mono);
	font-size: 11px;
}

.top-nav .spacer {
	flex: 1;
}

.top-nav .operator {
	color: var(--dim);
	font-size: 12px;
}

.top-nav-links {
	display: flex;
	gap: 4px;
}

.top-nav-links a {
	color: var(--dim);
	text-decoration: none;
	font-size: 13px;
	padding: 4px 10px;
	border-radius: var(--radius);
}

.top-nav-links a:hover {
	color: var(--text);
}

.top-nav-links a[aria-current] {
	color: var(--text);
	background: var(--panel);
}

/*
	The connection indicator: the single most important element on the
	page, because everything else on it stops meaning anything the moment
	this says the feed is gone.
*/
.feed-status {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	color: var(--mid);
	font-size: 12px;
}

.status-dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	display: inline-block;
	background: var(--ok);
	animation: softpulse var(--softpulse-dot) ease-in-out infinite;
}

.status-dot.offline {
	background: var(--alarm);
	animation-duration: var(--alarmpulse-lost);
	animation-name: alarmpulse;
}

/*
	The two segmented controls: theme, then chart layout. They sit
	together rather than 32px apart, because they are one cluster of
	appearance choices rather than two unrelated controls.
*/
.nav-controls {
	display: flex;
	align-items: center;
	gap: 10px;
}

.segmented {
	display: flex;
	padding: 3px;
	border: 1px solid var(--border);
	border-radius: var(--radius);
}

.segmented button {
	border: none;
	border-radius: var(--radius);
	padding: 4px 10px;
	color: var(--faint);
	font-size: 11.5px;
}

.segmented button[aria-pressed="true"] {
	background: var(--panel);
	color: var(--text);
}

.feed-latency {
	color: var(--faint);
	font-family: var(--mono);
	font-size: 11.5px;
}

.sign-out {
	border: none;
	padding: 0;
	color: var(--faint);
}

.sign-out:hover {
	color: var(--mid);
}

/*
	The alarm rail: full width, between the nav and the body, present only
	while the drops window holds something. NOT dismissible — an operator
	who could dismiss it would, and the condition would go on being true
	behind the dismissal — so it has no control of any kind on it.

	Its dot is STILL for a one-off drop and pulses only once the drops are
	sustained. The direction grants the pulse to the escalated case
	specifically, and says of the first non-zero second that nothing
	moves.
*/
.alarm-rail {
	display: flex;
	align-items: center;
	gap: 14px;
	padding: 10px var(--nav-pad);
	background: var(--alarm-bg);
	border-bottom: 1px solid var(--alarm-border);
}

.alarm-dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--alarm);
	flex: none;
}

.alarm-dot.sustained {
	animation: alarmpulse var(--alarmpulse-rail) ease-in-out infinite;
}

.alarm-headline {
	color: var(--alarm);
	font-size: 12.5px;
	font-weight: 600;
}

.alarm-detail {
	color: var(--alarm-dim);
	font-size: 12.5px;
}

/*
	The content region, and the one treatment that is applied to the whole
	of it.

	A dashboard showing stale numbers as though they were live is the
	worst failure this tool has, so a lost feed does not merely say so —
	it desaturates and dims every figure under it, and STOPS THE CHARTS.
	That last part is a correctness fix rather than a visual one: a chart
	still drifting while the feed is dead is a lie told in motion, and
	motion is what an operator reads from across a room.

	The transition is on the region rather than on the stale rule, so the
	200ms fade happens in both directions and the restore is a fade rather
	than a snap — with no reload, no remount, and therefore no reset of
	scroll position or of the rows somebody had expanded.
*/
main.content {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: var(--side-gap);
	padding: var(--page-pad);
	transition: filter 200ms linear, opacity 200ms linear;
}

main.content.stale {
	filter: saturate(.35);
	opacity: .55;
}

main.content.stale .chart-series {
	animation: none;
}



/*================================[  OVERVIEW  ]=============================*/

/*
	The body grid: charts left, a sidebar that is exactly 400px wide in
	every theme right. The width is fixed and NOT themeable — see the
	header.
*/
.overview {
	display: grid;
	grid-template-columns: 1fr var(--sidebar-width);
	gap: var(--body-gap);
	align-items: start;
}

/*	`min-width: 0` or a stretched chart canvas would widen the cell it is
	supposed to fit inside. */
.chart-column {
	min-width: 0;
}

.sidebar {
	display: flex;
	flex-direction: column;
	gap: var(--side-gap);
}

.card-header {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 12px;
	margin-bottom: 12px;
}

.card-total {
	color: var(--dim);
	font-size: 11.5px;
}

/*
	One block per namespace: swatch, name, count, and a bar scaled against
	the busiest of them. The bar is the ambient signal — a namespace
	dropping to zero collapses its bar visibly, with nobody reading a
	number.
*/
.namespace-list {
	display: flex;
	flex-direction: column;
	gap: 11px;
}

.namespace-row {
	display: flex;
	align-items: center;
	gap: 8px;
}

.namespace-swatch {
	width: 7px;
	height: 7px;
	border-radius: 2px;
	flex: none;
}

.namespace-name {
	color: var(--mid);
	font-size: 12px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.namespace-count {
	margin-left: auto;
	color: var(--text);
	font-size: 12px;
}

.namespace-bar {
	height: 3px;
	margin-top: 6px;
	background: var(--track);
}

.namespace-fill {
	display: block;
	height: 100%;
	opacity: .65;
}

/*	The operator-connection line is a footnote to the list, not the last
	entry in it — a full bar directly above it reads as a rule otherwise. */
.namespace-list + .placeholder {
	margin-top: 12px;
}

/*
	The nine lifetime counters. Label above value, two to a row; the five
	that record something going wrong turn `--caution` once they have
	moved and stay that way — a drop at 03:00 is still worth noticing at
	09:00.
*/
.counter-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 14px 18px;
	margin: 0;
}

.counter {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
}

.counter dt {
	color: var(--faint);
	font-size: 10.5px;
}

.counter dd {
	margin: 0;
	color: var(--text);
	font-family: var(--mono);
	font-variant-numeric: tabular-nums;
	font-size: 16px;
	letter-spacing: -.01em;
}

.counter dd.caution {
	color: var(--caution);
}



/*=================================[  CARDS  ]===============================*/

/*
	One rule, two treatments. Earth resolves `--side-bg`/`--side-border`
	to `transparent` and `--side-radius` to `0`, so the very same
	declarations render a hairline section there and a real card in Ocean
	and Frost — see the header's third rule.
*/
.card {
	background: var(--side-bg);
	border: 1px solid var(--side-border);
	border-top: 1px solid var(--side-top);
	border-radius: var(--side-radius);
	padding: var(--side-pad);
}

/*
	The two explicit overrides, for a card that must disagree with its
	theme's default. Neither is a second class tree: both set the same
	properties from the same tokens.
*/
.card[data-treatment="flush"] {
	background: transparent;
	border-color: transparent;
	border-top-color: var(--side-top);
	border-radius: 0;
}

.card[data-treatment="card"] {
	background: var(--panel);
	border-color: var(--border);
	border-radius: var(--panel-radius);
	padding: var(--card-pad);
}

.card h2 {
	margin: 0;
	font-size: 12.5px;
	font-family: var(--label-font);
	font-weight: var(--label-weight);
	letter-spacing: var(--label-tracking);
	text-transform: var(--label-case);
	color: var(--mid);
}

.field-list {
	display: grid;
	grid-template-columns: max-content 1fr;
	gap: 9px 18px;
	margin: 0;
	font-size: 11.5px;
}

.field-list dt {
	color: var(--faint);
}

.field-list dd {
	margin: 0;
	color: var(--text);
	text-align: right;
	min-width: 0;
}

/*	Two shades a field's value can take, and one that ellipsises. */
.field-list dd.muted {
	color: var(--mid);
}

.field-list dd.ok {
	color: var(--ok);
}

.field-list dd.truncate {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.placeholder {
	color: var(--faint);
	font-size: 12px;
	margin: 0;
}

.data-table {
	width: 100%;
	border-collapse: collapse;
	font-size: 12px;
}

.data-table th {
	text-align: left;
	font-family: var(--label-font);
	font-weight: var(--label-weight);
	letter-spacing: var(--label-tracking);
	text-transform: var(--label-case);
	font-size: 10.5px;
	color: var(--faint);
	border-bottom: 1px solid var(--hair);
	padding: 4px 0;
}

.data-table td {
	padding: 5px 0;
	border-bottom: 1px solid var(--hair);
	color: var(--mid);
}

.data-table tr:last-child td {
	border-bottom: none;
}

.numeric {
	text-align: right;
	color: var(--text);
}

/*
	A refusal, worded plainly: no icon, no box, no shake. The direction is
	explicit that an error line is a line.
*/
.banner {
	margin: 0;
	font-size: 11.5px;
}

.banner.error {
	color: var(--alarm);
}



/*================================[  STATES  ]===============================*/

/*
	The four states, all flat panels: no illustration, no spinner, no
	icon, and no retry button anywhere. This dashboard is read-only and an
	operator cannot make a node come back, so a control that implied they
	could would be worse than none.

	One rule with a `data-state` on it rather than four class trees — the
	same reasoning as the card treatment above.
*/
.state-panel {
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 14px 16px;
	border: 1px solid var(--border);
	border-radius: var(--panel-radius);
}

.state-label {
	color: var(--faint);
	font-family: var(--label-font);
	font-weight: var(--label-weight);
	letter-spacing: var(--label-tracking);
	text-transform: var(--label-case);
	font-size: 10.5px;
}

.state-title {
	margin: 0;
	color: var(--text);
	font-size: 13.5px;
}

.state-detail {
	margin: 0;
	color: var(--faint);
	font-size: 12px;
}

/*
	EMPTY is the COMMON case and must not look like a failure — a healthy
	node is quiet most of the time. So: no dashed border, no icon, no
	spinner, and a title in `--dim` rather than in `--text`, because
	nothing here is being announced.
*/
.state-panel[data-state="empty"] .state-title {
	color: var(--dim);
}

/*
	ERROR states the endpoint and its status; the detail says how old what
	is on screen is and when the dashboard will try again. The numbers
	beside it stay visible and stop updating, which is what they do
	anyway — they come off the socket, not off this read.
*/
.state-panel[data-state="error"] {
	border-color: var(--alarm-border);
}

.state-panel[data-state="error"] .state-label {
	color: var(--alarm);
}

.state-panel[data-state="error"] .state-detail {
	color: var(--alarm-dim);
}

/*
	LOST is the loud one, and the only panel that takes a full `--alarm`
	border. What makes it more than a panel is on `main.content` above.
*/
.state-panel[data-state="lost"] {
	background: var(--alarm-bg);
	border-color: var(--alarm);
}

.state-panel[data-state="lost"] .state-label {
	color: var(--alarm);
}

.state-panel[data-state="lost"] .state-detail {
	color: var(--alarm-dim);
}

/*	The lost panel sits between the alarm rail and the body, so it carries
	the page's own horizontal padding. */
.state-region {
	padding: var(--page-pad) var(--page-pad) 0;
}

/*
	LOADING is three bars and no border: a bordered box that appeared and
	vanished inside 400ms would be the flash the skeleton exists to avoid.
	Only the MIDDLE bar breathes — three moving in step reads as a
	control, and one moving reads as text that has not arrived.
*/
.state-panel[data-state="loading"] {
	gap: 8px;
	padding: 2px 0;
	border-color: transparent;
}

.skeleton-bar {
	display: block;
	height: 10px;
	background: var(--track);
	border-radius: var(--radius);
}

.skeleton-bar.breathing {
	animation: softpulse var(--softpulse-bar) ease-in-out infinite;
}



/*=================================[  LOGIN  ]===============================*/

.login-page {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	min-height: 100vh;
	background: var(--bg);
}

.login-column {
	display: flex;
	flex-direction: column;
	gap: 16px;
	width: 300px;
}

.login-wordmark {
	display: flex;
	align-items: center;
	gap: 8px;
}

.login-page h1 {
	margin: 0;
	font-size: 14px;
	font-weight: 600;
	letter-spacing: 0;
}

.login-page form {
	display: flex;
	flex-direction: column;
	gap: 14px;
}

.field {
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.field label {
	color: var(--faint);
	font-size: 11px;
}

.login-page button[type="submit"] {
	width: 100%;
	height: 36px;
	background: var(--accent);
	border: none;
	border-radius: var(--radius);
	color: var(--bg);
	font-size: 12.5px;
	font-weight: 600;
}

.login-page button[type="submit"]:hover {
	filter: brightness(1.08);
}



/*=================================[  CHARTS  ]==============================*/

/*
	Cards is the default treatment; `data-charts="stack"` on the document
	element is the override, and the theme picks which one it starts in
	(Earth stacks, Ocean and Frost card). Both have to look right in every
	theme — Earth+Cards and Ocean+Stack are valid combinations.
*/
.chart-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--col-gap);
}

.chart {
	margin: 0;
	background: var(--panel);
	border: 1px solid var(--border);
	border-radius: var(--panel-radius);
	padding: var(--card-pad);
}

.chart figcaption {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 12px;
	margin-bottom: 12px;
}

.chart-heading {
	display: flex;
	flex-direction: column;
	gap: 3px;
	min-width: 0;
}

.chart-title {
	font-family: var(--label-font);
	font-weight: var(--label-weight);
	letter-spacing: var(--label-tracking);
	text-transform: var(--label-case);
	font-size: 12.5px;
	color: var(--mid);
}

/*	What is being counted, said once so the headline figure beside it
	needs no unit. */
.chart-sub {
	color: var(--faint);
	font-size: 11px;
}

.chart-figures {
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 4px;
	flex: none;
}

/*
	The headline: the FIRST line's latest value, never the sum of the
	plotted lines — see the chart view's own documentation. It turns
	`--alarm` for a drops chart with something on it, which is the first
	rung of the escalation ladder.
*/
.chart-value {
	color: var(--text);
	font-family: var(--mono);
	font-variant-numeric: tabular-nums;
	font-size: var(--chart-value);
	font-weight: 500;
	letter-spacing: -.02em;
	line-height: 1;
}

.chart-value.alarm {
	color: var(--alarm);
}

.chart-peak {
	color: var(--faint);
	font-family: var(--mono);
	font-size: 10.5px;
}

/*	The annotation is positioned against the plot, not the panel, so it
	sits inside the canvas's bottom-left corner in both layouts. */
.chart-plot {
	position: relative;
}

/*
	The canvas is stretched non-uniformly, so every stroke inside it
	carries `vector-effect="non-scaling-stroke"` — see the chart views.
	Its three grid rules are drawn as SVG lines rather than as borders,
	because two of them are interior.
*/
.chart-canvas {
	display: block;
	width: 100%;
	height: var(--chart-height);
	overflow: hidden;
	background: transparent;
}

.chart-grid-interior {
	stroke: var(--grid-a);
	stroke-width: 1;
}

.chart-grid-baseline {
	stroke: var(--grid-b);
	stroke-width: 1;
}

/*
	The one thing a chart says while it has nothing to draw. Bottom-left
	of the plot, in `--ghost`, so it reads as part of the empty chart
	rather than as a label on it — see `crates/web-ui/src/drops.rs`.
*/
.chart-annotation {
	position: absolute;
	left: 0;
	bottom: 3px;
	color: var(--ghost);
	font-size: 10px;
	line-height: 1;
}

/*	A key, not a control: no interaction anywhere in it. */
.chart-legend {
	display: flex;
	flex-wrap: wrap;
	gap: 5px 14px;
	margin-top: 10px;
	font-size: 10.5px;
}

.chart-key {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	min-width: 0;
}

.chart-key-rule {
	width: 9px;
	height: 2px;
	border-radius: 1px;
	flex: none;
}

.chart-key-name {
	color: var(--dim);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.chart-key-value {
	color: var(--mid);
}

/*
	There is deliberately no line-colour table here. Series colour is
	DERIVED from the namespace's own name — a rule no stylesheet can
	compute — so each line carries its own colour, weight and opacity from
	`crates/web-ui/src/colour.rs`, and the legend key takes its swatch
	from the same value. See that module for why the rule beats a palette.
*/

/*
	Stack: no card at all. A transparent panel, no side border, one
	hairline rule above each chart, and a shorter canvas.
*/
:root[data-charts="stack"] .chart-grid {
	grid-template-columns: 1fr;
	gap: 0;
}

:root[data-charts="stack"] .chart {
	background: transparent;
	border: none;
	border-top: 1px solid var(--hair);
	border-radius: 0;
	padding: var(--stack-pad);
}

:root[data-charts="stack"] .chart-canvas {
	height: var(--chart-height-stack);
}

:root[data-charts="stack"] .chart-value {
	font-size: var(--chart-value-stack);
}



/*=================================[  TAIL  ]================================*/

/*
	The inspector's header: what it is, how fast rows are arriving beside
	how many are kept, and the pause control pushed to the right. The meta
	line is a FIGURE, so it is mono in every theme like every other figure
	on the page.
*/
.tail-header {
	display: flex;
	align-items: baseline;
	gap: 14px;
	margin-bottom: 12px;
}

.tail-meta {
	color: var(--faint);
	font-size: 11px;
}

.tail-pause {
	margin-left: auto;
	flex: none;
}

/*
	The frozen bar: the SECOND signal that a view is held, and the one that
	carries information rather than state. It sits directly under the
	header, and it is the reason pausing to read a payload cannot look like
	the traffic stopping.
*/
.tail-frozen {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-bottom: 12px;
	padding: 8px 0;
	background: var(--chrome);
	border-bottom: 1px solid var(--border);
	color: var(--accent);
	font-size: 12px;
}

/*	Two bars, drawn as borders: the only pause glyph on the page, and no
	icon pack to carry for it. */
.pause-glyph {
	width: 8px;
	height: 10px;
	border-left: 2px solid var(--accent);
	border-right: 2px solid var(--accent);
	flex: none;
	margin-left: 2px;
}

/*
	Bounded so a burst cannot run off the panel — the visible list is
	capped in the view as well, and this is what keeps an expanded payload
	on the last drawn row from pushing the panel open.
*/
.tail {
	border-top: 1px solid var(--hair);
	overflow: hidden;
}

.tail-row {
	border-bottom: 1px solid var(--hair);
}

.tail-row summary {
	display: grid;
	grid-template-columns: 104px 176px 1fr 88px 18px;
	gap: 16px;
	align-items: baseline;
	padding: var(--row-pad);
	font-family: var(--mono);
	font-size: 12px;
	cursor: pointer;
	list-style: none;
}

/*
	The caret is the row's fifth column, drawn rather than marked up: the
	browser's own disclosure triangle is the wrong mark in the wrong
	place, and a `<details>` is still what carries the state.
*/
.tail-row summary::-webkit-details-marker {
	display: none;
}

.tail-row summary::after {
	content: "+";
	color: var(--ghost);
	text-align: right;
}

.tail-row[open] summary::after {
	content: "−";
}

.tail-row summary:hover {
	background: var(--panel);
}

/*	Dim on purpose: needed when reading a row, noise when scanning a
	column of them. */
.tail-time {
	color: var(--faint);
}

/*
	The swatch is what makes the firehose scannable — at speed an operator
	reads a colour column rather than text — and its colour is DERIVED from
	the namespace's name, so it matches that namespace's line on the
	connections chart without either side holding a list. See
	`crates/web-ui/src/colour.rs`.
*/
.tail-namespace {
	display: flex;
	align-items: center;
	gap: 8px;
	min-width: 0;
	color: var(--mid);
}

.tail-swatch {
	width: 7px;
	height: 7px;
	border-radius: 2px;
	flex: none;
}

.tail-name {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.tail-channels {
	color: var(--dim);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*	Size is the second thing that goes wrong, after volume, and it costs
	nothing to surface — see the inspector's own module documentation. */
.tail-size {
	text-align: right;
	color: var(--dim);
}

.tail-size.caution {
	color: var(--caution);
}

.tail-payload {
	margin: 0;
	padding: 2px 18px 14px 120px;
	background: var(--bg);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	color: var(--mid);
	font-size: 12px;
	line-height: 1.6;
	white-space: pre-wrap;
	word-break: break-word;
}



/*===============================[  RESPONSE  ]==============================*/

/*
	Past ~2000px the chart column does not widen further: the grid takes a
	third column and the sidebar stays exactly 400px.
*/
@media (min-width: 2000px) {
	:root .chart-grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}

	:root[data-charts="stack"] .chart-grid {
		grid-template-columns: 1fr;
	}
}

/*
	Below ~1500px the chart grid drops to one column — the direction's
	"force stack", read as the stack TREATMENT and not merely one column,
	since a lone bordered card in a column of its own is the shape stack
	exists to avoid.
*/
@media (max-width: 1500px) {
	:root .chart-grid {
		grid-template-columns: 1fr;
		gap: 0;
	}

	:root .chart {
		background: transparent;
		border: none;
		border-top: 1px solid var(--hair);
		border-radius: 0;
		padding: var(--stack-pad);
	}

	:root .chart-canvas {
		height: var(--chart-height-stack);
	}

	:root .chart-value {
		font-size: var(--chart-value-stack);
	}
}

/*
	Below ~1400px the direction is deliberately silent — desktop-first was
	the brief, and narrow widths are unspecified. What is here is the
	minimum that keeps the page usable rather than a designed answer: the
	sidebar stops being a sidebar and the fixed 400px column is released,
	since below this width holding it would leave the charts narrower than
	the cards beside them.
*/
@media (max-width: 1100px) {
	.overview {
		grid-template-columns: minmax(0, 1fr);
	}
}


/*============================[  REDUCED MOTION  ]============================*/

/*
	`chartscroll` drops to a discrete once-a-second step and every pulse
	stops — but the ALARM COLOUR CHANGE SURVIVES. An operator who has
	asked for less motion has not asked to be told less; the colour is
	the signal and the pulse is only its emphasis. `animation: none`
	touches no colour or background property, so that half holds for
	free.

	This block sits at the very end of the file, and names the compound
	selectors explicitly, for one reason: it has to WIN. The pulsing
	states are two-class selectors (`.status-dot.offline`,
	`.alarm-dot.sustained`, `.skeleton-bar.breathing`), so a bare
	`.status-dot` override loses on specificity no matter where it
	appears, and an equal-specificity override loses on source order
	from anywhere above the rule it means to beat. An earlier version of
	this block was placed near the animation definitions and silently
	degraded nothing but the chart drift. Anything added here must match
	the specificity of what it overrides and stay at the end of the
	file; `e2e/tests/motion.spec.ts` pins that it actually does.
*/
@media (prefers-reduced-motion: reduce) {
	.chart-series {
		animation-timing-function: steps(1, end);
	}

	.status-dot,
	.status-dot.offline,
	.alarm-dot,
	.alarm-dot.sustained,
	.skeleton-bar,
	.skeleton-bar.breathing {
		animation: none;
		opacity:   1;
	}
}
