/* Nuno Labs portal — shared shell for every /www page in this app. */

/*
	`hidden` has to win against a class with a `display` of its own too. The UA
	stylesheet sets `[hidden] { display: none }`, but any class rule beats it -
	so a `<div class="build-carriers" hidden>` with `display: flex` stood there
	visible until JavaScript touched it. Without JavaScript it stayed visible.
*/
[hidden] { display: none !important; }


:root {
	--page-width: 80rem;
	--bg: #17171a;
	--raised: #1d1d21;
	--ink: #ececf1;
	/*
		Contrast against --raised (#1d1d21), worked out to WCAG: --ink    14.3:1
		primary text --muted   7.1:1   labels, secondary text --faint   4.8:1
		hints, explanations

		#74747f (3.6:1) and #4a4a53 (1.9:1) used to stand here. AA wants 4.5:1
		for text - so the hint text was not quiet but unreadable, and hints like
		"whole hubs only" or "one per piece" carry real instructions. Both
		levels are above it now and still stay well below --ink, so the ranking
		holds.

		Whoever turns these knobs works it out again: the darkest ground in the
		portal is #131316 (.build-part, .input), the lightest #26262d
		(.btn:hover).
	*/
	--muted: #a8a8b3;
	--faint: #888893;
	--hairline: rgba(255, 255, 255, 0.07);
	--hairline-strong: rgba(255, 255, 255, 0.14);
}

* { box-sizing: border-box; }

html, body {
	height: 100%;
	margin: 0;
}

html { scroll-behavior: smooth; }

body {
	background: var(--bg);
	color: var(--ink);
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto,
		Helvetica, Arial, sans-serif;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
	display: flex;
	flex-direction: column;
	min-height: 100vh;
	min-height: 100dvh;
}

/* ---- centred stage ---------------------------------------------------- */

.stage {
	flex: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 1.5rem;
	padding: 4rem 1.5rem;
	text-align: center;
	animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}

.mark {
	position: relative;
	width: 84px;
	height: 84px;
}

.mark img {
	width: 100%;
	height: 100%;
	display: block;
	object-fit: contain;
}

.wordmark {
	margin: 0;
	font-size: 0.6875rem;
	font-weight: 500;
	letter-spacing: 0.3em;
	text-indent: 0.3em;
	text-transform: uppercase;
	color: var(--muted);
}

.greeting {
	margin: 0;
	font-size: clamp(1.35rem, 3.2vw, 1.75rem);
	font-weight: 400;
	letter-spacing: -0.011em;
	color: var(--ink);
}

/* ---- tiles ------------------------------------------------------------ */

.tiles {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.75rem;
	width: 100%;
	max-width: 30rem;
	margin-top: 1rem;
	text-align: left;
}

/* Sized so two tiles fill the row and a lone tile does not stretch. */
.tile {
	flex: 1 1 12rem;
	max-width: 14.4rem;
	padding: 0.95rem 1.15rem;
	border: 1px solid var(--hairline);
	border-radius: 10px;
	background: var(--raised);
	text-decoration: none;
	/* No transform on hover - see .launch:hover. */
	transition: border-color 0.18s ease, background-color 0.18s ease;
}

.tile:hover,
.tile:focus-visible {
	border-color: var(--hairline-strong);
	background: #212127;
}

/* Wider variant for tiles that carry a line of explanation. */
.tiles--detail {
	max-width: 42rem;
}

.tiles--detail .tile {
	flex: 1 1 16rem;
	max-width: 20rem;
}

.tile-label {
	display: block;
	font-size: 0.875rem;
	font-weight: 500;
	color: var(--ink);
}

.tile-hint {
	display: block;
	margin-top: 0.25rem;
	font-size: 0.7188rem;
	line-height: 1.45;
	color: var(--faint);
}

/* ---- sub-page shell --------------------------------------------------- */

.topbar {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	padding: 1.25rem 1.5rem;
	text-decoration: none;
	align-self: flex-start;
}

.topbar img {
	width: 22px;
	height: 22px;
	display: block;
}

.topbar .wordmark {
	font-size: 0.625rem;
	transition: color 0.18s ease;
}

.topbar:hover .wordmark,
.topbar:focus-visible .wordmark {
	color: var(--ink);
}

.page-title {
	margin: 0;
	font-size: clamp(1.35rem, 3.2vw, 1.75rem);
	font-weight: 400;
	letter-spacing: -0.011em;
}

.empty {
	margin: 0;
	font-size: 0.875rem;
	color: var(--faint);
}

/* ---- footer ----------------------------------------------------------- */

.tray {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 0.85rem;
	padding: 1.5rem;
	font-size: 0.75rem;
	color: var(--faint);
	border-top: 1px solid var(--hairline);
	animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.12s both;
}

.tray a {
	color: var(--faint);
	text-decoration: none;
	transition: color 0.18s ease;
}

.tray a:hover,
.tray a:focus-visible {
	color: var(--ink);
}

.tray span { opacity: 0.45; }

/* ---- Sprachwahl ------------------------------------------------------- */

/*
	It stands in the footer of every page and on the sign-in page. Quiet like
	the line it stands in: no border until the mouse lies on it. The native
	field, not the dropdown of our own out of portal.js - nothing here should
	pull open that would be a form.
*/
.language {
	display: flex;
	align-items: center;
	gap: 0.4rem;
	margin: 0;
}

.language-choice {
	padding: 0.1rem 0.2rem;
	border: 1px solid transparent;
	border-radius: 6px;
	background: transparent;
	color: var(--faint);
	font: inherit;
	font-size: 0.75rem;
	cursor: pointer;
	transition: color 0.18s ease, border-color 0.18s ease;
}

.language-choice:hover, .language-choice:focus-visible {
	border-color: var(--hairline);
	color: var(--ink);
	outline: none;
}

/*
	The list itself is drawn by the operating system - without this it stands
	white on white in the dark.
*/
.language-choice option {
	background: var(--raised);
	color: var(--ink);
}

@keyframes rise {
	from { opacity: 0; transform: translateY(10px); }
	to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
	.stage, .tray { animation: none; }
}

/* ---- process pages ---------------------------------------------------- */

/*
	One width for every page. The portal is used on a laptop: a form in 34 rem
	stood as a strip in the middle of the screen, and a row editor (the
	suppliers of an item) broke a company's name letter by letter. The
	modifiers stay in the templates, they no longer change the width - a page
	that is narrower than its neighbour reads like a different application.
*/
.page {
	flex: 1;
	width: 100%;
	max-width: var(--page-width);
	margin: 0 auto;
	padding: 0.5rem 1.5rem 4rem;
	animation: rise 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
	/* The field grid asks how wide its page or panel is (.row, below). */
	container: fields / inline-size;
}

.page--narrow, .page--wide { max-width: var(--page-width); }

/*
	Document pages: wide enough for an A4 sheet at full size to fit in - 210 mm
	plus the page's margin. Through `:has()` and not through a class of its own,
	so that the rule holds for every sheet of this kind without anybody having
	to think of it. The delivery note stood on `page--narrow` with 34 rem:
	inside that `enhanceSheet` shrank the sheet to a good two thirds, and a
	document that size reads like a preview. The header, the sheet and the edit
	form are thereby the same width and read as one object.
*/
.page:has(.sheet-scroll) { max-width: calc(210mm + 3.5rem); }

.back {
	display: inline-block;
	margin-bottom: 1.75rem;
	font-size: 0.75rem;
	color: var(--faint);
	text-decoration: none;
	transition: color 0.18s ease;
}

.back::before { content: "\2190"; margin-right: 0.4rem; }
.back:hover, .back:focus-visible { color: var(--ink); }

.page-head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
	margin-bottom: 2rem;
}

.page-meta {
	margin: 0.4rem 0 0;
	font-size: 0.8125rem;
	color: var(--muted);
}

/*
	What the next phase is waiting for - it has no button, it follows the
	data. Quieter than the phase itself, and indented to stand under its word,
	not under the dot.
*/
.phase-waiting {
	margin-top: 0.25rem;
	padding-left: calc(7px + 0.5rem);
	font-size: 0.75rem;
	color: var(--faint);
}

.dot {
	display: inline-block;
	width: 7px;
	height: 7px;
	margin-right: 0.5rem;
	border: 1px solid transparent;
	border-radius: 50%;
	vertical-align: 0.05em;
}

/* ---- board ------------------------------------------------------------ */

.board {
	display: flex;
	gap: 0.75rem;
	overflow-x: auto;
	padding-bottom: 1rem;
	scrollbar-width: thin;
}

.board-col {
	flex: 1 1 9.5rem;
	min-width: 9.5rem;
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

.board-head {
	display: flex;
	align-items: center;
	margin: 0 0 0.25rem;
	padding-bottom: 0.5rem;
	border-bottom: 1px solid var(--hairline);
	font-size: 0.75rem;
	font-weight: 500;
	color: var(--muted);
}

.board-count {
	margin-left: auto;
	font-variant-numeric: tabular-nums;
	color: var(--faint);
}

.card {
	display: block;
	padding: 0.8rem 0.9rem;
	border: 1px solid var(--hairline);
	border-radius: 9px;
	background: var(--raised);
	text-decoration: none;
	transition: border-color 0.18s ease, background-color 0.18s ease;
}

.card:hover, .card:focus-visible {
	border-color: var(--hairline-strong);
	background: #212127;
}

.card-id {
	display: block;
	font-size: 0.6875rem;
	font-variant-numeric: tabular-nums;
	letter-spacing: 0.04em;
	color: var(--faint);
}

.card-main {
	display: block;
	margin-top: 0.25rem;
	font-size: 0.8125rem;
	color: var(--ink);
}

.card-sub {
	display: block;
	margin-top: 0.2rem;
	font-size: 0.7188rem;
	line-height: 1.45;
	color: var(--muted);
}

/* ---- Fortschrittsleiste ---------------------------------------------- */

/*
	One line, no connecting lines. The flow is not strictly linear, and arrows
	would claim an order that does not exist.
*/
.trail {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.2rem 0.9rem;
	margin: 0 0 2rem;
	padding: 0.45rem 0.7rem;
	border: 1px solid var(--hairline);
	border-radius: 8px;
	transition: background-color 0.35s ease, border-color 0.35s ease;
}

.trail.is-complete {
	background: rgba(79, 174, 106, 0.12);
	border-color: rgba(79, 174, 106, 0.38);
}

.trail.is-cancelled {
	background: rgba(209, 106, 94, 0.1);
	border-color: rgba(209, 106, 94, 0.32);
}

.trail-step {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	padding: 0.2rem 0.15rem;
	border-radius: 5px;
	font-size: 0.6875rem;
	text-decoration: none;
	transition: color 0.18s ease;
}

/*
	A checkbox per step, not a colour per phase: empty while it is open, ringed
	while it is up, green with a tick once it is done.
*/
.trail-mark {
	display: grid;
	place-items: center;
	flex: none;
	width: 13px;
	height: 13px;
	border: 1.5px solid var(--faint);
	border-radius: 3px;
	font-size: 9px;
	font-weight: 700;
	line-height: 1;
	color: #17171a;
}

.trail-step.is-done .trail-mark { background: #4fae6a; border-color: #4fae6a; }
.trail-step.is-current .trail-mark { border-color: var(--ink); box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1); }

/* Manufacturing runs alongside the flow: a dashed box. */
.trail-step.is-parallel:not(.is-done) .trail-mark { border-style: dashed; }

.trail-label { color: var(--faint); white-space: nowrap; }
.trail-step.is-done .trail-label { color: var(--muted); }
.trail-step.is-current .trail-label { color: var(--ink); font-weight: 500; }
.trail-step:hover .trail-label,
.trail-step:focus-visible .trail-label { color: var(--ink); }

.trail-note {
	margin-left: auto;
	font-size: 0.6875rem;
	color: #d16a5e;
}

/* Do not push the jump targets under the page's edge. */
.panel[id] { scroll-margin-top: 1.5rem; }

/* ---- workflow actions ------------------------------------------------- */

.btn {
	display: inline-block;
	padding: 0.5rem 0.9rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 8px;
	background: var(--raised);
	color: var(--ink);
	font: inherit;
	font-size: 0.8125rem;
	text-decoration: none;
	cursor: pointer;
	transition: border-color 0.18s ease, background-color 0.18s ease;
}

.btn:hover, .btn:focus-visible { border-color: rgba(255, 255, 255, 0.3); background: #26262d; }

.btn--blocked {
	border-style: dashed;
	border-color: var(--hairline);
	background: transparent;
	color: var(--faint);
	cursor: not-allowed;
}

.btn--blocked:hover { border-color: var(--hairline); background: transparent; }

.blocked-why {
	display: block;
	margin-top: 0.15rem;
	font-size: 0.6875rem;
	color: var(--faint);
}

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

.panel {
	margin-bottom: 1rem;
	padding: 1.1rem 1.25rem;
	border: 1px solid var(--hairline);
	border-radius: 10px;
	background: var(--raised);
}

.panel-head {
	margin: 0 0 0.9rem;
	font-size: 0.75rem;
	font-weight: 500;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--muted);
}

.panel-note {
	margin: -0.4rem 0 0.9rem;
	font-size: 0.7188rem;
	color: var(--faint);
}

.panel-foot {
	margin-top: 1rem;
	padding-top: 0.9rem;
	border-top: 1px solid var(--hairline);
}

.row {
	display: grid;
	grid-template-columns: 13rem 1fr;
	gap: 0.5rem 1rem;
	align-items: baseline;
	padding: 0.42rem 0;
	font-size: 0.8125rem;
}

.row + .row { border-top: 1px solid rgba(255, 255, 255, 0.04); }

.row:has(textarea) { align-items: start; }
.row:has(textarea) .row-label { padding-top: 0.45rem; }

.row-label { color: var(--muted); }
/*
	`overflow-wrap`, because a link to the outside has no spaces it could break
	at - without it a park's Maps link stepped out of the board.
*/
.row-value { color: var(--ink); overflow-wrap: anywhere; }
/*
	A text the process writes itself has lines that mean something: the report of
	the installation carries one per sensor hub. As HTML they would run into one
	another.
*/
.row-lines { white-space: pre-line; }

/*
	The field grid: wherever two field rows stand side by side in a panel or a
	form, they run in two columns as soon as there is room - on a laptop a
	single column of 22-rem inputs left half of every panel empty. A row that
	needs the width (a table, a text, a person with their card) and whatever
	else stands in between (a heading, a hint, the buttons) takes the whole
	line. Asked of the panel or page around it, not of the screen: a quick
	entry dialog or a park card stays one column by itself. Steps that are
	read in their order (the help, how the warehouses are chosen) stay one
	column too: `rows--list` on their panel.
*/
.panel, .modal-form { container: fields / inline-size; }

@container fields (min-width: 46rem) {
	/* Every label column on a wide page the same, in a grid or not. */
	.row { grid-template-columns: 10.5rem minmax(0, 1fr); }
	:is(form, section, div, details, fieldset):has(> .row ~ .row):not(.row, .facts, .rows--list) {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: 2.5rem;
		align-content: start;
	}
	:is(form, section, div, details, fieldset):has(> .row ~ .row):not(.row, .facts, .rows--list) > :not(.row),
	:is(form, section, div, details, fieldset):has(> .row ~ .row):not(.row, .facts, .rows--list) > :is(
		.row--table, .row--person, .row--sub, .row--edit,
		.row:has(textarea, .tbl, .pick, .tbl-read, .row-lines, .person-card)
	) { grid-column: 1 / -1; }
	/* A line of its own between rows: drawn under every row, because in two
	   columns the first row of the right one has no row above it. */
	:is(form, section, div, details, fieldset):has(> .row ~ .row):not(.row, .facts, .rows--list) > .row {
		grid-template-columns: 10.5rem minmax(0, 1fr);
		border-top: 0;
		border-bottom: 1px solid rgba(255, 255, 255, 0.04);
		/* A value as text beside an input in the other column: on its middle. */
		align-items: center;
	}
	:is(form, section, div, details, fieldset):has(> .row ~ .row):not(.row, .facts, .rows--list) > .row:not(.row--table, .row--person) :is(.input, .combo) { max-width: none; }
	/* A full-width row keeps its label column at the width of the others. */
	:is(form, section, div, details, fieldset):has(> .row ~ .row):not(.row, .facts, .rows--list) > .row:is(.row--table, .row--person, .row--sub, .row--edit, :has(textarea, .tbl, .pick, .tbl-read, .row-lines, .person-card)) {
		grid-template-columns: 10.5rem minmax(0, 1fr);
		/* A tall row keeps its label at the top, by its first line. */
		align-items: start;
	}
	:is(form, section, div, details, fieldset):has(> .row ~ .row):not(.row, .facts, .rows--list) > .row:is(.row--table, :has(textarea, .tbl, .pick)) > .row-label { padding-top: 0.45rem; }
	/* A text across the line is still a text: not wider than one reads. */
	:is(form, section, div, details, fieldset):has(> .row ~ .row):not(.row, .facts, .rows--list) > .row textarea.input { max-width: 52rem; }
}
.unset { color: var(--faint); }
.req { color: #d16a5e; }

.inline-link {
	color: var(--ink);
	text-decoration: none;
	border-bottom: 1px solid var(--hairline-strong);
}

.inline-link:hover, .inline-link:focus-visible { border-bottom-color: currentColor; }

/* ---- inputs ----------------------------------------------------------- */

.input {
	width: 100%;
	max-width: 22rem;
	padding: 0.4rem 0.55rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 6px;
	background: #131316;
	color: var(--ink);
	font: inherit;
	font-size: 0.8125rem;
}

.input:focus-visible {
	outline: none;
	border-color: rgba(255, 255, 255, 0.34);
}

textarea.input { resize: vertical; }

/* The native date and time picker is otherwise black on black. */
.input::-webkit-calendar-picker-indicator { filter: invert(0.6); cursor: pointer; }

/* ---- flash ------------------------------------------------------------ */

.flash {
	margin: 0 0 1.5rem;
	padding: 0.6rem 0.85rem;
	border: 1px solid var(--hairline);
	border-left-width: 2px;
	border-radius: 6px;
	font-size: 0.8125rem;
}

.flash--ok { border-left-color: #4fae6a; color: var(--ink); }
/*
	Done, but not finished - the same colour the board uses for "urgent"
	(STATES/URGENCY in production.py). Not red: nothing has gone wrong, there is
	only one step left.
*/
.flash--warn { border-left-color: #e0912f; color: var(--ink); }
.flash--err { border-left-color: #d16a5e; color: var(--ink); }

/*
	The schema strip hangs below the header and belongs to no page, so it brings
	its own margin along.
*/
.schema-warning { margin: 0 auto 1rem; max-width: 74rem; width: calc(100% - 2rem); }

@media (max-width: 34rem) {
	.row { grid-template-columns: 1fr; gap: 0.15rem; }
	.input { max-width: none; }
}

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

.table-wrap {
	overflow-x: auto;
	border: 1px solid var(--hairline);
	border-radius: 10px;
	background: var(--raised);
}

.table {
	width: 100%;
	border-collapse: collapse;
	font-size: 0.8125rem;
}

.table th {
	padding: 0.7rem 1rem;
	border-bottom: 1px solid var(--hairline);
	font-size: 0.6875rem;
	font-weight: 500;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	text-align: left;
	white-space: nowrap;
	color: var(--muted);
}

.table td {
	padding: 0.65rem 1rem;
	border-top: 1px solid rgba(255, 255, 255, 0.04);
	color: var(--ink);
	white-space: nowrap;
}

.table tbody tr {
	cursor: pointer;
	transition: background-color 0.15s ease;
}

.table tbody tr:hover { background: #212127; }

/*
	A list under headings (the accessory options in the segments of the box):
	the heading row is no record - no pointer, no hover - and stands apart
	from the group above it.
*/
.table tbody tr.table-group-head { cursor: default; }
.table tbody tr.table-group-head:hover { background: none; }
.table-group-head th {
	padding-top: 1.6rem;
	border-bottom: 1px solid var(--hairline);
	font-size: 0.75rem;
	font-weight: 600;
	color: var(--ink);
}
.table-group:first-of-type .table-group-head th { padding-top: 0.9rem; }
.table-group-head .tab-count { margin-left: 0.35rem; letter-spacing: 0; }

/*
	A table that becomes cards on a phone (the dashboard): every row a card,
	its first cell the card's title, every other cell with its column's name
	(`data-label`) beside the value - instead of a table three screens wide
	that scrolls sideways under the thumb.
*/
@media (max-width: 40rem) {
	.table--cards thead { display: none; }
	.table--cards, .table--cards tbody, .table--cards tr, .table--cards td { display: block; width: auto; }
	.table--cards tbody tr { padding: 0.7rem 1rem; }
	.table--cards tbody tr + tr { border-top: 1px solid var(--hairline); }
	.table--cards td {
		position: relative;
		padding: 0.2rem 0 0.2rem 7.5rem;
		border: 0;
		white-space: normal;
		overflow-wrap: anywhere;
	}
	.table--cards td::before {
		content: attr(data-label);
		position: absolute;
		left: 0;
		top: 0.3rem;
		width: 7rem;
		font-size: 0.75rem;
		color: var(--faint);
	}
	.table--cards td:first-child { padding: 0 0 0.3rem; font-size: 0.875rem; }
	.table--cards td:first-child::before { content: none; }
	.table--cards td.num { text-align: left; }
}

/* ---- chrome: brand left, account right ------------------------------- */

.chrome {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.9rem 1.5rem;
}

/* The header brings its own spacing along. */
.chrome .topbar { padding: 0; align-self: auto; }

.account {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	padding: 0.3rem 0.6rem 0.3rem 0.3rem;
	border: 1px solid transparent;
	border-radius: 999px;
	text-decoration: none;
	transition: border-color 0.18s ease, background-color 0.18s ease;
}

.account:hover, .account:focus-visible {
	border-color: var(--hairline);
	background: var(--raised);
}

.avatar {
	display: grid;
	place-items: center;
	flex: none;
	width: 26px;
	height: 26px;
	border-radius: 50%;
	background: #2a2a31;
	color: var(--ink);
	font-size: 0.625rem;
	font-weight: 600;
	letter-spacing: 0.03em;
}

.avatar--lg {
	width: 46px;
	height: 46px;
	font-size: 0.9375rem;
}

.account-text {
	display: flex;
	flex-direction: column;
	line-height: 1.25;
}

.account-name {
	font-size: 0.75rem;
	color: var(--ink);
}

.account-id {
	font-size: 0.6563rem;
	color: var(--faint);
}

.identity {
	display: flex;
	align-items: center;
	gap: 0.9rem;
}

@media (max-width: 30rem) {
	.account-text { display: none; }
}

/* ---- chips ------------------------------------------------------------ */

.chips {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem;
}

.chip {
	padding: 0.25rem 0.6rem;
	border: 1px solid var(--hairline);
	border-radius: 999px;
	font-size: 0.7188rem;
	color: var(--muted);
}

/* ---- checkbox --------------------------------------------------------- */

.check {
	display: inline-flex;
	align-items: center;
	gap: 0.45rem;
	font-size: 0.8125rem;
	color: var(--muted);
	cursor: pointer;
}

.check input {
	width: 15px;
	height: 15px;
	accent-color: #4a90d9;
	cursor: pointer;
}

/* ---- production ------------------------------------------------------- */

.head-actions {
	display: flex;
	gap: 0.5rem;
	flex-wrap: wrap;
}

/*
	Transition buttons in the page header are forms - without this the <form>'s
	default margin pushes them out of the line.
*/
.head-actions form { margin: 0; }

.stats {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem;
	margin-bottom: 1.5rem;
}

.stat {
	flex: 1 1 9rem;
	padding: 0.9rem 1.1rem;
	border: 1px solid var(--hairline);
	border-radius: 10px;
	background: var(--raised);
}

.stat-value {
	display: block;
	font-size: 1.5rem;
	font-weight: 300;
	font-variant-numeric: tabular-nums;
	letter-spacing: -0.02em;
	color: var(--ink);
}

.stat-of {
	font-size: 0.8125rem;
	color: var(--muted);
}

.stat-label {
	display: block;
	margin-top: 0.2rem;
	font-size: 0.7188rem;
	color: var(--muted);
}

.panel-row {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 1rem;
	margin-bottom: 0.9rem;
}

.panel-head--tight {
	margin-bottom: 0.25rem;
	font-size: 0.875rem;
	letter-spacing: 0;
	text-transform: none;
}

.panel-row .panel-note { margin-top: 0; }

.badge {
	flex: none;
	padding: 0.15rem 0.55rem;
	border: 1px solid;
	border-radius: 999px;
	font-size: 0.6875rem;
	white-space: nowrap;
}

.meter {
	position: relative;
	height: 5px;
	margin: 0.2rem 0 0.5rem;
	border-radius: 999px;
	background: rgba(255, 255, 255, 0.07);
	overflow: hidden;
}

.meter-fill {
	display: block;
	height: 100%;
	border-radius: 999px;
	transition: width 0.3s ease;
}

.meter-legend {
	margin: 0 0 0.5rem;
	font-size: 0.75rem;
	color: var(--muted);
}

.meter-legend strong { color: var(--ink); font-weight: 500; }

.card-prod {
	display: block;
	margin-top: 0.35rem;
	font-size: 0.6875rem;
	color: var(--muted);
}

.inline-form {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	margin-left: 0.6rem;
}

.input--qty {
	width: 4.5rem;
	max-width: 4.5rem;
	padding: 0.25rem 0.4rem;
}

.btn--small {
	padding: 0.28rem 0.6rem;
	font-size: 0.75rem;
}

/* ---- messages --------------------------------------------------------- */

.chrome-right {
	display: flex;
	align-items: center;
	gap: 0.6rem;
}

.pill {
	padding: 0.22rem 0.6rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 999px;
	font-size: 0.6875rem;
	color: var(--muted);
	text-decoration: none;
	white-space: nowrap;
	transition: color 0.18s ease, border-color 0.18s ease;
}

.pill:hover, .pill:focus-visible { color: var(--ink); border-color: rgba(255, 255, 255, 0.3); }

/* "Help" beside the account: a pill like the questions and notifications -
   a word says more than the "?" that stood here. */
.help-button { flex: none; }

/* /help: the snippets to copy stand under their fold, at full width. */
.invitation:has(textarea) { align-items: start; }
/* A snippet under its label, over the full width: a command is not wrapped. */
#mcp .row { grid-template-columns: 9rem 1fr; }
#mcp .row--snippet { grid-template-columns: 1fr; }
#mcp .row--snippet .row-label { padding-top: 0; }
.help-config { margin-top: 0.5rem; }
.help-secret { margin: 0.9rem 0 0; }

.inbox {
	width: 100%;
	max-width: 30rem;
	margin-top: 1.25rem;
	text-align: left;
}

.inbox-line {
	display: flex;
	align-items: baseline;
	gap: 0.6rem;
	padding: 0.45rem 0;
	border-top: 1px solid var(--hairline);
	font-size: 0.75rem;
	color: var(--muted);
	text-decoration: none;
	transition: color 0.18s ease;
}

.inbox-line:hover, .inbox-line:focus-visible { color: var(--ink); }

.inbox-dot {
	flex: none;
	width: 5px;
	height: 5px;
	border-radius: 50%;
	background: #4a90d9;
}

.inbox-text {
	flex: 1;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.inbox-when { flex: none; color: var(--faint); font-size: 0.6875rem; }

.inbox-more {
	display: inline-block;
	margin-top: 0.5rem;
	font-size: 0.6875rem;
	color: var(--faint);
	text-decoration: none;
}

.inbox-more:hover { color: var(--ink); }

.note {
	display: flex;
	align-items: baseline;
	gap: 1rem;
	padding: 0.55rem 0;
	font-size: 0.8125rem;
	color: var(--muted);
}

.note + .note { border-top: 1px solid rgba(255, 255, 255, 0.04); }
.note--unread { color: var(--ink); }
.note-body { flex: 1; }
.note-when { flex: none; font-size: 0.7188rem; color: var(--faint); }

/* ---- combobox --------------------------------------------------------- */

/*
	Without JavaScript the native <select> stays standing and visible. Only once
	portal.js wraps it in a .combo does the field of our own take over.

	A row, because a picker that can create carries a "+" beside it
	(portal.js, .combo-add). Without one the trigger is the only child in the
	row and stands exactly as it did before.
*/
.combo {
	display: flex;
	gap: 0.35rem;
	position: relative;
	width: 100%;
	max-width: 22rem;
}

.combo .combo-native {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	border: 0;
}

.combo-trigger {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.5rem;
	flex: 1 1 auto;
	min-width: 0;
	width: 100%;
	padding: 0.4rem 0.55rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 6px;
	background: #131316;
	color: var(--ink);
	font: inherit;
	font-size: 0.8125rem;
	text-align: left;
	cursor: pointer;
}

.combo-trigger::after {
	content: "";
	flex: none;
	width: 0.4rem;
	height: 0.4rem;
	margin-right: 0.1rem;
	border-right: 1.5px solid var(--muted);
	border-bottom: 1.5px solid var(--muted);
	transform: translateY(-2px) rotate(45deg);
}

.combo-trigger.is-empty { color: var(--faint); }

.combo-trigger:hover,
.combo-trigger:focus-visible {
	outline: none;
	border-color: rgba(255, 255, 255, 0.34);
}

.combo-panel {
	position: absolute;
	z-index: 20;
	top: calc(100% + 4px);
	left: 0;
	right: 0;
	padding: 0.35rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 8px;
	background: #1b1b20;
	box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
}

.combo-search {
	width: 100%;
	padding: 0.35rem 0.45rem;
	margin-bottom: 0.35rem;
	border: 1px solid var(--hairline);
	border-radius: 5px;
	background: #131316;
	color: var(--ink);
	font: inherit;
	font-size: 0.78rem;
}

.combo-search:focus-visible {
	outline: none;
	border-color: rgba(255, 255, 255, 0.28);
}

.combo-list {
	max-height: 13rem;
	overflow-y: auto;
	scrollbar-width: thin;
}

.combo-option {
	padding: 0.35rem 0.45rem;
	border-radius: 5px;
	font-size: 0.8125rem;
	color: var(--ink);
	cursor: pointer;
}

.combo-option:hover,
.combo-option.is-active { background: #26262d; }

.combo-option.is-selected { color: #7fc79a; }

/*
	---- Multiple choice: the same box, several values ---------------------

	The button shows what is picked as chips instead of as text: five roles read
	one after another are a list, set side by side they are a state. The list
	stays open while picking - whoever sets two roles does not want to pull it
	open twice.
*/

.combo--multi .combo-trigger {
	position: relative;
	flex-wrap: wrap;
	justify-content: flex-start;
	gap: 0.3rem;
	min-height: 2rem;
	padding-right: 1.4rem;
}

/*
	The arrow sits at the top right, so that it does not wander along when the
	line wraps.
*/
.combo--multi .combo-trigger::after {
	position: absolute;
	top: 0.75rem;
	right: 0.55rem;
	margin: 0;
}

.combo-chip {
	padding: 0.05rem 0.4rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 999px;
	background: #1b1b20;
	font-size: 0.75rem;
	line-height: 1.5;
	color: var(--ink);
}

.combo-option--multi {
	display: flex;
	align-items: center;
	gap: 0.45rem;
}

/*
	The tick takes up its place even when it is not set - otherwise the labels
	shift back and forth while picking.
*/
.combo-check {
	flex: none;
	width: 0.75rem;
	font-size: 0.75rem;
	color: transparent;
}

.combo-option--multi.is-selected .combo-check { color: #7fc79a; }

/*
	Locked: to be seen, not to be moved. Whoever may not write suppliers still
	sees that this company is a dealer.
*/
.combo-option--multi.is-locked {
	color: var(--faint);
	cursor: default;
}

.combo-option--multi.is-locked:hover { background: transparent; }

.combo-empty,
.combo-error {
	padding: 0.35rem 0.45rem;
	margin: 0;
	font-size: 0.75rem;
	color: var(--faint);
}

.combo-error { color: #d16a5e; }

.combo-foot {
	margin-top: 0.35rem;
	padding-top: 0.35rem;
	border-top: 1px solid var(--hairline);
}

.combo-create {
	width: 100%;
	padding: 0.35rem 0.45rem;
	border: 0;
	border-radius: 5px;
	background: transparent;
	color: var(--muted);
	font: inherit;
	font-size: 0.78rem;
	text-align: left;
	cursor: pointer;
}

.combo-create:hover:not(:disabled) { background: #26262d; color: var(--ink); }
.combo-create:disabled { color: var(--faint); cursor: default; }

/* The "+" beside the picker - it presses the entry in the panel. */
.combo-add {
	flex: none;
	width: 1.9rem;
	padding: 0;
	border: 1px solid var(--hairline-strong);
	border-radius: 6px;
	background: #131316;
	color: var(--muted);
	font: inherit;
	font-size: 0.95rem;
	line-height: 1;
	cursor: pointer;
}

.combo-add:hover,
.combo-add:focus-visible {
	outline: none;
	background: #26262d;
	color: var(--ink);
	border-color: rgba(255, 255, 255, 0.34);
}

@media (max-width: 34rem) {
	.combo { max-width: none; }
}

/* ---- dialog ----------------------------------------------------------- */

body.is-modal { overflow: hidden; }

/*
	A photo at full size (portal.js showPhoto): the same layer as a dialog, only
	without a box - the picture is the content. A click anywhere closes it, like
	Escape.
*/
.modal--photo { align-items: center; cursor: zoom-out; }

.modal-photo {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.5rem;
	max-width: 100%;
	margin: 0;
}

.modal-photo img {
	max-width: 100%;
	max-height: calc(100vh - 8rem);
	border-radius: 8px;
}

.modal-photo figcaption { color: var(--muted); font-size: 0.8125rem; }

.modal {
	position: fixed;
	inset: 0;
	z-index: 50;
	display: flex;
	align-items: flex-start;
	justify-content: center;
	padding: 3rem 1.5rem;
	overflow-y: auto;
	background: rgba(8, 8, 10, 0.72);
	animation: fade 0.16s ease both;
}

/*
	A click beside a dialog that already holds entries does not close it - the
	box gives a short nudge instead (portal.js modalShell).
*/
@keyframes modal-nudge {
	0%, 100% { transform: translateX(0); }
	25% { transform: translateX(-6px); }
	75% { transform: translateX(6px); }
}
.modal-box.is-nudged { animation: modal-nudge 0.22s ease-in-out 2; }
@media (prefers-reduced-motion: reduce) {
	.modal-box.is-nudged { animation: none; outline: 2px solid var(--muted); }
}

.modal-box {
	width: 100%;
	/* As wide as a row editor needs (the suppliers of a new component). */
	max-width: 48rem;
	padding: 1.4rem 1.5rem 1.25rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 12px;
	background: var(--raised);
	box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
	animation: rise 0.22s cubic-bezier(0.22, 1, 0.36, 1) both;
}

.modal-head {
	margin: 0 0 1rem;
	font-size: 1rem;
	font-weight: 400;
	letter-spacing: -0.008em;
	color: var(--ink);
}

.modal-form .row { grid-template-columns: 11rem 1fr; }

.modal-actions {
	display: flex;
	justify-content: flex-end;
	gap: 0.5rem;
	margin-top: 1.1rem;
	padding-top: 0.9rem;
	border-top: 1px solid var(--hairline);
}

/*
	Deleting cannot be taken back - the button says so before you press it. The
	border stays red: a red surface next to "Save" would be the loudest button
	on the page without being the most important one.
*/
.btn--danger {
	border-color: color-mix(in srgb, #d16a5e 45%, transparent);
	color: #d16a5e;
}

.btn--danger:hover, .btn--danger:focus-visible {
	border-color: #d16a5e;
	background: color-mix(in srgb, #d16a5e 14%, transparent);
}

.panel--danger {
	border-color: color-mix(in srgb, #d16a5e 35%, transparent);
	box-shadow: inset 2px 0 0 #d16a5e;
}

.panel--danger .panel-head { color: #d16a5e; }

/*
	Purchase numbers are long and do not belong in the 11rem column of a field
	row - every second one wraps there.
*/
.blockers {
	margin: 0.8rem 0 0;
	padding-left: 1.1rem;
	font-size: 0.8125rem;
	color: var(--ink);
}

.blockers li { margin-bottom: 0.3rem; }

.btn--quiet {
	border-color: transparent;
	background: transparent;
	color: var(--muted);
}

.btn--quiet:hover, .btn--quiet:focus-visible {
	border-color: var(--hairline);
	background: transparent;
	color: var(--ink);
}

@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

@media (max-width: 34rem) {
	.modal { padding: 1.5rem 0.75rem; }
	.modal-form .row { grid-template-columns: 1fr; }
}

/* ---- file field ------------------------------------------------------- */

.file-current {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	flex-wrap: wrap;
	margin-bottom: 0.4rem;
	font-size: 0.8125rem;
}

.input--file {
	padding: 0.3rem 0.4rem;
	font-size: 0.75rem;
	color: var(--muted);
	cursor: pointer;
}

/* The native radio button is otherwise light grey on dark. */
.input--file::file-selector-button {
	margin-right: 0.6rem;
	padding: 0.25rem 0.6rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 5px;
	background: var(--raised);
	color: var(--ink);
	font: inherit;
	font-size: 0.75rem;
	cursor: pointer;
}

.input--file::file-selector-button:hover {
	border-color: rgba(255, 255, 255, 0.3);
	background: #26262d;
}

/* The photos of the notes: tiles, the file field below them. */
.photo-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
	gap: 0.6rem;
	margin-bottom: 0.6rem;
}

.photo-tile {
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
	margin: 0;
	font-size: 0.75rem;
}

.photo-tile img {
	display: block;
	width: 100%;
	aspect-ratio: 4 / 3;
	object-fit: cover;
	border: 1px solid var(--hairline-strong);
	border-radius: 6px;
	background: #131316;
}

.filepick {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	flex-wrap: wrap;
}

.filepick .filepick-native {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	border: 0;
}

.filepick-name {
	font-size: 0.75rem;
	color: var(--faint);
}

.hint {
	margin: 0.75rem 0 1.25rem;
	font-size: 0.75rem;
	line-height: 1.5;
	color: var(--muted);
}

.hint strong { color: var(--ink); font-weight: 500; }

.panel-foot .hint { margin: 0.75rem 0 0; }

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

.btn:disabled {
	border-color: var(--hairline);
	background: transparent;
	color: var(--faint);
	cursor: not-allowed;
}

.btn:disabled:hover {
	border-color: var(--hairline);
	background: transparent;
}

.combo.is-invalid .combo-trigger {
	border-color: #d16a5e;
}

/* ---- tooltip ---------------------------------------------------------- */

/*
	Explanations about a control belong on the element, not permanently on the
	page. The wrapper carries the tooltip, so that it appears on a disabled
	button too - a disabled button gets no mouse events itself.
*/
.tip {
	position: relative;
	display: inline-flex;
}

.tip::after {
	content: attr(data-tip);
	position: absolute;
	bottom: calc(100% + 7px);
	left: 50%;
	z-index: 30;
	width: max-content;
	max-width: 17rem;
	padding: 0.4rem 0.55rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 6px;
	background: #1b1b20;
	box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
	color: var(--muted);
	font-size: 0.7188rem;
	line-height: 1.45;
	text-align: left;
	white-space: normal;
	opacity: 0;
	transform: translate(-50%, 3px);
	pointer-events: none;
	transition: opacity 0.15s ease 0.3s, transform 0.15s ease 0.3s;
}

.tip:hover::after,
.tip:focus-within::after {
	opacity: 1;
	transform: translate(-50%, 0);
}

/* An empty tooltip: do not fade it in at all. */
.tip[data-tip=""]::after { content: none; }

@media (hover: none) {
	.tip::after { display: none; }
}

/* ---- tabs ------------------------------------------------------------- */

.tabs {
	display: flex;
	gap: 1.25rem;
	margin-bottom: 1.25rem;
	border-bottom: 1px solid var(--hairline);
}

.tab {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	padding: 0 0 0.55rem;
	margin-bottom: -1px;
	border-bottom: 1.5px solid transparent;
	font-size: 0.8125rem;
	color: var(--muted);
	text-decoration: none;
	transition: color 0.18s ease, border-color 0.18s ease;
}

.tab:hover { color: var(--ink); }

.tab.is-active {
	color: var(--ink);
	border-bottom-color: var(--ink);
}

.tab-count {
	padding: 0.05rem 0.35rem;
	border-radius: 999px;
	background: rgba(255, 255, 255, 0.07);
	font-size: 0.6875rem;
	font-variant-numeric: tabular-nums;
	color: var(--muted);
}

.tab.is-active .tab-count { color: var(--ink); }

/*
	---- Manufacturing: one block per order ----------------------------------

	The order of the blocks is the priority. There is no second ranking above
	it: the same row twice on one page is exactly what made the board
	unreadable.
*/

.job-id {
	display: flex;
	align-items: baseline;
	gap: 0.8rem;
	min-width: 0;
}

.job-rank {
	flex: none;
	min-width: 1rem;
	font-size: 0.875rem;
	font-variant-numeric: tabular-nums;
	color: var(--faint);
}

.job-due {
	flex: none;
	text-align: right;
	font-size: 0.75rem;
	line-height: 1.45;
}

.job-due-date {
	display: block;
	color: var(--ink);
	font-weight: 500;
}

/*
	Only the deadline carries colour. If the whole block were coloured, every
	order would shout equally loudly and the urgency would be invisible again.
*/
.job-due-note { color: var(--c, var(--muted)); }
.job-due-note--plain { color: var(--faint); }

/*
	The time left in colour, the appointment neutral: otherwise the date lights
	up too, and it decides nothing.
*/
.job-due-sub {
	display: block;
	color: var(--faint);
}

/*
	The figures and the action on one line. A footer of its own per block made
	the page three times as tall as its content.
*/
.job-bar {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 0.7rem;
}

/*
	A short bar right beside its number - drawn over the full width, the bar and
	the number lie so far apart that you look twice.
*/
.job-bar .meter {
	flex: none;
	width: 7rem;
	margin: 0;
}

.job-action { margin-left: auto; margin-bottom: 0; }

.job-count {
	flex: none;
	font-size: 0.75rem;
	color: var(--muted);
	font-variant-numeric: tabular-nums;
}

.job-count strong { color: var(--ink); font-weight: 500; }

.panel-row:has(.job-id) { margin-bottom: 0.7rem; }

/*
	The note closes the header off, the block gives the spacing - otherwise the
	two add up to a blank line per order.
*/
.job-id .panel-note { margin-bottom: 0; }

.job-hint {
	margin: 0.7rem 0 0;
	font-size: 0.75rem;
	color: var(--faint);
}

.queue-note {
	margin: -0.4rem 0 1rem;
	font-size: 0.75rem;
	color: var(--faint);
}

/*
	The closing line is only meant for the rows without an appointment at the
	end. In narrow windows it falls away instead of pushing the first sentence
	onto two lines.
*/
@media (max-width: 60rem) {
	.queue-note-aside { display: none; }
}

/*
	---- panels that fold open ----------------------------------------------

	Two cases, one mechanism: manufacturing's component panel and the process
	steps of an installation.
*/

.drawer-head,
.step-head {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	margin: 0;
	font-size: 0.75rem;
	font-weight: 500;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--muted);
	cursor: pointer;
	list-style: none;
	transition: color 0.18s ease;
}

.drawer-head::-webkit-details-marker,
.step-head::-webkit-details-marker { display: none; }

/*
	The short version in the summary of a section that is folded shut: it says
	what stands inside and does not push itself forward.
*/
.step-sum {
	margin-left: auto;
	font-size: 0.6875rem;
	letter-spacing: 0;
	text-transform: none;
	color: var(--faint);
	white-space: nowrap;
}

.drawer-head::before,
.step-head::before {
	content: "";
	flex: none;
	border-left: 4px solid currentColor;
	border-top: 3.5px solid transparent;
	border-bottom: 3.5px solid transparent;
	transition: transform 0.18s ease;
}

.drawer-head:hover, .drawer-head:focus-visible,
.step-head:hover, .step-head:focus-visible { color: var(--ink); }

.drawer[open] .drawer-head,
.step[open] .step-head { margin-bottom: 0.9rem; }

.drawer[open] .drawer-head::before,
.step[open] .step-head::before { transform: rotate(90deg); }

/*
	---- the step that is up now ---------------------------------

	A coloured edge and a lighter head, in the colour of the current phase - the
	same one the dot beside the number and the progress bar carry. What is done
	and what comes later stays shut, so that the next step does not have to be
	looked for. The edge and the open state say that already; a label beside it
	would have said it a second time.
*/

.step.is-current {
	border-color: var(--hairline-strong);
	box-shadow: inset 2px 0 0 var(--step-c, #4a90d9);
}

.step.is-current .step-head { color: var(--ink); }

/*
	Steps folded shut are a list, not a row of cards - the gap between them may
	be smaller than between open panels.
*/
.step:not([open]) { margin-bottom: 0.5rem; }

.drawer-note {
	font-weight: 400;
	letter-spacing: 0;
	text-transform: none;
	color: var(--faint);
}

@media (max-width: 34rem) {
	.job-due { text-align: left; }
	.job-action { margin-left: 0; }
	.drawer-note { display: none; }
}

/* ---- sortierbare Spaltenkoepfe ---------------------------------------- */

.sort {
	display: inline-flex;
	align-items: baseline;
	gap: 0.3rem;
	color: inherit;
	text-decoration: none;
	transition: color 0.18s ease;
}

/*
	A head is a link and has to say so before the mouse is on it. It was a
	plain label: the cursor turned into a hand on hover, which only helps
	somebody who is already hovering - so the sorting went unfound, and a list
	that opens in an order nobody chose looks like a list that cannot be
	sorted. A dotted line under the word says it and costs no width, which
	matters here: these tables are wide enough to scroll already.
*/
.sort {
	text-decoration: underline dotted var(--hairline-strong);
	text-underline-offset: 0.3em;
}

.sort:hover, .sort:focus-visible {
	color: var(--ink);
	text-decoration-color: currentColor;
}

.sort.is-active { color: var(--ink); text-decoration: none; }

/*
	The arrow stands only on the active column but must not shift the width of
	the other heads - hence without a placeholder of its own.
*/
.sort-arrow { font-size: 0.75rem; line-height: 1; }

.list-time {
	margin-left: 0.45rem;
	font-variant-numeric: tabular-nums;
	color: var(--muted);
}

/*
	---- The dashboard: a stock, not a process --------------------------------

	The pages of the process show one job, the dashboard shows a stock: the
	parks, and per park what stands there. For that the table needs three things
	more - right-aligned numbers, a second line inside a cell, and a serial
	number that can be compared character by character.
*/

/*
	Numbers belong at the right edge: "2 of 12" and "11" under each other can
	otherwise only be compared with a finger on the screen.
*/
.table th.num, .table td.num {
	text-align: right;
	font-variant-numeric: tabular-nums;
}

/*
	The closing line to the number - "of 3". Muted, because the reference value
	does not answer the question but only places it.
*/
.cell-of { color: var(--muted); }

/*
	A second line inside a cell: the state under the city, the warehouse under
	the location, "planned" under the date. As a column of its own each of them
	would be a column that stays empty in nine rows out of ten.
*/
.cell-note {
	display: block;
	margin-top: 0.28rem;
	font-size: 0.6875rem;
	color: var(--faint);
}

/*
	Serial numbers are compared, not read: SH-00004 and SH-00006 differ in one
	character, and it has to stand in the same place as in the row above.
*/
.serial {
	font-variant-numeric: tabular-nums;
	letter-spacing: 0.02em;
}

/*
	Two tables on one page need more air between them than a heading needs from
	its own table - otherwise the head belongs to the table above it visually.
*/
.panel-head--spaced { margin-top: 2.25rem; }

/*
	The row of figures stands right under the page header; the header's spacing
	is enough, and a second block below it turns that into a blank line.
*/
.stats + .panel-head { margin-top: 0.5rem; }

/*
	---- The quantity field -------------------------------------------------

	A narrow number field for counts in table rows - manufacturing, the demand
	and the accessories use it.
*/

.qty {
	width: 4.5rem;
	padding: 0.35rem 0.5rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 6px;
	background: #131316;
	color: var(--ink);
	font: inherit;
	font-size: 0.8125rem;
	text-align: right;
}
.qty:focus-visible { border-color: rgba(255, 255, 255, 0.3); outline: none; }

/*
	---- Manufacturing: versions, warehouses, stock ----------------------

	The demand of an installation has to be read per hardware version; on the
	manufacturing page every version gets its row under the total.
*/

.line {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 0.7rem;
	margin-top: 0.7rem;
	padding-top: 0.6rem;
	border-top: 1px solid rgba(255, 255, 255, 0.04);
}

.line-label {
	min-width: 11rem;
	font-size: 0.8125rem;
	color: var(--ink);
}

/*
	Orders under their version's row: indented, so that the mapping is readable
	without lines.
*/
.row--sub .row-label { padding-left: 1.1rem; }

.badge--inline { margin-left: 0.5rem; vertical-align: 0.1em; }

/*
	An order for the warehouse: the item, the count and the button on one line.
*/
.free-form {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 0.6rem;
}

.free-form .input, .free-form .combo { max-width: 26rem; flex: 1 1 16rem; }

/* The figures of a version at the right in the head of the stock block. */
.figures {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem 1.1rem;
	justify-content: flex-end;
	font-size: 0.75rem;
	color: var(--muted);
	text-align: right;
}

.figure strong {
	font-size: 1.05rem;
	font-weight: 400;
	font-variant-numeric: tabular-nums;
	color: var(--ink);
}

/* Tables that open nothing: no hand, no highlight. */
.table--static tbody tr { cursor: default; }
.table--static tbody tr:hover { background: transparent; }
.table--static td { white-space: normal; }

/*
	---- The preview of a CSV import -----------------------------------

	One row per row of the file: what it would do, what it runs into, and the
	tick that allows an overwrite. It opens nothing, and the conflict sentences
	need wrapping.
*/

.table--import tbody tr { cursor: default; }
.table--import tbody tr:hover { background: transparent; }
.table--import td { white-space: normal; vertical-align: top; }

/*
	A row with an error stands out without colouring the table red: only its
	left edge carries the colour, as with the messages.
*/
.import-row.is-error td:first-child { box-shadow: inset 2px 0 0 #d16a5e; }
.import-row.is-skipped td:first-child { box-shadow: inset 2px 0 0 #e0912f; }
.import-row.is-unchanged td { color: var(--muted); }

.import-note {
	margin: 0.3rem 0 0;
	font-size: 0.7188rem;
	line-height: 1.45;
	color: var(--muted);
}
.import-note--err { color: #d16a5e; }
.import-note s { color: var(--faint); }

/*
	The disclosure with the column names recognised stands inside a panel and so
	brings no border of its own along.
*/
.drawer--inline { margin-top: 1rem; border: 0; padding: 0; background: transparent; }

/*
	A cell that only gives information - the row number, the list of columns.
*/
.cell-quiet { color: var(--muted); font-variant-numeric: tabular-nums; }

.is-short { color: #e0912f; font-weight: 500; }
.is-short-row td { color: var(--muted); }
.is-short-row td:first-child { color: #d16a5e; }

.inline-form--tight { margin-left: 0; }

/* Tables that fold open inside a panel, not as a panel of their own. */
.drawer--inner {
	margin-top: 0.9rem;
	padding-top: 0.6rem;
	border-top: 1px solid var(--hairline);
}

.drawer--inner .table-wrap { margin-top: 0.2rem; }

@media (max-width: 34rem) {
	.figures { justify-content: flex-start; text-align: left; }
}

/*
	---- The table editor: sensor hubs per version ---------------------------

	One row per version: the select field, the count, the cross. The total
	stands below it - it is what the workflow and manufacturing read.
*/

.row--table { align-items: start; }
.row--table .row-label { padding-top: 0.45rem; }

.tbl-rows { display: flex; flex-direction: column; gap: 0.4rem; }

.tbl-row {
	display: flex;
	align-items: center;
	gap: 0.5rem;
}

/*
	---- One line per sensor hub, in the step of the installer ---------------

	The number, the park, the acceptance and the QR sheet on one line; what the
	installer noted beneath it, in their words. It wraps: a park name and a
	note are as long as they are, and on a phone the line is 390 px.
*/
.hub-rows { gap: 0.55rem; }
.hub-rows .tbl-row { flex-wrap: wrap; gap: 0.35rem; }
.hub-no { font-variant-numeric: tabular-nums; }
.hub-note { color: var(--muted); margin-top: 0.15rem; }

.tbl-row .input, .tbl-row .combo { flex: 1 1 12rem; max-width: 18rem; }

.tbl-remove {
	flex: none;
	width: 1.6rem;
	height: 1.6rem;
	padding: 0;
	border: 1px solid transparent;
	border-radius: 5px;
	background: transparent;
	color: var(--faint);
	font: inherit;
	font-size: 1rem;
	line-height: 1;
	cursor: pointer;
}

.tbl-remove:hover, .tbl-remove:focus-visible {
	border-color: var(--hairline);
	color: #d16a5e;
	outline: none;
}

.tbl-foot {
	display: flex;
	align-items: center;
	gap: 0.8rem;
	margin-top: 0.55rem;
}

.tbl-total { font-size: 0.75rem; color: var(--muted); }
.tbl-total strong { color: var(--ink); font-weight: 500; }

/* The read view of the tables: one row per line. */
.tbl-read { display: flex; flex-direction: column; gap: 0.2rem; }
.tbl-read-line strong { font-weight: 500; font-variant-numeric: tabular-nums; }

/*
	---- The accessories of the packing list --------------------------------

	Option groups with lines. The group switch picks everything, and the line
	has a tick and a quantity of its own. Without JavaScript every group is open
	and the group switch does nothing.
*/

.pick { display: flex; flex-direction: column; gap: 0.35rem; }

.pick-group {
	border: 1px solid var(--hairline);
	border-radius: 8px;
	overflow: hidden;
}

.pick-head {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	padding: 0.45rem 0.7rem;
	background: rgba(255, 255, 255, 0.025);
}

.pick-toggle {
	display: flex;
	align-items: center;
	flex: 1;
	gap: 0.6rem;
	padding: 0;
	border: 0;
	background: transparent;
	color: var(--ink);
	font: inherit;
	font-size: 0.8125rem;
	text-align: left;
	cursor: pointer;
}

.pick-toggle::before {
	content: "";
	flex: none;
	border-left: 4px solid var(--muted);
	border-top: 3.5px solid transparent;
	border-bottom: 3.5px solid transparent;
	transform: rotate(90deg);
	transition: transform 0.18s ease;
}

.pick-group.is-collapsed .pick-toggle::before { transform: none; }
.pick-group.is-collapsed .pick-items { display: none; }

.pick-count {
	margin-left: auto;
	font-size: 0.6875rem;
	font-variant-numeric: tabular-nums;
	color: var(--faint);
}

.pick-items { padding: 0.25rem 0.7rem 0.45rem; }

.pick-item {
	display: flex;
	align-items: center;
	gap: 0.7rem;
	padding: 0.3rem 0;
	font-size: 0.8125rem;
}

.pick-item + .pick-item { border-top: 1px solid rgba(255, 255, 255, 0.04); }

.pick-item .check { flex: 1; color: var(--muted); }
.pick-item.is-on .check { color: var(--ink); }

.pick-stock {
	flex: none;
	min-width: 6rem;
	text-align: right;
	font-size: 0.7188rem;
	font-variant-numeric: tabular-nums;
	color: var(--faint);
}

.pick-stock.is-empty { color: #d16a5e; }

.pick-qty { flex: none; }
.pick-qty:disabled { opacity: 0.35; }

/* The one accessory box and its segments: a heading each, the options beneath. */
.pick-box { margin: 0; font-size: 0.8125rem; font-weight: 600; color: var(--ink); }
.pick-segment { display: flex; flex-direction: column; gap: 0.35rem; }
/* Power supply, mounting, lightning protection: each segment its own block. */
.pick-segment + .pick-segment {
	margin-top: 0.6rem;
	padding-top: 0.75rem;
	border-top: 1px solid var(--hairline);
}
.pick-segment-name {
	margin: 0;
	font-size: 0.6875rem;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--muted);
}
.pick-segment > .pick-segment-name { color: var(--ink); }
.tbl-read .pick-segment-name { margin-top: 0.3rem; }

@media (max-width: 34rem) {
	.pick-stock { display: none; }
	.tbl-row .input, .tbl-row .combo { max-width: none; }
}

/*
	---- Procurement: the line rows ------------------------------------

	Six columns instead of the demand editor's two, so a grid instead of flexbox
	- otherwise the fields wrap in the middle of the row.
*/

.tbl--procurement .tbl-head,
.tbl--procurement .tbl-row {
	display: grid;
	/*
		Weighted by importance, not evenly: the stock item decides whether stock
		is kept, and its placeholder "— collective item" has to stay readable.
		The web link may be narrow, a URL does not fit in any column anyway.
	*/
	grid-template-columns:
		minmax(6rem, 1.4fr)    /* The description */
		minmax(6.5rem, 1.15fr) /* Lagerartikel */
		minmax(4rem, .85fr)    /* Hersteller */
		minmax(4.5rem, .95fr)  /* Hersteller-Nr. */
		minmax(4.5rem, .8fr)   /* Weblink */
		4rem                   /* The quantity */
		5.5rem                 /* Preis */
		1.6rem;                /* Entfernen */
	gap: 0.4rem;
	align-items: center;
}
.tbl--procurement .tbl-head {
	margin-bottom: 0.4rem;
	font-size: 0.6875rem;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--faint);
}
.tbl--procurement .input,
/*
	The combo box replaces the select field visually - without it here the item
	column stays at .combo's base width.
*/
.tbl--procurement .combo { max-width: none; }
.tbl--procurement .qty { width: 100%; }

/*
	The total stands at the right and ends where the column "Price/pcs" ends -
	it is the sum of that column, and to the left of "+ Line" it stood under the
	description. The gap is exactly the remove column plus its spacing (1.6rem +
	0.4rem); without it the number would stand under the cross.
*/
.tbl--procurement .tbl-total {
	margin-left: auto;
	padding-right: 2rem;
	text-align: right;
	font-variant-numeric: tabular-nums;
}

/*
	Item and manufacturer numbers are there to be compared, not to be read -
	proportional digits slip out of line while you do that.
*/
.mono {
	font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
	font-size: 0.9em;
}

/*
	A hint that stays standing, unlike .flash - a purchase still lying there as
	a draft, for instance.
*/
.panel--warn { border-left: 2px solid #e0912f; }

/*
	A row with the name at the left and the action at the right - the open
	items.
*/
.catalog {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.7rem 0;
}
.catalog + .catalog { border-top: 1px solid rgba(255, 255, 255, 0.04); }
.catalog:first-of-type { padding-top: 0; }
.catalog:last-of-type { padding-bottom: 0; }
.catalog-name { font-size: 0.8125rem; color: var(--ink); }
.catalog-hint {
	display: block;
	margin-top: 0.2rem;
	font-size: 0.7188rem;
	line-height: 1.45;
	color: var(--faint);
}
.catalog-action { flex: 0 0 auto; text-align: right; }

/*
	---- The delivery addresses of an installation ------------------------

	One card per address: the recipient with the cross on top, the address and
	the addresses on file beside it, the parks delivered there underneath. A
	card and not a line, because a delivery address is three lines of text.
*/
.tbl--deliveries .tbl-rows { gap: 0.9rem; }
.tbl--deliveries .dlv-row {
	display: flex;
	flex-direction: column;
	align-items: stretch;
	gap: 0.45rem;
	padding: 0.7rem;
	border: 1px solid var(--hairline);
	border-radius: 8px;
}
.dlv-head { display: flex; align-items: center; gap: 0.5rem; }
.dlv-head .input, .dlv-head .combo { flex: 1 1 auto; max-width: 22rem; }
.dlv-head .tbl-remove { margin-left: auto; }
.dlv-body { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 0.5rem; }
.dlv-body .dlv-address { flex: 1 1 14rem; max-width: 22rem; min-height: 4.6rem; }
/* The address form: addition and street across, postcode and town side by side. */
.dlv-form { flex: 1 1 16rem; max-width: 22rem; display: grid; grid-template-columns: 6.5rem minmax(0, 1fr); gap: 0.4rem; }
.dlv-form .dlv-wide, .dlv-form .dlv-noted { grid-column: 1 / -1; }
.dlv-form .input { width: 100%; min-width: 0; }
.dlv-noted { margin: 0; font-size: 0.75rem; color: var(--muted); }
.dlv-body .adr-offers { flex: 1 1 12rem; }
.dlv-parks { display: flex; flex-wrap: wrap; gap: 0.3rem 1rem; font-size: 0.8125rem; }
.dlv-park { display: inline-flex; align-items: center; gap: 0.35rem; cursor: pointer; }

/*
	---- The deliveries of an installation (shipment section) ---------------

	A card per delivery address: who and where on top, one line per delivery
	note with its consignment, and the button for the next note at the foot.
*/
.dlv-list { display: flex; flex-direction: column; gap: 0.75rem; max-width: 46rem; }
.dlv-card {
	display: flex;
	flex-direction: column;
	gap: 0.45rem;
	padding: 0.7rem 0.8rem;
	border: 1px solid var(--hairline);
	border-radius: 8px;
}
.dlv-card-head { display: flex; align-items: baseline; gap: 0.6rem; }
.dlv-card-title { font-weight: 500; color: var(--ink); }
.dlv-state { font-size: 0.75rem; color: #6fcf97; }
.dlv-card-sub { margin: 0; font-size: 0.8125rem; color: var(--muted); }
/*
	A note: its name, state and shipping order on the first line, the carrier
	and the tracking number on the next, where its DHL label stands beneath -
	each a line of its own, wrapping between its parts, never inside a word.
*/
.dlv-note { display: flex; flex-wrap: wrap; align-items: center; gap: 0.45rem 0.8rem; }
.dlv-note + .dlv-note { padding-top: 0.55rem; border-top: 1px solid var(--hairline); }
.dlv-note-head { flex: 1 1 auto; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.15rem 0.35rem; }
.dlv-note-head > * { white-space: nowrap; }
.dlv-consignment { flex: 1 1 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.75rem; font-size: 0.8125rem; }
.dlv-consignment .combo, .dlv-carrier { flex: 0 0 8rem; }
.dlv-carrier-name { font-weight: 500; color: var(--ink); }
.dlv-number { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 0.1rem 0.45rem; }
.dlv-track { word-break: break-all; }
.dlv-follow { font-size: 0.8125rem; white-space: nowrap; }
.dlv-tracking { flex: 1 1 11rem; max-width: 16rem; }
.dlv-note .receipt-actions { margin-left: auto; }
.dlv-boxes { flex: 1 1 100%; display: flex; flex-direction: column; gap: 0.3rem; }
.dlv-box { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.6rem; font-size: 0.8125rem; }
.dlv-box-no { flex: 0 0 5.5rem; color: var(--ink); }
.dlv-box-lines { flex: 1 1 12rem; }
.dlv-box .input--qty { width: 5rem; }
/* A DHL label: on paper or still waiting for the printer (nuno_portal.print_queue). */
.dlv-label { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.3rem 0.6rem; font-size: 0.8125rem; color: var(--muted); }
.dlv-label.is-late { color: #d16a5e; }
/* A note's own label: a line of its own, its buttons at the end of it. */
.dlv-note > .dlv-label { flex: 1 1 100%; }
.dlv-label-actions { display: inline-flex; flex-wrap: wrap; gap: 0.35rem; margin-left: auto; }
/* A box's size, length × width × height in cm. */
.dlv-size { display: inline-flex; align-items: center; gap: 0.25rem; }
.dlv-size .input--qty, .dlv-box .dlv-size .input--qty { width: 3.6rem; }
/*
	The DHL label of a note: the box on one line, the insurance beneath it,
	the button last - in a box of its own under the carrier. With the label
	offered, the tracking number is typed only by choice: the switch below
	shows the field and puts the label's controls away. One way or the other.
*/
.dlv-api {
	flex: 1 1 100%;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 0.55rem;
	padding: 0.65rem 0.75rem;
	border: 1px dashed var(--hairline-strong);
	border-radius: 8px;
}
.dlv-api-fields { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.5rem 1rem; }
.build-field--size { flex: 0 0 auto; }
.dlv-manual { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.8125rem; color: var(--muted); cursor: pointer; }
.dlv-note.has-api :is(.dlv-tracking, .dlv-follow) { display: none; }
.dlv-note.has-api:has(.dlv-manual input:checked) :is(.dlv-tracking, .dlv-follow) { display: inline-block; }
.dlv-note.has-api:has(.dlv-manual input:checked) .dlv-api { display: none; }
/* "No shipment needed" ticked: no parcel, so the parcel's fields step aside. */
.dlv-list .dlv-note:has(.dlv-noship input:checked) :is(.dlv-carrier, .combo:has(> .dlv-carrier), .dlv-tracking, .dlv-follow, .dlv-api, .dlv-manual, .receipt-actions) { display: none; }
/* Additional insurance: off until ticked, then the amount per box beside it. */
.dlv-insure { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.9rem; font-size: 0.8125rem; color: var(--muted); }
.dlv-insure:not(:has(input[type="checkbox"]:checked)) .dlv-insure-box { display: none; }
.dlv-insure-box { display: inline-flex; align-items: center; gap: 0.4rem; }
.dlv-insure-box .input--qty { width: 6rem; }

/*
	---- The boxes of a delivery-note draft --------------------------------

	A line per piece of the delivery, a column per box, and a click puts the
	line into its box: the cells are radios dressed as squares, the chosen one
	filled with the line's count. A line ticked "split" trades its squares for
	numbers - box 1 is the rest and takes none. The last column is the new box.
*/
.box-editor { display: flex; flex-direction: column; gap: 0.55rem; }
.box-editor .panel-head, .box-editor .panel-note { margin-bottom: 0; }
.panel-subhead { margin: 0; font-size: 0.8125rem; font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
.box-table { display: flex; flex-direction: column; gap: 0.3rem; overflow-x: auto; }
.box-head, .box-line {
	display: grid;
	grid-template-columns: minmax(8rem, 1fr) repeat(var(--boxes), minmax(3.25rem, 5.5rem));
	gap: 0.4rem;
	align-items: center;
}
.box-head { font-size: 0.6875rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--faint); }
.box-label { font-size: 0.8125rem; }
.box-group { margin-top: 0.45rem; font-size: 0.6875rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--faint); }
.box-cell { display: flex; justify-content: center; }
.box-cell .input--qty { width: 100%; }
.box-cell--rest { color: var(--muted); font-variant-numeric: tabular-nums; }
.box-cell--rest.is-over { color: #d16a5e; }
.box-head .is-new { color: var(--muted); }
.box-line:not(.box-head):hover { background: rgba(255, 255, 255, 0.025); }
.box-pick { display: flex; justify-content: center; width: 100%; cursor: pointer; }
.box-pick input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.box-pick-mark {
	display: inline-flex; align-items: center; justify-content: center;
	width: 100%; height: 2rem;
	border: 1px dashed var(--hairline-strong); border-radius: 6px;
	color: transparent; font-variant-numeric: tabular-nums; font-size: 0.8125rem;
	transition: background 0.12s, border-color 0.12s;
}
.box-pick:hover .box-pick-mark { border-color: var(--muted); }
.box-pick input:checked + .box-pick-mark { border: 1px solid #4a90d9; background: rgba(74, 144, 217, 0.22); color: var(--ink); font-weight: 600; }
.box-pick input:focus-visible + .box-pick-mark { outline: 2px solid #4a90d9; outline-offset: 1px; }
.box-share { display: none; }
.box-split { margin-left: 0.4rem; font-size: 0.6875rem; color: var(--faint); cursor: pointer; white-space: nowrap; }
.box-split input { width: 11px; height: 11px; vertical-align: -1px; accent-color: #4a90d9; }
.box-line:has(.box-split input:checked) .box-pick { display: none; }
.box-line:has(.box-split input:checked) .box-share { display: flex; justify-content: center; }
.ls-move { display: inline-flex; align-items: flex-end; gap: 0.4rem; }
.ls-move select.input, .ls-move .combo { min-width: 10rem; }
.box-line--weight { padding-top: 0.3rem; border-top: 1px solid var(--hairline); }
.box-contents { flex: 1 1 100%; font-size: 0.8125rem; }

/*
	---- The solar parks of an installation ---------------------------------

	Two lines per park: the park, its hubs and the cross; under them who
	stands behind it - the operator, the direct marketer and the contract
	partner side by side. Five pickers on one line do not fit the field column,
	and the park is the row's name, so it keeps the first line. The empty
	picker says which party it is (nuno_parks.html), so no column heads.
*/
/*
	Every park is numbered, and a hairline stands between two of them: a row is
	two lines of pickers, and without either the operator of the first park
	read as belonging to the second. A counter and not a number in the
	template: rows come and go in the browser ("+ Solar park", the cross), and
	the numbers count along without a script.
*/
.tbl--parks .tbl-rows { gap: 0.75rem; counter-reset: park; }
.tbl--parks .tbl-row {
	display: grid;
	grid-template-columns: 1.4rem repeat(3, minmax(0, 1fr)) 4.5rem 1.6rem;
	gap: 0.35rem 0.4rem;
	align-items: center;
	counter-increment: park;
}
.tbl--parks .tbl-row + .tbl-row {
	padding-top: 0.75rem;
	border-top: 1px solid var(--hairline);
}
.tbl--parks .tbl-row::before {
	content: counter(park);
	grid-column: 1;
	grid-row: 1;
	font-size: 0.75rem;
	font-variant-numeric: tabular-nums;
	color: var(--muted);
	text-align: center;
}
/*
	The children: the hidden key, the park, the hubs, the parties, the cross.
	A combo box wraps its select, so by place and not by class. The parties
	end where the park ends.
*/
.tbl--parks .tbl-row > :nth-child(2) { grid-column: 2 / 5; grid-row: 1; }
.tbl--parks .tbl-row > .tbl-qty,
.tbl--parks .tbl-row > .park-versions { grid-column: 5; grid-row: 1; }
.tbl--parks .tbl-row > .tbl-qty,
.tbl--parks .park-versions > .tbl-qty { width: 100%; }
/*
	Several versions at the site: each park gets its lines of version × count
	under its parties, with "+ Version" behind them - a line of its own, the
	park picker takes the place of the count.
*/
.tbl--parks .tbl-row--versions > :nth-child(2) { grid-column: 2 / 6; }
.tbl--parks .tbl-row--versions > .park-versions {
	grid-column: 2 / 6;
	grid-row: 3;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.35rem 0.75rem;
}
.park-hub { display: inline-flex; align-items: center; gap: 0.4rem; }
.park-hub .combo, .park-hub .input { width: 14rem; max-width: 100%; }
.park-hub .tbl-qty { width: 4.5rem; }
.tbl--parks .tbl-row > :nth-child(4) { grid-column: 2; grid-row: 2; }
.tbl--parks .tbl-row > :nth-child(5) { grid-column: 3; grid-row: 2; }
.tbl--parks .tbl-row > :nth-child(6):not(.tbl-remove) { grid-column: 4; grid-row: 2; }
.tbl--parks .tbl-remove { grid-column: 6; grid-row: 1; }
.tbl--parks .input, .tbl--parks .combo { max-width: none; min-width: 0; }
/*
	In a narrow column - the create form, a phone - three pickers with their
	"+" side by side leave none of them room for a name: then one party per
	line.
*/
.tbl--parks { container-type: inline-size; }
@container (max-width: 44rem) {
	.tbl--parks .tbl-row > :nth-child(4) { grid-column: 2 / 6; grid-row: 2; }
	.tbl--parks .tbl-row > :nth-child(5) { grid-column: 2 / 6; grid-row: 3; }
	.tbl--parks .tbl-row > :nth-child(6):not(.tbl-remove) { grid-column: 2 / 6; grid-row: 4; }
	.tbl--parks .tbl-row--versions > .park-versions { grid-row: 5; }
}
/*
	The create form's column is narrower still: beside the hubs and the cross
	the park's name had some 80 px and broke inside its words. Then the park
	gets the line to itself, the hubs the one below it, the parties the rest.
*/
@container (max-width: 30rem) {
	.tbl--parks .tbl-row { grid-template-columns: 1.4rem minmax(0, 1fr) 1.6rem; }
	.tbl--parks .tbl-row > :nth-child(2) { grid-column: 2; grid-row: 1; }
	.tbl--parks .tbl-remove { grid-column: 3; grid-row: 1; }
	.tbl--parks .tbl-row > .tbl-qty,
	.tbl--parks .tbl-row > .park-versions { grid-column: 2; grid-row: 2; width: 6rem; justify-self: start; }
	.tbl--parks .tbl-row--versions > .park-versions { grid-column: 2 / -1; grid-row: 2; width: auto; }
	.tbl--parks .tbl-row > :nth-child(4) { grid-column: 2 / -1; grid-row: 3; }
	.tbl--parks .tbl-row > :nth-child(5) { grid-column: 2 / -1; grid-row: 4; }
	.tbl--parks .tbl-row > :nth-child(6):not(.tbl-remove) { grid-column: 2 / -1; grid-row: 5; }
}
/* A long name wraps between its words, never inside one. */
.tbl--parks .combo-trigger { overflow-wrap: normal; word-break: normal; }

@media (max-width: 60rem) {
	/* Six columns do not fit on a phone - then one under the other. */
	.tbl--procurement .tbl-head { display: none; }
	.tbl--procurement .tbl-row { grid-template-columns: 1fr 1fr 1.6rem; }
	/*
		Without the price column there is nothing for the total to belong under.
	*/
	.tbl--procurement .tbl-total { padding-right: 0; }
}


/*
	---- Manufacturing: build now ------------------------------------------

	The page begins with the building, not with the demand: pick the version,
	pick the part, the count. The parts are cards with a radio button - the
	stock and the material state stand on them straight away, so that the choice
	is not answered only after the click.
*/

/*
	The product: a select field above the part cards. It used to stand here as a
	row of pills in which the one picked had only a lighter border - with two
	versions that read as two buttons, one of which is off.
*/
.build-field--product {
	margin-bottom: 0.9rem;
	max-width: 22rem;
}

.build-field--product .input { max-width: none; }

.build-group {
	margin: 0 0 0.9rem;
	padding: 0;
	border: 0;
	min-width: 0;
}

.build-legend {
	padding: 0;
	margin-bottom: 0.5rem;
	font-size: 0.75rem;
	font-weight: 500;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--muted);
}

/* With JavaScript the bar at the top takes the label over. */
.build-form.is-enhanced .build-legend { display: none; }

.build-parts {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
	gap: 0.5rem;
}

.build-part {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 0.2rem;
	padding: 0.7rem 0.85rem;
	border: 1px solid var(--hairline);
	border-radius: 9px;
	background: #131316;
	cursor: pointer;
	transition: border-color 0.18s ease, background-color 0.18s ease;
}

.build-part:hover { border-color: var(--hairline-strong); }

/* The radio button itself stays invisible, the card shows the choice. */
.build-part input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	opacity: 0;
	pointer-events: none;
}

.build-part:has(input:checked) {
	border-color: var(--ink);
	background: rgba(255, 255, 255, 0.05);
}

.build-part:has(input:focus-visible) { outline: 2px solid rgba(255, 255, 255, 0.35); outline-offset: 2px; }

.build-part--hub { border-style: dashed; }
.build-part--hub:has(input:checked) { border-style: solid; }

.build-part-name {
	font-size: 0.8125rem;
	color: var(--ink);
}

.build-part-meta {
	font-size: 0.6875rem;
	line-height: 1.4;
	color: var(--muted);
	font-variant-numeric: tabular-nums;
}

.build-part-short {
	font-size: 0.6875rem;
	line-height: 1.4;
	color: #e0912f;
}

.build-tag {
	display: inline-block;
	margin-left: 0.3rem;
	padding: 0.05rem 0.4rem;
	border: 1px solid rgba(91, 179, 224, 0.5);
	border-radius: 999px;
	font-size: 0.625rem;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: #5bb3e0;
	vertical-align: 0.15em;
}

.build-hint {
	margin: 0.6rem 0 0;
	font-size: 0.75rem;
	color: var(--faint);
}

.build-row {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	gap: 0.9rem 1.25rem;
	margin-top: 1rem;
}

.build-field {
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
	min-width: 0;
}

.build-field--installation { flex: 1 1 18rem; }
.build-field--serials { flex: 1 1 18rem; }
/* The parcel weight stands in the button row of the packing list. */
.build-field--parcel { flex: 0 0 6.5rem; }
.build-field--parcel .input { max-width: none; }
.build-field--installation .input, .build-field--installation .combo,
.build-field--serials .input { max-width: none; }

.build-label {
	font-size: 0.7188rem;
	color: var(--muted);
}

.build-optional {
	margin-left: 0.3rem;
	color: var(--faint);
	font-weight: 400;
}
/*
	The changing hint stands under the field and wraps at its width. In the
	label line it pushed the field out of the row as soon as the text became two
	lines - and it is a different length for every part.
*/
.build-help {
	display: block;
	margin: 0.1rem 0 0;
	/*
		The size explicitly: in the label line the hint inherited its 0.7188rem;
		under the field it would otherwise inherit the base size and would be
		louder than the label above it.
	*/
	font-size: 0.7188rem;
	line-height: 1.45;
}

/* ---- Manufacturing: the running orders ------------------------------- */

.panel-head--section {
	display: flex;
	align-items: baseline;
	gap: 0.6rem;
	margin: 1.75rem 0 0.9rem;
}

.kind {
	display: inline-block;
	margin-left: 0.5rem;
	padding: 0.1rem 0.5rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 999px;
	font-size: 0.6563rem;
	font-weight: 400;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--muted);
	vertical-align: 0.15em;
}

.kind--hub { border-color: rgba(79, 174, 106, 0.5); color: #4fae6a; }
.kind--carrier { border-color: rgba(91, 179, 224, 0.5); color: #5bb3e0; }

.job--queue .panel-row:has(.job-id) { margin-bottom: 0.5rem; }

/*
	Reporting it finished: the count, the serial numbers, the button - on one
	row that wraps when it gets tight.
*/
.produce {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: 0.6rem 0.9rem;
	margin-top: 0.8rem;
	padding-top: 0.7rem;
	border-top: 1px solid rgba(255, 255, 255, 0.05);
}

.produce-field {
	display: flex;
	flex-direction: column;
	gap: 0.25rem;
}

.produce-field--grow { flex: 1 1 16rem; }
.produce-field--grow .input { max-width: none; }

.produce-carriers { flex: 1 1 16rem; }

.produce-summary {
	font-size: 0.75rem;
	color: var(--muted);
	cursor: pointer;
	list-style: none;
}

.produce-summary::-webkit-details-marker { display: none; }
.produce-summary::before {
	content: "";
	display: inline-block;
	margin-right: 0.45rem;
	border-left: 4px solid currentColor;
	border-top: 3.5px solid transparent;
	border-bottom: 3.5px solid transparent;
	transition: transform 0.18s ease;
}

.produce-carriers[open] .produce-summary::before { transform: rotate(90deg); }
.produce-carriers .chips { margin-top: 0.5rem; }

/* The editor under a read-only list on the item page (build group, bill of
   materials): folded shut, with air above it; opened, it takes the list's
   place instead of repeating it row by row. */
.edit-fold { margin-top: 1.1rem; }
.edit-fold > .produce-summary { display: inline-block; padding: 0.2rem 0; }
.edit-fold > form { margin-top: 0.9rem; }
.panel:has(> .edit-fold[open]) > .table-wrap { display: none; }
.panel:has(> .edit-fold[open]) > .edit-fold { margin-top: 0.4rem; }

.job-hint.is-short { color: #e0912f; }

/* ---- serial numbers as badges ----------------------------------------- */

.chips--serials { margin-top: 0.45rem; }

.chip--serial {
	color: var(--muted);
	font-variant-numeric: tabular-nums;
}

.chip--check {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	cursor: pointer;
}

.chip--check input { width: 13px; height: 13px; accent-color: #4a90d9; }
.chip--check:has(input:checked) { border-color: var(--ink); color: var(--ink); }

/*
	---- The device list: the number and the router per row ----------------

	It takes the place of the count and the number field as soon as a router is
	built in. The number at the left, the select field beside it - that one is
	wider, because the wifi name and the MAC stand there together.
*/

.tbl--devices .tbl-serial { flex: 0 1 11rem; max-width: 12rem; font-variant-numeric: tabular-nums; }
.tbl--devices .tbl-row .combo, .tbl--devices .tbl-router { flex: 1 1 16rem; max-width: 24rem; }
/*
	The pick list of production: the number is a combo box over the prepared
	hubs - number, router, state in one line - and there is no router column.
*/
.tbl--pick .tbl-row .combo { flex: 1 1 22rem; max-width: 32rem; }
.tbl--pick .tbl-empty { margin: 0.5rem 0 0; }

/*
	The number carries its router already - a carrier that has been reported has
	done so since it was built. Instead of the select field the MAC stands
	there, quietly.
*/
.tbl-locked {
	flex: 1 1 16rem;
	max-width: 24rem;
	padding: 0.35rem 0;
	font-size: 0.75rem;
	font-variant-numeric: tabular-nums;
	color: var(--faint);
	cursor: help;
}

/*
	In the form "Build for the warehouse" the list stands under the choice of
	part and not beside it: two columns per row need the width.
*/
.build-field--devices { margin-top: 0.9rem; }
.build-field--devices .tbl { margin-top: 0.35rem; }

/*
	On an order it stands inside the card - there is less room there, so the row
	may wrap.
*/
.produce-field--rows { flex: 1 1 100%; }
.produce-field--rows .tbl-row { flex-wrap: wrap; }

/*
	---- Taking devices on -------------------------------------------------

	The same row list, but four columns instead of two - and a page of its own,
	so there is room for a header above it. On a phone the row wraps and the
	header disappears: it then labels nothing any more, which is what the fields
	have their aria-label for.
*/

.tbl--intake .tbl-head,
.tbl-row--intake {
	display: grid;
	grid-template-columns: minmax(8rem, 1fr) minmax(9rem, 1.1fr) minmax(7rem, 0.9fr) minmax(10rem, 1.3fr) minmax(12rem, 1.6fr) 1.6rem;
	align-items: center;
	gap: 0.5rem;
}
/*
	Without the park field on the serial number (a site without
	setup_installation_config) the column is missing.
*/
.tbl--intake.tbl--without-park .tbl-head,
.tbl--intake.tbl--without-park .tbl-row--intake {
	grid-template-columns: minmax(8rem, 1fr) minmax(10rem, 1.2fr) minmax(8rem, 1fr) minmax(12rem, 1.6fr) 1.6rem;
}
/*
	In the warehouse there is no park: the cell stays, the field does not - that
	way the router does not slip into the park column.
*/
.tbl-row--intake .tbl-park-off { visibility: hidden; }

.tbl--intake .tbl-head {
	margin-bottom: 0.45rem;
	font-size: 0.7188rem;
	color: var(--muted);
}

.tbl--intake .input, .tbl--intake .combo { max-width: none; }
.tbl--intake .tbl-serial { font-variant-numeric: tabular-nums; }

/*
	What "in the warehouse" and "out in the field" mean - under the list, not in
	a manual: the difference decides whether stock comes into being.
*/
.hints {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 0.25rem 0.7rem;
	margin: 1rem 0 0;
	font-size: 0.75rem;
}
.hints dt { color: var(--ink); }
.hints dd { margin: 0; color: var(--muted); }

@media (max-width: 46rem) {
	.tbl--intake .tbl-head { display: none; }
	.tbl-row--intake { grid-template-columns: 1fr 1.6rem; }
	.tbl-row--intake .tbl-serial,
	.tbl-row--intake .tbl-version,
	.tbl-row--intake .tbl-park { grid-column: 1 / -1; }
}

/*
	---- The installation: manufacturing per version --------------------------
*/

.prod-line {
	margin-top: 0.7rem;
	padding-top: 0.6rem;
	border-top: 1px solid rgba(255, 255, 255, 0.04);
}

.prod-line-head {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 0.7rem;
}

.prod-line-label {
	min-width: 11rem;
	font-size: 0.8125rem;
	color: var(--ink);
}

.prod-line .row--sub { padding: 0.3rem 0; border-top: 0; }

@media (max-width: 34rem) {
	.build-parts { grid-template-columns: 1fr 1fr; }
	.prod-line-label { min-width: 0; }
}

/*
	---- The start page: the launcher -----------------------------------

	The icon at the top, the label under it, in a grid five objects wide.

	A fixed column width instead of 1fr, and the tracks centred: the tiles come
	out of nav_items() and are not always five - whoever sees no user
	administration has four. With 1fr the grid filled the whole width and the
	row stood at the left edge instead of under the logo.

	Classes of its own instead of .tiles: /processes and /master-data still need
	text tiles with a description, and a shared grid would be an icon grid
	without icons there.
*/

.launcher {
	display: grid;
	/*
		auto-fit, not repeat(5, ...): five tracks are the upper limit, not the
		default. With five fixed ones an empty track would keep its place -
		whoever sees no user administration has four tiles and the row would
		visibly stand to the left of the middle. auto-fit folds the empty track
		away, and justify-content centres what is left.

		The width has to be definite for that (width instead of fit-content),
		otherwise the browser cannot work the number of tracks out.
	*/
	grid-template-columns: repeat(auto-fit, 8.75rem);
	width: 100%;
	/*
		Five columns plus four gaps - beyond that the row wraps. It gets
		narrower by itself: auto-fit takes tracks away when the room is missing.
	*/
	max-width: 46.15rem;
	justify-content: center;
	gap: 0.6rem;
	margin-top: 0.5rem;
}

.launch {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.65rem;
	padding: 1.15rem 0.55rem 1rem;
	border: 1px solid var(--hairline);
	border-radius: 12px;
	background: var(--raised);
	/*
		It carries the colour for the icon: the icon draws with currentColor and
		lightens up together with the label on hover.
	*/
	color: var(--muted);
	text-decoration: none;
	/*
		The border, the surface, the text colour - and explicitly no
		`transform`.

		The tile used to lift by one pixel on hover (`translateY(-1px)`, with a
		transition). An animated transform lifts the element onto a compositor
		layer of its own and puts it back afterwards; while that happens the
		whole row is rasterised again. The label and the border lie on whole
		pixels and survive that invisibly - the 1.5 px strokes of the icons do
		not: they jumped by a pixel, in the tiles beside it too, and twice when
		moving from one tile to the next. One pixel of lift was not worth that.
		What hover shows is the border, the surface and the lighter text colour.
	*/
	transition: border-color 0.18s ease, background-color 0.18s ease, color 0.18s ease;
}
/*
	The star lies over the tile but does not belong to it (it is a form of its
	own, see below) - without the first line the tile would fall back into its
	resting state as soon as the pointer is on the star.
*/
.launch-slot:hover > .launch,
.launch:hover,
.launch:focus-visible {
	border-color: var(--hairline-strong);
	background: #212127;
	color: var(--ink);
}

.launch-icon {
	width: 26px;
	height: 26px;
	flex: none;
}

.launch-label {
	font-size: 0.75rem;
	line-height: 1.35;
	font-weight: 500;
	color: var(--ink);
	/*
		"Benutzerverwaltung" is one word and wider than the tile - without
		hyphenation it stuck out. lang="de" on the document is there for that.
	*/
	overflow-wrap: break-word;
	hyphens: auto;
}

/*
	A tile that leads out of the portal - today only fleet management. The
	little arrow stands for the fact that a new tab opens here; without it the
	tile looks like any other, and the jump onto a foreign page comes
	unannounced.
*/
.launch--external { position: relative; }
.launch--external::after {
	content: "\2197";
	position: absolute;
	top: 0.45rem;
	right: 0.5rem;
	font-size: 0.7rem;
	line-height: 1;
	color: var(--faint);
}

/*
	The line under a tile's label: on the master data the number of records.
	Quiet, because it is a piece of information and not a title.
*/
.launch-sub {
	font-size: 0.6875rem;
	font-variant-numeric: tabular-nums;
	color: var(--faint);
	margin-top: -0.35rem;
}

/*
	---- The star on a tile ---------------------------------------------

	What is marked stands on the start page in a row of its own right at the
	top. The star itself hangs on every tile in the portal, so under /processes
	and in the master data too - see nuno_launch.html.

	It lies at the top left, because the arrow of the external tile already
	stands at the top right; two marks in the same corner would be neither of
	them.

	Empty it is invisible and only appears when the pointer is on the tile (or
	the focus is inside it): a grey outline, small, so that it is not a second
	icon next to the tile's own. Set, it is a yellow filled star - and that one
	stays standing even without a pointer, otherwise you could not tell that a
	tile lies on the start page. On a phone there is no hover; there the empty
	star always stands there, otherwise you could not reach it.
*/

.launch-slot {
	position: relative;
	/*
		The slot is only the frame around the tile and the star - the tile fills
		it, otherwise it would stand alone at the top of a grid cell that is too
		tall.
	*/
	display: flex;
}
.launch-slot > .launch { flex: 1 1 auto; min-width: 0; }

.favorite { position: absolute; top: 0.3rem; left: 0.3rem; margin: 0; line-height: 0; }

.favorite-stern {
	/*
		`appearance: none` is no cosmetic here but a must: Safari draws the
		first submit button of a form as a default button - a blue capsule in
		the system's accent colour, which neither `background` nor `border` gets
		past. That is exactly what the star looked like there: a blue box below
		the tile.
	*/
	appearance: none;
	-webkit-appearance: none;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 22px;
	height: 22px;
	padding: 0;
	border: 0;
	border-radius: 6px;
	background: none;
	/*
		The empty star. #6b6b76 against the tile's surface (#1d1d21) is 3.2:1 -
		the threshold WCAG wants for controls (text needs 4.5:1, see the
		reckoning at :root). Darker it would be quiet to the point of
		invisibility, lighter it would be a second icon next to the tile's own.
	*/
	color: #6b6b76;
	cursor: pointer;
	/* Invisible until the tile is hovered - see above. */
	opacity: 0;
	transition: color 0.18s ease, opacity 0.18s ease;
}

.favorite-stern svg { width: 13px; height: 13px; }

/*
	Visible: the pointer on the tile (or on the star itself - it lies over the
	tile, not inside it) or the focus in either of them.
*/
.launch-slot:hover .favorite-stern,
.launch-slot:focus-within .favorite-stern { opacity: 1; }

.favorite-stern:hover,
.favorite-stern:focus-visible { color: var(--ink); }

/*
	Set: yellow and filled - that is what a star is - and always to be seen. A
	colour of its own and not the amber of the warnings (#e0912f): a favorite is
	not one.
*/
.launch-slot--favorite .favorite-stern { color: #f3c24b; opacity: 1; }
.launch-slot--favorite .favorite-stern svg { fill: currentColor; }
.launch-slot--favorite .favorite-stern:hover,
.launch-slot--favorite .favorite-stern:focus-visible { color: #f8d67f; }

/*
	Without a pointer there is no hover: the empty star then always stands
	there.
*/
@media (hover: none) {
	.favorite-stern { opacity: 1; }
}

/*
	The favorites row of the start page: a little air below, so that the fixed
	row under it does not look like a wrap of the same row.
*/
.launcher--favorites { margin-bottom: 0.6rem; }

/*
	---- Devices ----------------------------------------------------------

	One card per hardware version, clickable, and the editor behind it: the
	assemblies per hub, and per assembly its components. The rows are the table
	editor of the demand, only wider - an assembly is called something longer
	than a version number - and with the dot for the serial number.
*/

a.device { display: block; color: inherit; text-decoration: none; transition: border-color 0.18s ease, background-color 0.18s ease; }
a.device:hover, a.device:focus-visible { border-color: var(--hairline-strong); background: #212127; }

.tbl-row--wide .tbl-item { flex: 1 1 18rem; max-width: none; }

/* The German name beside the item of an accessory line: the delivery note
   prints it, so a line without one says so in red. */
.tbl-row .tbl-de { flex: 1 1 12rem; max-width: 16rem; }
.tbl-de.is-missing { border-color: color-mix(in srgb, #d16a5e 70%, transparent); }

.tbl-flag {
	display: inline-flex;
	align-items: center;
	gap: 0.3rem;
	font-family: ui-monospace, "SF Mono", Menlo, monospace;
	font-size: 0.6875rem;
	letter-spacing: 0.06em;
	color: var(--muted);
	cursor: pointer;
	white-space: nowrap;
}
.tbl-flag input { margin: 0; accent-color: var(--ink); }
.tbl-flag:has(input:checked) { color: var(--ink); }

/*
	The carrier choice in the form "Build now": it appears as soon as the whole
	hub is picked. The ticks are the count.
*/
.build-carriers {
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
	margin-top: 0.8rem;
	padding: 0.7rem 0.85rem;
	border: 1px dashed var(--hairline-strong);
	border-radius: 9px;
}

.build-carriers .build-part-short { margin: 0; }

/*
	---- The sheet: a document in the portal ---------------------------------

	White, with a shadow, at the width of a letter. It carries its own styles
	(see nuno_delivery_note.html), so that the page and the PDF show the same
	thing - only the frame around it stands here.
*/

.sheet {
	background: #ffffff;
	box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35), 0 8px 24px rgba(0, 0, 0, 0.25);
	/*
		A4, with the PDF's page margin. On the screen the document should have
		the proportions that come out of the printer - `mm` is the right unit
		for that, on the screen too. If the content grows beyond it, the sheet
		grows: then it is simply two pages.
	*/
	width: 210mm;
	min-height: 297mm;
	padding: 14mm 12mm;
	box-sizing: border-box;
	margin: 0 0 1.5rem;
}

/*
	Narrow windows: the sheet keeps its proportions and is scaled down instead
	of being cut off. The factor is worked out by `enhanceSheet` in portal.js,
	because it depends on the window's width.
*/
/*
	Without JavaScript: scroll sideways. With JavaScript `enhanceSheet` sets the
	class `is-fitted`, scales the sheet down to the window's width and gives the
	frame the scaled-down height - then there is nothing to scroll.
*/
.sheet-scroll { overflow-x: auto; margin-bottom: 1.5rem; }
.sheet-scroll.is-fitted { overflow: hidden; }

/*
	The rows in the draft: the label at the left, the inputs at the right beside
	each other. They need more height than a display row, hence classes of their
	own.
*/
.row--edit { align-items: flex-start; }
.row--edit .row-label { display: flex; flex-direction: column; gap: 0.1rem; }

.ls-edit {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: 0.75rem 1rem;
}

.ls-edit .build-field--serials { flex: 1 1 16rem; }
.ls-remove { align-self: flex-end; margin-bottom: 0.15rem; cursor: pointer; }
.ls-name { color: var(--ink); }
.row--edit .build-optional { margin-left: 0; }
.sheet-scroll .sheet { margin-bottom: 0; transform-origin: top left; }

/*
	Printed out of the browser the same document comes out as out of the PDF
	button: the sheet only, without a frame, on A4.
*/
@media print {
	@page { size: A4; margin: 14mm 12mm; }

	body { background: #ffffff; }
	body > *:not(.page),
	.page > *:not(.sheet-scroll),
	.chrome, .tray, .back, .page-head, .panel { display: none !important; }
	/*
		Both selectors, because `.page:has(.sheet-scroll)` above caps the page
		at A4 width and is just as specific as `.page`. Without repeating it the
		page would keep its screen width of 842 px and would run over the edge
		of the printable area (around 700 px after the margin).
	*/
	.page,
	.page:has(.sheet-scroll) { padding: 0; margin: 0; max-width: none; gap: 0; }
	.sheet-scroll { overflow: visible; margin: 0; }
	/*
		The scaling from the screen has to go too: on paper `@page` sets the
		format, and a sheet already scaled down would be scaled down twice.
	*/
	.sheet-scroll .sheet {
		width: auto;
		min-height: 0;
		padding: 0;
		box-shadow: none;
		transform: none !important;
		color-adjust: exact;
		-webkit-print-color-adjust: exact;
	}
	.sheet-scroll.is-fitted { height: auto !important; overflow: visible; }
}

/*
	---- The warehouse -----------------------------------------------------

	Seeing stock and posting it. The table is the counting form at the same
	time: a column "counted" with input fields, and a button below it.
*/

.build-field--item { flex: 1 1 22rem; }
.build-field--item .input, .build-field--item .combo { max-width: none; }
.build-field--note { flex: 1 1 14rem; }
.build-field--note .input { max-width: none; }

/* Receipt / issue as a switch, not as two buttons. */
.segmented {
	display: inline-flex;
	border: 1px solid var(--hairline-strong);
	border-radius: 7px;
	overflow: hidden;
}

.segment { position: relative; }

.segment input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	opacity: 0;
	pointer-events: none;
}

.segment span {
	display: block;
	padding: 0.4rem 0.8rem;
	font-size: 0.8125rem;
	color: var(--muted);
	cursor: pointer;
	transition: background-color 0.18s ease, color 0.18s ease;
}

.segment + .segment span { border-left: 1px solid var(--hairline-strong); }
.segment:hover span { color: var(--ink); }
.segment:has(input:checked) span { background: rgba(255, 255, 255, 0.08); color: var(--ink); }
.segment:has(input:focus-visible) span { outline: 2px solid rgba(255, 255, 255, 0.35); outline-offset: -2px; }

.hint--inline { margin: 0; font-size: 0.7188rem; }

/* Tabs with a search field at the right-hand edge. */
.tabs--with-search { align-items: flex-end; }
.tab-search { margin: 0 0 0.45rem auto; }
.input--search { width: 14rem; max-width: 14rem; padding: 0.3rem 0.55rem; font-size: 0.78rem; }

/*
	Without a rule vertical-align: middle holds. The alignment to the top was
	there for the two-line item cell; since only the item stands there, buttons
	and numbers would stick to the top edge of a row whose warehouse column
	wraps.
*/

.cell-actions { white-space: nowrap; }
.cell-actions .inline-link + .inline-link { margin-left: 0.7rem; }


.stat-value--text { font-size: 1.05rem; padding-top: 0.3rem; }
.stat--wide { flex: 3 1 22rem; }
.stat-text { display: block; font-size: 0.75rem; line-height: 1.5; color: var(--muted); }

/* Movements: receipts green, issues red - the direction at a glance. */
.is-plus { color: #4fae6a; }
.is-minus { color: #d16a5e; }

.figure--link {
	color: var(--muted);
	text-decoration: none;
	border-bottom: 1px solid var(--hairline-strong);
	align-self: center;
}
.figure--link:hover { color: var(--ink); border-bottom-color: currentColor; }

@media (max-width: 34rem) {
	.tab-search { margin-left: 0; width: 100%; }
	.input--search { width: 100%; max-width: none; }
}

/*
	Posting inside the row: the count with a plus and a minus, and beside it the
	counted number with "Set" - side by side, without wrapping. All three stock
	actions thereby stand in the row; before, the setting lay below the table as
	a collective form, and for a single item the way was down to the end of the
	page and back. The current stock stands in bold beside it, so that it is
	clear what all three refer to.
*/
.cell-book { white-space: nowrap; }
.rowbook { display: inline-flex; align-items: center; gap: 0.35rem; }
/*
	The button's label must not wrap. The table is at 100 % width and squeezes
	columns together instead of overflowing - since the third action stands in
	the row, that turned into "+ / Receipt" on two lines. With nowrap the button
	holds together and the table scrolls inside .table-wrap if it must.
*/
.rowbook .btn { white-space: nowrap; }
.qty--row { width: 4.2rem; padding: 0.28rem 0.4rem; }
.rowbook-form { display: none; }
.stock-now { font-weight: 500; font-variant-numeric: tabular-nums; }
/*
	The stock table is at 100 % width, and the width left over went into every
	column alike - behind the build group stood a gap before the right-aligned
	stock. Every column fits its content now, only the item takes up the rest
	and wraps a long name. The build group may wrap too, when a part sits in
	several - but not down to one word per line.
*/
.table--stock .cell-fit { width: 1%; white-space: nowrap; }
.table--stock td.cell-fit--wrap { white-space: normal; min-width: 11rem; }
/* Seven columns: with the usual 1rem at each side the last one ran out of the page. */
.table--stock th, .table--stock td { padding-left: 0.6rem; padding-right: 0.6rem; }
.table--stock th:first-child, .table--stock td:first-child { padding-left: 1rem; }
/*
	The delivery date on a line of its own below "+200 ordered": beside it,
	the note made the whole stock column wide, and the right-aligned numbers
	stood far away from the build group again.
*/
.table--stock .on-order-date { display: block; }

.th-note {
	display: block;
	margin-top: 0.15rem;
	font-size: 0.625rem;
	font-weight: 400;
	letter-spacing: 0;
	text-transform: none;
	color: var(--faint);
}

/*
	One detail per line - the item description out of the Excel, taken apart
	again. Without bullets: it is not a list of points but a list of fields that
	simply have no labels.
*/
.facts {
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: 0.8125rem;
	line-height: 1.6;
}

.facts li + li { margin-top: 0.3rem; }
.facts li { color: var(--ink); overflow-wrap: anywhere; }

/*
	A hint as the last thing in a panel: .hint's margin would be one paragraph
	too many there.
*/
.panel > .hint:last-child { margin-bottom: 0; }

/*
	The tools at the right of the page header: the search field and "Create new"
	side by side, like the tabs and the search in the warehouse. On narrow
	screens one under the other.
*/
.page-tools {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	flex-wrap: wrap;
}

.page-search { margin: 0; }

@media (max-width: 34rem) {
	.page-tools { width: 100%; }
	.page-search { flex: 1 1 auto; }
	.page-search .input--search { width: 100%; max-width: none; }
}

/*
	---- Assigning out of the warehouse --------------------------------------

	Finished hubs that belong to no installation, as ticks under the version's
	row; the oldest ones are preselected. Dashed like the carrier choice in the
	build form: an offer, not a posting.
*/
.assign {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem 0.7rem;
	margin-top: 0.55rem;
	padding: 0.55rem 0.7rem;
	border: 1px dashed var(--hairline-strong);
	border-radius: 9px;
}

.assign-label { flex: none; font-size: 0.75rem; color: var(--muted); }
.assign .chips { margin: 0; }
.assign .btn { margin-left: auto; }

/*
	A reserved number is a button: a click releases it. It looks like the badge
	beside it, only with an x - what is clickable should not dress itself up as
	a form.
*/
.chip-form { display: inline; margin: 0; }

.chip--release {
	cursor: pointer;
	background: transparent;
	font: inherit;
	font-size: 0.7188rem;
	line-height: inherit;
	transition: border-color 0.18s ease, color 0.18s ease;
}

.chip--release:hover, .chip--release:focus-visible { border-color: var(--ink) !important; color: var(--ink) !important; }
.chip-x { margin-left: 0.35rem; opacity: 0.7; }

/*
	The release by hand (fleet/lifecycle.py): the small button next to the badge
	"waiting for the release", and on the delivery-note draft the number itself.
	Green like "finished" - which is what the hub becomes with it.
*/
.chip--lifecycle { border-color: #4fae6a; color: #4fae6a; margin: 0 0.15rem; }


/*
	---- The document row: the delivery note, the shipment ---------------------

	The document with its state at the left, the button that creates it at the
	right - on one row. Before, the field was a select field and the button
	stood in the foot of the section.
*/
.receipt {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.4rem 0.75rem;
}

.receipt-status { color: var(--muted); }

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

.receipt-actions .build-label { font-size: 0.75rem; color: var(--muted); }

@media (max-width: 34rem) {
	.receipt-actions { margin-left: 0; width: 100%; }
}

/*
	---- The person's card beside the choice ------------------------------

	Whoever is picked as the installer stands to the right of the field with
	their company, their address and how to reach them - on the surface that
	otherwise stays empty beside a select box. The company and the login used to
	have rows of their own below it.
*/
.row--person .row-value {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	gap: 0.6rem 1.25rem;
}

.row--person .row-value .combo,
.row--person .row-value > select.input,
/*
	Beside a free-text field stands the contract partner's address - the same
	pattern, only the field is a text area here.
*/
.row--person .row-value > textarea.input { flex: 0 1 22rem; }

.person-card {
	flex: 1 1 14rem;
	min-width: 12rem;
	padding: 0.45rem 0.7rem;
	border: 1px solid var(--hairline);
	border-radius: 8px;
	font-size: 0.75rem;
	line-height: 1.5;
	color: var(--muted);
}

.person-card[hidden] { display: none; }
/* "take over" on the address card: under the rows, not beside them. */
.person-card .adr-take { margin-top: 0.35rem; }

/*
	The addresses offered beside a free-text field (nuno_address.html): one flex
	item holding them, so a second one stacks under the first instead of
	wrapping onto its own full-width line.
*/
.adr-offers {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
	flex: 1 1 14rem;
	min-width: 12rem;
}

.adr-offers .person-card { flex: none; }
.person-name { color: var(--ink); font-weight: 500; }
.person-line { display: block; overflow-wrap: anywhere; }
.person-line a { color: inherit; }

/*
	A locked packing list: the hint and the button that opens it for a back
	order, on one row.
*/
.tbl-lock { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; margin-bottom: 0.4rem; }
/*
	The button at the right, with a border and a surface like any other: you do
	not open a lock with something that looks like a subordinate clause.
*/
.tbl-lock .btn { margin-left: auto; }

/*
	---- Manufacturing: the card of an order ------------------------------

	One large number - how many hubs are there, out of how many - and under it
	the two ways to close the gap: out of the warehouse, or build. Everything
	else is smaller than the number. The first version had three sentences of
	hints per order; manufacturing found them overwhelming.
*/

.job-big {
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: 0 0.5rem;
	margin: 0.5rem 0 0;
}

.job-big-value {
	font-size: 2.25rem;
	font-weight: 300;
	line-height: 1;
	letter-spacing: -0.03em;
	font-variant-numeric: tabular-nums;
	color: var(--ink);
}

.job-big-of {
	font-size: 0.9375rem;
	color: var(--muted);
}

.job-big-note {
	font-size: 0.75rem;
	color: var(--faint);
}

.job--queue .meter { margin: 0.55rem 0 0.4rem; }
.job--queue .chips--serials { margin-top: 0.35rem; }

/*
	One way per row, dashed: an offer, not a posting. The label at the left says
	which one - then the fields need no further sentence.
*/
.way {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem 0.8rem;
	margin: 0.6rem 0 0;
	padding: 0.55rem 0.7rem;
	border: 1px dashed var(--hairline-strong);
	border-radius: 9px;
}

.way-label {
	flex: none;
	min-width: 6.5rem;
	font-size: 0.75rem;
	font-weight: 500;
	color: var(--ink);
}

.way .chips { margin: 0; }
.way .btn { margin-left: auto; }

/*
	Building: the fields with their label above them, the button at the height
	of the fields.
*/
.way--build { align-items: flex-end; }
.way--build .way-label { align-self: center; }
.way--build .produce-field .chips { margin-top: 0.15rem; }
/*
	With a row list the order's row wraps - it is no longer a row but a block.
*/
.way--rows { flex-wrap: wrap; align-items: flex-start; }

.job-foot {
	display: flex;
	justify-content: flex-end;
	margin: 0.45rem 0 0;
}

.job-badge { margin-left: 0.5rem; vertical-align: 0.15em; }

/* The build form in its drawer. */
#now-bauen .build-form { margin-top: 0.2rem; }
#now-bauen .build-field--product { margin-bottom: 0.7rem; }
#now-bauen .panel-foot { margin-top: 0.9rem; }

/*
	The demand was lowered: the one row that may be red - it asks for a
	decision, and nothing here cuts by itself.
*/
.way--alert { border-color: rgba(209, 106, 94, 0.55); }
.way--alert .way-label, .way--alert .way-text { color: #d16a5e; }
.way-text { font-size: 0.75rem; font-variant-numeric: tabular-nums; }

.btn--alert { border-color: rgba(209, 106, 94, 0.55); color: #d16a5e; }
.btn--alert:hover, .btn--alert:focus-visible { border-color: #d16a5e; background: rgba(209, 106, 94, 0.08); }

/*
	"Out of the warehouse" without free hubs: it says so instead of being
	missing. Hubs that belong to another installation stand there dimmed.
*/
.way-empty { font-size: 0.75rem; color: var(--faint); }
.chip--muted { opacity: 0.55; }

/*
	---- Autosave: saved, but not confirmed yet --------------------

	The autosave writes fields and starts nothing. So that the step afterwards
	is not forgotten, a red note stands on the button that carries it onward -
	not which fields, only that there are some - and the card and the list carry
	a badge.
*/
.advance-note {
	font-size: 0.75rem;
	color: #d16a5e;
}

.flag {
	display: inline-block;
	margin-left: 0.4rem;
	padding: 0.05rem 0.4rem;
	border: 1px solid rgba(209, 106, 94, 0.6);
	border-radius: 999px;
	font-size: 0.625rem;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: #d16a5e;
	vertical-align: 0.15em;
	white-space: nowrap;
}

/* The state of the section, to the right of the buttons: "saved 12:03". */
.save-status {
	margin-left: auto;
	font-size: 0.75rem;
	color: var(--faint);
	font-variant-numeric: tabular-nums;
}

.save-status.is-bad { color: #d16a5e; }

.card-flag {
	display: block;
	margin-top: 0.35rem;
	font-size: 0.6875rem;
	color: #d16a5e;
}

/*
	---- The warehouse: ordered, not delivered yet --------------------------

	The number against buying twice. Blue like an open purchase - it is the same
	thing, seen from the other side.
*/
.on-order { color: #4a90d9; white-space: nowrap; }
/* The date is past and the goods are not there. */
.on-order.is-late { color: #d16a5e; }

/*
	A purchase, the delivery section: catching the date up - the field and the
	button on one row, otherwise the button falls below the full date field.
*/
.appointment-form { display: flex; align-items: center; gap: 0.4rem; }
.appointment-form .input { width: auto; }

/* In the purchase form under the row: this item is ordered already. */
.tbl-warn {
	margin: -0.15rem 0 0.15rem;
	font-size: 0.75rem;
	color: #e0912f;
}

/*
	---- The map: the solar parks on the dashboard -----------------------------

	Taken over from the map tool (nuno-tools, apps/solar-portfolio) and set to
	this portal's colours. The drawing is done by Leaflet; the classes with
	`leaflet-` in front belong to the library, everything with `map-` to us.
*/

.panel--card { padding-bottom: 1rem; }

.map {
	height: clamp(320px, 48vh, 520px);
	border: 1px solid var(--hairline);
	border-radius: 10px;
	overflow: hidden;
	/*
		The ground while the tiles are on their way - otherwise the library's
		white flashes up on a dark page.
	*/
	background: #131316;
}

/* Without JavaScript this sentence stays standing in the field. */
.map-hint {
	display: flex;
	height: 100%;
	margin: 0;
	align-items: center;
	justify-content: center;
	padding: 1rem;
	text-align: center;
	font-size: 0.8125rem;
	color: var(--faint);
}

/*
	OpenStreetMap delivers light tiles; the portal is dark. The same filter as
	in the map tool, and it only hits the tile layer: the markers, the popups
	and the controls lie in layers of their own beside it and keep their
	colours.
*/
.map .leaflet-tile-pane {
	filter: invert(0.92) hue-rotate(178deg) brightness(0.61) saturate(0.55) contrast(1.14);
}

/*
	A park is a dot, and its size is the power output - the diameter comes out
	of map_view.size() and stands on the element. The brightness says the same
	thing a second time, so that even the smallest dot can still be placed: one
	colour, three levels, no second meaning.
*/
.map-marker { background: none; border: 0; }

.map-marker-point {
	display: block;
	width: 100%;
	height: 100%;
	border: 2px solid rgba(19, 19, 22, 0.75);
	border-radius: 50%;
	background: #e0912f;
	box-shadow: 0 0 0 1px rgba(224, 145, 47, 0.25), 0 2px 8px rgba(0, 0, 0, 0.45);
	transition: transform 150ms ease;
}

.map-marker--small  .map-marker-point { opacity: 0.78; }
.map-marker--medium .map-marker-point { opacity: 0.89; }
.map-marker--large  .map-marker-point { opacity: 1; }

.map-marker:hover .map-marker-point,
.map-marker:focus-visible .map-marker-point {
	transform: scale(1.14);
	opacity: 1;
}

/* On hover: the name and the power output. The click opens the popup. */
.map .leaflet-tooltip.map-tip {
	padding: 0.4rem 0.55rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 7px;
	background: #212127;
	box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
	color: var(--ink);
	font-size: 0.6875rem;
	line-height: 1.45;
	white-space: nowrap;
}

.map .leaflet-tooltip.map-tip b,
.map .leaflet-tooltip.map-tip small { display: block; }
.map .leaflet-tooltip.map-tip b { font-weight: 500; }
.map .leaflet-tooltip.map-tip small { color: var(--muted); font-size: 0.6875rem; }
/*
	The tip belongs to the surface, not to the border: with the border's colour
	it stayed invisible on the dark tile, and the label floated without a
	connection.
*/
.map .leaflet-tooltip.map-tip::before { border-top-color: #212127; }

.map .leaflet-popup-content-wrapper {
	border: 1px solid var(--hairline-strong);
	border-radius: 9px;
	background: #212127;
	box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
	color: var(--ink);
}

.map .leaflet-popup-content { margin: 0.75rem 0.9rem; font-size: 0.75rem; line-height: 1.5; }
.map .leaflet-popup-tip { border: 0; background: #212127; box-shadow: none; }

.map-popup strong { display: block; font-size: 0.8125rem; font-weight: 500; }
.map-popup span { display: block; margin-top: 0.15rem; color: var(--muted); }
.map-popup b { display: block; margin-top: 0.4rem; font-weight: 400; color: #e0912f; }
.map-popup a { display: block; margin-top: 0.55rem; color: #5bb3e0; text-decoration: none; }
.map-popup a:hover { text-decoration: underline; }

/* The library's controls onto the portal's surfaces. */
.map .leaflet-bar,
.map .leaflet-bar a {
	border-color: var(--hairline-strong);
	background: #212127;
	color: var(--ink);
}

.map .leaflet-bar a:hover { background: #26262d; color: var(--ink); }
.map .leaflet-bar { box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45); }

.map .leaflet-control-attribution {
	border-top-left-radius: 6px;
	background: rgba(29, 29, 33, 0.85);
	color: var(--faint);
	font-size: 0.625rem;
}

.map .leaflet-control-attribution a { color: var(--muted); }

/*
	The legend: the same three levels as map_view.LEVELS, in size and in
	brightness.
*/
.map-legend {
	display: flex;
	flex-wrap: wrap;
	gap: 0.9rem;
	align-items: center;
	font-size: 0.6875rem;
	color: var(--muted);
	white-space: nowrap;
}

.map-legend span { display: inline-flex; gap: 0.4rem; align-items: center; }

.map-point {
	display: inline-block;
	border-radius: 50%;
	background: #e0912f;
}

.map-point--small  { width: 8px;  height: 8px;  opacity: 0.78; }
.map-point--medium { width: 12px; height: 12px; opacity: 0.89; }
.map-point--large  { width: 16px; height: 16px; opacity: 1; }

/*
	What the map cannot show, it says below it - with the way to where the
	coordinates are entered.
*/
.map-without {
	font-size: 0.75rem;
	color: var(--faint);
}

.map-without .inline-link { color: var(--muted); }

/*
	---- The appointment meeting: the wizard ---------------------------------

	One page per installation, the steps of the deck as a bar at the left, one
	explaining sentence per step, the questions, the pictures of the options. It
	is shared on screen during the meeting - hence little text and large
	targets.
*/

.wiz {
	display: grid;
	grid-template-columns: 13rem minmax(0, 1fr);
	gap: 1.25rem;
	align-items: start;
}

.wiz-rail {
	position: sticky;
	top: 1rem;
	display: flex;
	flex-direction: column;
	gap: 0.15rem;
}

.wiz-rail-step {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	padding: 0.45rem 0.6rem;
	border-radius: 6px;
	color: var(--muted);
	text-decoration: none;
	font-size: 0.8125rem;
	transition: background 0.15s ease, color 0.15s ease;
}

.wiz-rail-step:hover, .wiz-rail-step:focus-visible { background: #212127; color: var(--ink); }
.wiz-rail-step.is-active { background: var(--raised); color: var(--ink); box-shadow: inset 2px 0 0 #3fb8c4; }

.wiz-num {
	flex: none;
	width: 1.35rem;
	height: 1.35rem;
	border-radius: 50%;
	border: 1px solid var(--hairline-strong);
	display: inline-grid;
	place-items: center;
	font-size: 0.6875rem;
	color: var(--faint);
	font-variant-numeric: tabular-nums;
}

.wiz-rail-step.is-done .wiz-num { border-color: #4fae6a; color: #4fae6a; }
.wiz-rail-step.is-blocked .wiz-num { border-color: #d16a5e; color: #d16a5e; }
.wiz-label { flex: 1; }
.wiz-state { font-size: 0.6875rem; color: #d16a5e; font-variant-numeric: tabular-nums; }
.wiz-rail-step.is-done .wiz-state::after { content: "\2713"; color: #4fae6a; }

.wiz-body { min-width: 0; }
.wiz-step { margin-bottom: 1rem; }
/*
	Without JavaScript every step stands one under the other - then more air
	separates them.
*/
.wiz:not(.is-js) .wiz-step { margin-bottom: 2rem; }

.wiz-head { margin-bottom: 1rem; }
.wiz-kicker { font-size: 0.6875rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); }
.wiz-title { margin: 0.15rem 0 0; font-size: 1.25rem; font-weight: 500; letter-spacing: -0.01em; }
.wiz-intro { margin: 0.35rem 0 0; color: var(--muted); font-size: 0.875rem; }

/* "more": the rest of the deck, as a tooltip instead of as a paragraph. */
.tip--inline {
	color: var(--faint);
	border-bottom: 1px dotted var(--faint);
	cursor: help;
	font-size: 0.75rem;
	margin-left: 0.25rem;
}

.wiz-nav { display: flex; justify-content: space-between; gap: 0.5rem; margin-top: 1rem; }
.wiz-nav .btn { text-decoration: none; }
.wiz-nav .btn:only-child { margin-left: auto; }
.wiz-open { margin-top: 0.75rem; }

/* Open points: red blocks, orange warns, grey is a hint. */
.gs-open {
	list-style: none;
	margin: 0.5rem 0 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 0.2rem;
	font-size: 0.75rem;
	color: var(--muted);
}

.gs-open li::before {
	content: "";
	display: inline-block;
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var(--faint);
	margin-right: 0.5rem;
	vertical-align: 0.1em;
}

.gs-open li.is-block::before { background: #d16a5e; }
.gs-open li.is-warn::before { background: #e0912f; }
.gs-open a { color: inherit; text-decoration: none; }
.gs-open a:hover, .gs-open a:focus-visible { color: var(--ink); }

.gs-count {
	font-size: 0.75rem;
	padding: 0.3rem 0.6rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 999px;
	color: var(--muted);
	white-space: nowrap;
}

.gs-count.is-block { border-color: #d16a5e; color: #d16a5e; }
.gs-count.is-ok { border-color: #4fae6a; color: #4fae6a; }

/* One box per park with the same questions. */
.parks { display: flex; flex-direction: column; gap: 0.9rem; margin-top: 0.5rem; }

.park {
	border: 1px solid var(--hairline);
	border-radius: 8px;
	padding: 0.6rem 0.9rem 0.4rem;
	margin: 0;
	min-width: 0;
}

.park-head {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	padding: 0 0.35rem;
	font-size: 0.8125rem;
	font-weight: 500;
	color: var(--ink);
	width: calc(100% - 0.7rem);
}

.park-hubs { font-size: 0.6875rem; color: var(--faint); }
.park-copy { margin-left: auto; }
.park .row { grid-template-columns: 11rem 1fr; }

/*
	The options as cards with a picture - the photo row of the deck, clickable.
*/
.opts {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
	gap: 0.6rem;
	margin: 0.4rem 0 0.9rem;
}

.opt {
	position: relative;
	padding: 0.4rem;
	border: 1px solid var(--hairline);
	border-radius: 8px;
	background: var(--raised);
	transition: border-color 0.15s ease;
}

/*
	The label is the choice, the card only its frame: the magnifier sits beside
	it and should not pick the option along with it.
*/
.opt-pick {
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
	cursor: pointer;
}

.opt:hover { border-color: var(--hairline-strong); }
.opt:has(input:checked) { border-color: #3fb8c4; box-shadow: inset 0 0 0 1px #3fb8c4; }
.opt:has(input:focus-visible) { outline: 2px solid #3fb8c4; outline-offset: 2px; }
.opt:has(input:disabled) .opt-pick { cursor: default; }
.opt input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }

/*
	The magnifier at the corner of a photo card - the photo at full size. Always
	visible, not only on hover: on a tablet during the meeting there is no
	hover.
*/
.opt-zoom {
	position: absolute;
	top: 0.65rem;
	right: 0.65rem;
	display: grid;
	place-items: center;
	width: 1.55rem;
	height: 1.55rem;
	border: 1px solid rgba(255, 255, 255, 0.28);
	border-radius: 6px;
	background: rgba(10, 10, 12, 0.62);
	color: #fff;
	text-decoration: none;
}

.opt-zoom svg { width: 0.9rem; height: 0.9rem; }
.opt-zoom:hover { background: rgba(10, 10, 12, 0.88); border-color: #3fb8c4; }

.opt-img {
	display: block;
	aspect-ratio: 6 / 5;
	border-radius: 5px;
	overflow: hidden;
	background: #131316;
}

.opt-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.opt-noimg { display: grid; place-items: center; height: 100%; color: var(--faint); font-size: 1.5rem; }
.opt-label { font-size: 0.75rem; line-height: 1.3; color: var(--muted); padding: 0 0.15rem 0.15rem; }
.opt:has(input:checked) .opt-label { color: var(--ink); }


/* Network coverage: the choice, the button, the source on one row. */
.lte { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
/*
	Wide enough for "Still to be settled" - otherwise the coverage choice breaks
	into two lines and the row becomes twice as tall.
*/
.lte .combo, .lte-select { flex: 0 1 14rem; }
.lte-select.is-warn { border-color: #d16a5e; }
.lte-src { flex: 1 1 10rem; max-width: 16rem; font-size: 0.75rem; }
.lte-note { flex-basis: 100%; font-size: 0.7188rem; color: #d16a5e; }
.is-warn { color: #d16a5e; }

/* The summary: blocks with a header, otherwise only values. */
.sum { display: flex; flex-direction: column; gap: 0.9rem; }
.sum-block { border-top: 1px solid var(--hairline); padding-top: 0.6rem; }
.sum-block:first-child { border-top: 0; padding-top: 0; }

.sum-h {
	margin: 0 0 0.35rem;
	font-size: 0.6875rem;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
	font-weight: 500;
}

.sum-line { font-size: 0.8125rem; color: var(--ink); padding: 0.1rem 0; }
.sum-k { display: inline-block; min-width: 7rem; color: var(--faint); font-size: 0.75rem; }
.sum-grid { display: grid; grid-template-columns: 7rem minmax(0, 1fr); gap: 0.15rem 0.5rem; font-size: 0.8125rem; }
.sum-grid .sum-k { min-width: 0; }
.sum-item { display: inline-block; margin-right: 0.75rem; }
/*
	The summary of the meeting folds: on top the facts at a glance and what is
	open, under it one line per park and per block - every answer is a click
	away instead of a page of grids.
*/
.sum-glance {
	display: flex;
	flex-wrap: wrap;
	gap: 0.35rem 1.25rem;
	font-size: 0.8125rem;
	color: var(--muted);
}
.sum-glance strong { color: var(--ink); font-weight: 500; }
.sum-block--open { border-top: 0; padding-top: 0; }
.sum-fold { border-top: 1px solid var(--hairline); padding-top: 0.55rem; }
.sum-fold > summary {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.2rem 0.75rem;
	cursor: pointer;
	list-style: none;
}
.sum-fold > summary::-webkit-details-marker { display: none; }
.sum-fold > summary::before {
	content: "";
	flex: none;
	align-self: center;
	width: 0.35rem;
	height: 0.35rem;
	border-right: 1.5px solid var(--muted);
	border-bottom: 1.5px solid var(--muted);
	transform: rotate(-45deg);
	transition: transform 0.15s;
}
.sum-fold[open] > summary::before { transform: rotate(45deg); }
.sum-fold-title { font-size: 0.8125rem; color: var(--ink); font-weight: 500; }
.sum-fold-facts { font-size: 0.75rem; color: var(--faint); }
.sum-fold-facts.is-warn { color: #e0912f; }
.sum-fold-count {
	margin-left: auto;
	padding: 0.05rem 0.5rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 999px;
	font-size: 0.6875rem;
	color: var(--muted);
}
.sum-fold-count.is-block { border-color: #d16a5e; color: #d16a5e; }
.sum-fold[open] > summary { margin-bottom: 0.5rem; }
.sum-fold > :not(summary) { margin-left: 1rem; }
.sum-topic + .sum-topic { margin-top: 0.6rem; }
.sum-topic-h {
	margin: 0 0 0.2rem;
	font-size: 0.6875rem;
	font-weight: 500;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--faint);
}
.sum-grid { grid-template-columns: minmax(7rem, 12rem) minmax(0, 1fr); }
.sum-open { color: #e0912f; }

/*
	The section "Appointment" on the installation page: the meeting in short.
*/
.gs { display: flex; flex-direction: column; gap: 0.35rem; }
.gs-line { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; font-size: 0.8125rem; }
.gs-k { color: var(--faint); font-size: 0.75rem; }
.gs-park { display: flex; flex-direction: column; font-size: 0.75rem; padding: 0.3rem 0 0; }
.gs-park-name { color: var(--ink); }
.gs-park-facts { color: var(--muted); }

/*
	The packing list: what the meeting found per group, and the drop shipments.
*/
.pick-hint { margin-left: 0.5rem; font-size: 0.6875rem; color: #3fb8c4; font-weight: 400; text-transform: none; letter-spacing: 0; }
.pick-direct { font-size: 0.6875rem; color: #e0912f; }
.pick-foot { display: flex; justify-content: flex-end; padding-top: 0.5rem; }

/* Participant rows: the person, the role, the note. */
.tbl-row--tn .tbl-role { flex: 0 1 9rem; max-width: 10rem; }
.tbl-row--tn .tbl-note { flex: 1 1 8rem; max-width: 14rem; }

/* Supplier rows of an item: the dealer, its item number. */
.tbl-row--supplier .tbl-part-no { flex: 0 1 10rem; max-width: 12rem; }
.tbl-row--supplier .tbl-part-url { flex: 1 1 12rem; max-width: 20rem; }

@media (max-width: 48rem) {
	.wiz { grid-template-columns: 1fr; }
	.wiz-rail { position: static; flex-direction: row; flex-wrap: wrap; }
	.wiz-label { display: none; }
	.park .row { grid-template-columns: 1fr; }
}

/*
	---- Questions out of the process ----------------------------------------

	The button stands in every section header, and the number beside it only
	when something really is open there. Both quietly: a question is a common
	thing, and an exclamation mark on every step would be invisible after the
	second day.

	One colour for "waiting" (#e0912f, the same as the deadline in
	manufacturing), one for "holding up" (#d16a5e, the same as the cancelled
	phase). A question has no more states than that.
*/

.questions-mark {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	margin-left: auto;
	flex: none;
}

.questions-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 22px;
	height: 22px;
	border-radius: 6px;
	color: var(--faint);
	text-decoration: none;
	transition: color 0.18s ease, background 0.18s ease;
}

.questions-button svg { width: 15px; height: 15px; }

.questions-button:hover, .questions-button:focus-visible {
	color: var(--ink);
	background: rgba(255, 255, 255, 0.06);
}

/*
	The number of open questions - in the section header, in the list, on the
	card.
*/
.questions-number {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 17px;
	height: 17px;
	padding: 0 0.3rem;
	border: 1px solid rgba(224, 145, 47, 0.5);
	border-radius: 999px;
	font-size: 0.625rem;
	font-weight: 500;
	letter-spacing: 0;
	color: #e0912f;
	text-decoration: none;
	white-space: nowrap;
}

.questions-number.is-block { border-color: rgba(209, 106, 94, 0.6); color: #d16a5e; }

/*
	A panel header carrying a button at the right. `.panel-head` is otherwise a
	plain heading and stays one - here it becomes a row.
*/
.panel-head--questions {
	display: flex;
	align-items: center;
	gap: 0.55rem;
}

/* ---- a question in a list ---------------------------------------- */

.question {
	padding: 0.6rem 0;
	border-top: 1px solid var(--hairline);
}

.question:first-of-type { border-top: 0; padding-top: 0; }

.question-subject {
	display: block;
	font-size: 0.875rem;
	color: var(--ink);
	text-decoration: none;
}

.question-subject:hover, .question-subject:focus-visible { text-decoration: underline; }

.question-meta {
	margin: 0.2rem 0 0;
	font-size: 0.6875rem;
	color: var(--faint);
}

.question-status { color: var(--muted); }
.question-block { color: #d16a5e; }
.question-when { margin-left: 0.4rem; }

/*
	Questions that are done stay standing but step back: the list is the work
	list, and the archive should not cover it.
*/
.question.is-done .question-subject { color: var(--muted); }
.question.is-done .question-status { color: #4fae6a; }

.question.is-block { box-shadow: inset 2px 0 0 #d16a5e; padding-left: 0.6rem; }

.question-answer,
.question-text {
	margin: 0.35rem 0 0;
	font-size: 0.8125rem;
	color: var(--muted);
	white-space: pre-wrap;
}

.question-post { padding: 0.55rem 0; border-top: 1px solid var(--hairline); }
.question-post:first-of-type { border-top: 0; padding-top: 0; }

/* ---- the question elsewhere ------------------------------------------ */

/*
	The header: the pill carries the colour, because it stands next to the
	message pill and otherwise the two would be the same.
*/
.pill--question { border-color: rgba(224, 145, 47, 0.45); color: #e0912f; }
.pill--question:hover, .pill--question:focus-visible { border-color: #e0912f; color: #e0912f; }

/* The start page: the same dot as for a message, a different colour. */
.inbox-dot--question { background: #e0912f; }
.inbox-dot--block { background: #d16a5e; }

/* The board of the installation planning. */
.card-flag--question { color: #e0912f; }

/*
	A dialog: a label above the field instead of the two-column row - the dialog
	is narrow, and the question should have the whole width.
*/
.modal-label {
	display: block;
	margin: 0.8rem 0 0.3rem;
	font-size: 0.6875rem;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--faint);
}

.modal-form > .modal-label:first-child { margin-top: 0; }
.modal-form .check { margin-top: 0.8rem; }

/*
	The title of a wizard step and the question button on one row. The kicker
	("3 / 7") stays above it - it belongs to the heading, not to the button.
*/
.wiz-title { display: flex; align-items: center; gap: 0.55rem; }

/*
	A field standing alone in a panel takes the panel's width. `.input`'s 22rem
	are meant for the two-column form row - an answer is running text and wraps
	there after four words.
*/
.input--wide { max-width: none; }
.modal-form .input { max-width: none; }

/* The filter bar under /questions. */
.chip.is-active { border-color: var(--ink); color: var(--ink); }

/*
	---- User administration -------------------------------------------------

	The roles as a list with ticks and one sentence under each: ten entries
	where the sentence is the reason for granting one. The sentence stands
	indented under the tick, at the level of the hints.
*/

.roles { display: flex; flex-direction: column; gap: 0.6rem; }

.role .check { color: var(--ink); }

.role-hint {
	display: block;
	margin: 0.1rem 0 0 1.65rem;
	font-size: 0.7188rem;
	line-height: 1.45;
	color: var(--faint);
}

/*
	The invitation link: a field to copy out of, the button beside it. The field
	is wide because the link is long, and one line high because you want to
	select all of it.
*/
.invitation { display: flex; gap: 0.5rem; align-items: center; }
.invitation .input { flex: 1 1 auto; min-width: 0; }
.invitation .btn { flex: none; }

@media (max-width: 34rem) {
	.invitation { flex-wrap: wrap; }
}

/*
	---- Acceptance in the portal -------------------------------------------
*/

/*
	The traffic light of the deployment check behind the link to the report: a
	dot, not a word - the word stands in the tooltip. The dot of the phase
	(.dot) sits in front of its text, this one behind it.
*/
.dot--traffic_light { width: 8px; height: 8px; margin: 0 0 0 0.4rem; vertical-align: 0; }

/*
	What is not right about the report but is no fault of the page: a different
	person, a deviating number, test data instead of the bucket.
*/
.warn { color: #e0912f; }

/* The red rows of the deployment check under the traffic light. */
.abn-listing { margin: 0.35rem 0 0; padding-left: 1.1rem; color: var(--muted); }
.abn-listing li { margin-bottom: 0.2rem; }

/* The photos of a section: square tiles, each one opens the picture. */
.photo-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
	gap: 0.4rem;
	max-width: 36rem;
}

.photo-thumb {
	display: block;
	aspect-ratio: 1;
	border: 1px solid var(--hairline);
	border-radius: 6px;
	overflow: hidden;
	background: rgba(255, 255, 255, 0.03);
}

.photo-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-thumb:hover, .photo-thumb:focus-visible { border-color: var(--hairline-strong); }

/* The camera picture from the day of the acceptance. */
.capture-image {
	display: block;
	width: 100%;
	max-width: 36rem;
	border: 1px solid var(--hairline);
	border-radius: 6px;
}

/* Its time below it: a panel-note pulls itself up under a heading, here that
   would put it into the picture's lower edge. */
.panel-note.capture-caption { margin: 0.5rem 0 0; }

/* The call with the installer: each question above its answer. */
.call-field { margin-bottom: 0.9rem; }
.call-field .input { max-width: 40rem; }
.call-question { font-size: 0.8125rem; }
.call-foot {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem 1rem;
}
.call-saved { font-size: 0.7188rem; color: var(--faint); }

/*
	---- OKRs: a goal and its curve ------------------------------------------

	The curve is drawn on the server as SVG; here only its ink. Grid and axes
	recessive, the two lines 2px, the pace line dashed so that it never reads
	as a third count.
*/
.okr + .okr { margin-top: 1.5rem; }
.okr-chart { margin: 0; }
/* On /okr the curve is the way into the goal's own page. */
.okr-link { color: inherit; text-decoration: none; }
.okr-link:hover { text-decoration: underline; }
.okr-chart-link { display: block; color: inherit; text-decoration: none; }
.okr-actions { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; justify-content: flex-end; }
.okr .stats { margin-bottom: 1rem; }
/*
	The SVG scales its text with its width - on a phone 11px would shrink to
	4px. Below 40rem the curve keeps its size and scrolls inside the card.
*/
.okr-plot { overflow-x: auto; }
.okr-plot svg { display: block; width: 100%; min-width: 40rem; height: auto; }
.okr-grid line { stroke: var(--hairline); stroke-width: 1; }
.okr-axis { font-size: 11px; fill: var(--faint); font-variant-numeric: tabular-nums; }
.okr-axis--ink { fill: var(--muted); }
.okr-target { stroke: var(--hairline-strong); stroke-width: 1; }
.okr-pace { stroke: var(--muted); stroke-width: 1.5; stroke-dasharray: 5 5; }
.okr-today { stroke: var(--hairline-strong); stroke-width: 1; stroke-dasharray: 2 3; }
.okr-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.okr-end { stroke: var(--raised); stroke-width: 2; }
/* The strips carry the tooltip; the one under the pointer shows as a column. */
.okr-hover rect { fill: transparent; }
.okr-hover rect:hover { fill: rgba(255, 255, 255, 0.06); }
.okr-legend {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem 1.1rem;
	margin-top: 0.6rem;
	font-size: 0.7188rem;
	color: var(--muted);
}
.okr-legend i {
	display: inline-block;
	width: 14px;
	height: 2px;
	margin-right: 0.4rem;
	vertical-align: middle;
	border-radius: 1px;
}
.okr-legend i.okr-legend-pace {
	background: none;
	border-top: 1.5px dashed var(--muted);
	height: 0;
}
.okr-table { margin-top: 1rem; }
.okr-table summary {
	cursor: pointer;
	font-size: 0.8125rem;
	color: var(--muted);
	margin-bottom: 0.6rem;
}
/*
	What the goal still needs bought: a table per supplier, the supplier as the
	head row of its block. Rows the stock already covers step back - they are
	there to show why nothing is missing, not to be read first.
*/
.okr-buy { margin-top: 1.4rem; padding-top: 1.2rem; border-top: 1px solid var(--hairline); }
.okr-buy-head { margin: 0; font-size: 0.9375rem; font-weight: 500; color: var(--ink); }
.okr-buy .okr-actions form { margin: 0; }
.okr-buy-table tbody tr { cursor: default; }
.okr-buy-table tr.okr-buy-supplier th {
	padding-top: 1rem;
	text-transform: none;
	letter-spacing: 0;
	font-size: 0.8125rem;
	color: var(--ink);
}
.okr-buy-table tr.okr-buy-supplier th .cell-note { display: inline; margin-left: 0.5rem; }
.okr-buy-table tr.okr-buy-covered td { color: var(--faint); }
/* The purchase list on the procurement page: a block per supplier. */
.buy-group + .buy-group { margin-top: 1.4rem; padding-top: 1.2rem; border-top: 1px solid var(--hairline); }
.buy-group .catalog-name, .buy-group .catalog-hint { display: block; }
.buy-assign { display: flex; gap: 0.4rem; align-items: center; margin: 0; }
.buy-table tbody tr { cursor: default; }

/*
	---- Production's timeline: how many hubs can be built when -------------

	The OKR curve's ink, with two additions: the demand line in --ink instead
	of --muted (next to the blue a grey reads as the same line), and a band of
	its own for what has no date, set off by a faint ground. The buildable in
	that band is dashed: a count, not a date.
*/
.timeline-chart { margin: 0.4rem 0 1.2rem; }
.timeline-needed { stroke: var(--ink); }
.okr-legend i.timeline-legend-needed { border-top-color: var(--ink); }
.timeline-open { stroke-dasharray: 4 4; }
/* The hub's line leads; its assemblies run thinner beside it. */
.timeline-hub { stroke-width: 2.5; }
.timeline-assembly { stroke-width: 1.5; }
.timeline-legend-note { flex-basis: 100%; color: var(--faint); }
.timeline-band { fill: rgba(255, 255, 255, 0.03); stroke: none; }
.timeline-verdict { margin: 0 0 0.8rem; font-size: 0.875rem; color: var(--muted); }
.timeline-missing { display: flex; flex-direction: column; gap: 0.4rem; margin-bottom: 1rem; }
.timeline-missing .panel-note { margin: 0; }
/* One delivery, one limiting part per line - a cell lists several. */
.timeline-arrival, .timeline-limit { display: block; }
.timeline-arrival + .timeline-arrival, .timeline-limit + .timeline-limit { margin-top: 0.45rem; }
.timeline .table-wrap + .table-wrap,
.timeline .timeline-foot + .table-wrap { margin-top: 1rem; }
/* The assemblies under the hub: a heading of the panel's own kind. */
.timeline-sub { margin: 1.6rem 0 0.7rem; }
.timeline-foot { margin: 0.9rem 0 0; }

/*
	---- The installation list: what is still to be done --------------------

	A few words per item, the blocking ones in red, at most three - a column to
	run the eye down, not a page of open points.
*/
.todo { display: flex; flex-wrap: wrap; gap: 0.25rem 0.4rem; }
.todo-item {
	padding: 0.05rem 0.45rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 999px;
	font-size: 0.6875rem;
	color: var(--muted);
	/* A long point wraps inside its pill instead of running out of the card. */
	max-width: 100%;
	overflow-wrap: anywhere;
}
.todo-item.is-block { border-color: #d16a5e; color: #d16a5e; }
.todo-more { font-size: 0.6875rem; color: var(--faint); align-self: center; }
.todo-cell { max-width: 18rem; }
/*
	In the list a row stays one line high: one point, cut off where it is too
	long, the rest as a number - all of them in the tooltip. Pills wrapping onto
	a second and third line made single rows twice as high, and when the column
	had scrolled out of view, for no visible reason.
*/
.todo-cell .todo { flex-wrap: nowrap; align-items: center; }
.todo-cell .todo-item {
	min-width: 0;
	/* No higher than a line of text - otherwise a row with a point stands
	   two pixels above one without. */
	line-height: 1.1;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.todo-cell .todo-more { flex-shrink: 0; white-space: nowrap; }
/* A long park list or company name is cut off, not the table made wider. */
.table td.cell-clip {
	max-width: 15rem;
	overflow: hidden;
	text-overflow: ellipsis;
}
/*
	The badges beside a value - "changed", the open questions - sit on the
	middle of the line and do not count towards its height: a badge a pixel
	higher than the text stretched its row, and the rows of a list stood at
	three different heights.
*/
.table td .flag,
.table td .questions-number {
	vertical-align: middle;
	line-height: 1.2;
	margin-block: -3px;
}
.card-note { display: block; font-size: 0.6875rem; color: var(--faint); }
.card-todo { display: block; margin-top: 0.35rem; }

/*
	---- The tools under the processes -------------------------------------

	A second row of tiles on /processes, under a heading of its own: the
	tools are small procedures that belong to no process page. The heading is
	quiet - a label for the row, not a title for the page.
*/
.launcher-title {
	margin: 2.25rem 0 -0.25rem;
	font-size: 0.6875rem;
	font-weight: 500;
	letter-spacing: 0.3em;
	text-indent: 0.3em;
	text-transform: uppercase;
	color: var(--faint);
}

/*
	---- Link router (/tools/link-router) -----------------------------------

	Two fields side by side where there is room, the camera above them when
	the browser can read codes, the answer under them. The fields take the
	whole width: a code is long, and the one at the bench reads it back.
*/
.link-fields {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
	gap: 0.9rem 1.25rem;
	margin-top: 1rem;
}
.link-fields .input { max-width: none; font-variant-numeric: tabular-nums; }

.scan { margin-bottom: 0.5rem; }
.scan-video {
	display: block;
	width: 100%;
	max-width: 28rem;
	aspect-ratio: 4 / 3;
	object-fit: cover;
	border-radius: 10px;
	border: 1px solid var(--hairline);
	background: #000;
	margin-bottom: 0.6rem;
}
.scan-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.9rem; }
.scan-status { font-size: 0.75rem; color: var(--muted); }

.link-confirm { margin-top: 1rem; }
.link-confirm-text { margin: 0 0 0.6rem; color: var(--ink); }

.link-result {
	margin: 1rem 0 0;
	padding: 0.55rem 0.8rem;
	border-left: 3px solid var(--hairline-strong);
	border-radius: 6px;
	background: var(--bg);
	font-size: 0.8125rem;
	color: var(--muted);
}
.link-result--ok { border-left-color: #4fae6a; color: var(--ink); }
.link-result--warn { border-left-color: #e0912f; color: var(--ink); }
.link-result--err { border-left-color: #d16a5e; color: var(--ink); }

/*
	On a phone the camera is the tool: the picture takes the width, upright
	like the phone, and the fields stay large enough to read a code back.
*/
@media (max-width: 40rem) {
	.scan-video { max-width: none; aspect-ratio: 3 / 4; max-height: 60vh; }
	.scan-bar { justify-content: space-between; }
	.link-fields .input { font-size: 1rem; }
}

/*
	---- The logbook of a sensor hub (hub_log.py) ----------------------------
	The shape of the desk's activity feed: a thin line down the left, a dot
	per step on it, newest on top. One sentence per step - the person in
	bold, what they did, the document, how long ago in faint type.

	Line and dot hang on the same line item and on the same pixel: the line
	is 1px wide at 4px, the dot 7px wide at 1px - both centred on 4.5px.
	Placed against the list and the item separately, they drifted apart.
	The first item's line starts at its dot, the last one's ends there.
*/
.logbook {
	list-style: none;
	margin: 0.3rem 0 0;
	padding: 0;
}

.logbook-entry {
	position: relative;
	padding: 0.55rem 0 0.55rem 1.5rem;
	font-size: 0.8125rem;
	line-height: 1.55;
	color: var(--muted);
	overflow-wrap: anywhere;
}
.logbook-entry::before {
	content: "";
	position: absolute;
	left: 4px;
	top: 0;
	bottom: 0;
	width: 1px;
	background: var(--hairline-strong);
}
.logbook-entry:first-child::before { top: 1.2rem; }
.logbook-entry:last-child::before { bottom: calc(100% - 1.2rem); }
.logbook-entry:only-child::before { display: none; }
.logbook-entry::after {
	content: "";
	position: absolute;
	left: 1px;
	top: calc(1.2rem - 3.5px);
	width: 7px;
	height: 7px;
	box-sizing: border-box;
	border-radius: 50%;
	background: var(--ink);
	box-shadow: 0 0 0 3px var(--raised);
}

.logbook-person { color: var(--ink); font-weight: 500; }
.logbook-detail { color: var(--ink); }
.logbook-when { color: var(--faint); white-space: nowrap; }

/*
	---- The build photos of a sensor hub (build_photos.section) -------------
	Two pictures side by side, upright as the phone takes them and cut to
	the same size; a click opens the full one. On a narrow phone they stand
	one under the other.
*/
.build-photos {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
	gap: 0.75rem;
	margin-top: 0.3rem;
}
.build-photo { margin: 0; }
.build-photo img, .build-photo-empty {
	display: block;
	width: 100%;
	aspect-ratio: 3 / 4;
	object-fit: cover;
	border-radius: 6px;
	border: 1px solid var(--hairline);
}
.build-photo-empty {
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--faint);
	font-size: 0.8125rem;
	border-style: dashed;
}
.build-photo figcaption {
	margin-top: 0.35rem;
	font-size: 0.8125rem;
	color: var(--muted);
}

/*
	Spending (/spending): a bar per month, gross. The bars thin with rounded
	ends, a 2px gap where the hatched "basis unknown" sits on top. The whole
	column is the link - the strip behind the bar lights up under the pointer
	and marks the month opened below.
*/
.spend-chart { margin: 0.5rem 0 0; }
.spend-bar { cursor: pointer; }
.spend-slot { fill: transparent; }
.spend-bar:hover .spend-slot { fill: rgba(255, 255, 255, 0.04); }
.spend-bar--selected .spend-slot { fill: rgba(255, 255, 255, 0.06); }
.spend-unknown { fill: url(#spend-hatch); stroke: var(--faint); stroke-width: 1; }
.spend-hatch-line { stroke: var(--faint); stroke-width: 2; }
.spend-chart .okr-legend i { width: 10px; height: 10px; border-radius: 2px; }
.spend-chart .okr-legend i.spend-legend-unknown {
	background: repeating-linear-gradient(45deg, var(--faint) 0 2px, transparent 2px 5px);
	border: 1px solid var(--faint);
}
/* An amount in euros needs more room than a quantity. */
.qty.qty--money { width: 8.5rem; }
/* The share of a dealer as a short bar beside its percentage. */
.spend-share {
	display: inline-block;
	width: 4rem;
	height: 6px;
	margin-right: 0.5rem;
	vertical-align: middle;
	border-radius: 3px;
	background: var(--hairline);
	overflow: hidden;
}
.spend-share i { display: block; height: 100%; border-radius: 3px; }

/* ---- the photo of an item (Item.image) on its stock page ---------------- */

.item-head { display: flex; align-items: center; gap: 1rem; min-width: 0; }
.item-photo {
	flex: none;
	display: block;
	width: 6.5rem;
	height: 6.5rem;
	padding: 0.35rem;
	border-radius: 10px;
	background: var(--raised);
	border: 1px solid var(--hairline);
}
.item-photo img { display: block; width: 100%; height: 100%; object-fit: contain; }
.item-photo:hover, .item-photo:focus-visible { border-color: var(--hairline-strong); }
@media (max-width: 34rem) {
	.item-photo { width: 4.5rem; height: 4.5rem; }
}
