/*
 * The one stylesheet. Both shells and every page load this file and only this
 * file, and no page may override a rule from a <style> block of its own: a
 * block inside a view comes later in the document and silently beats the
 * sheet on equal specificity, so the first narrow fix written that way is the
 * end of the design system. A page that needs something new adds it here.
 *
 * The order below is the order of the system: the ground, the shell, the
 * page, the record, the controls, the messages, the drawer, the lists, and
 * the guards that stop anything panning sideways.
 *
 * THREE WIDTHS, AND EVERY MEDIA QUERY IN THIS FILE IS ONE OF THEM.
 *
 * 900px, where the navigation rail collapses behind the menu button and any
 * layout that stands two wide things side by side folds to one column.
 * 760px, where two columns of one thing become one, and where a dense table
 * sheds the columns that do not decide anything.
 * 640px, the phone: the page gutter narrows, the top bar drops what only
 * repeats what the reader already knows, and a label and its value stop
 * sharing a line.
 *
 * There were two names for the middle question until this was written, 700
 * in five places and 760 in one, which is exactly the drift a design system
 * exists to stop: two numbers a hundredth of a screen apart, neither of them
 * wrong, and a reader with no way to tell which one a new rule should take.
 * The five moved to 760 rather than the one to 700, because the one at 760
 * was the considered number: it is the width at which the outstanding-work
 * columns were measured to stop reading as two named groups.
 *
 * A fourth width is a decision, not a fix. Add it here with its reason or do
 * not add it.
 */

/* ---------- Reset and ground ---------- */

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

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

body {
	margin: 0;
	background: var(--bg);
	font: var(--fs)/var(--lh) var(--font);
	color: var(--ink);
	-webkit-font-smoothing: antialiased;
}

h1,
h2,
h3,
p,
ul,
ol,
dl,
dd,
figure {
	margin: 0;
}

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

a {
	color: var(--accent);
	text-decoration: underline;
	text-underline-offset: 2px;
}

a:hover {
	color: var(--accent-ink);
}

/* One focus treatment everywhere, in the accent, always visible. A compliance
   screen is worked from the keyboard and a lost focus ring is a lost reader. */
:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
	border-radius: 2px;
}

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

code,
kbd,
samp {
	font-family: var(--mono);
	font-size: 0.92em;
}

/* Identifiers, reference numbers and any string compared character by
   character. Letter spacing separates a zero from an O at reading size. */
.mono {
	font-family: var(--mono);
	letter-spacing: 0.02em;
}

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

/*
 * The hidden attribute has to win over any display this stylesheet gives an
 * element. The browser's own rule for it is [hidden] { display: none }, which
 * an author rule of the same specificity beats simply by being an author
 * rule: a .kv list or a .json-pair with display: grid stayed on screen after
 * a script had set el.hidden = true, and the identity editor opened with the
 * list it replaces still sitting above it. Every panel on the record page is
 * folded away with the attribute, so this rule is what makes any of them
 * fold at all.
 */
[hidden] {
	display: none !important;
}

/*
 * SAID TO A SCREEN READER AND NOT DRAWN. There was no rule for this class and
 * one caption in the tree was already using it, so the accessible name of the
 * Hub proposal table was being PRINTED under the table, in the caption's own
 * ink, on a record page. A class that does nothing is worse than no class,
 * because the person who wrote it believed it worked.
 *
 * clip-path rather than display: none or visibility: hidden, both of which take
 * the text away from a screen reader as well as from the screen, which is the
 * exact opposite of what this is for. The same six lines were written inline
 * for the sign-out label in the top bar; that rule now uses this one.
 */
.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* ---------- The page container ---------- */

.wrap {
	max-width: var(--page-max);
	margin: 0 auto;
	padding: 0 calc(var(--space) * 3);
}

/* ---------- The top bar ---------- */

.topbar {
	display: flex;
	align-items: center;
	gap: calc(var(--space) * 2);
	height: 52px;
	padding: 0 calc(var(--space) * 2);
	background: var(--surface);
	border-bottom: 1px solid var(--line);
}

.topbar-brand {
	display: flex;
	align-items: baseline;
	gap: var(--space);
	font-family: var(--font-display);
	font-size: 17px;
	font-weight: 600;
	letter-spacing: 0.01em;
	color: var(--ink);
	text-decoration: none;
	/* The bar is one line high. A brand that wraps takes the bar with it. */
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

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

/* The surface tag. Both shells live at the same host and look alike, so the
   one thing that must never be in doubt is which of them you are reading. It
   is set in mono because it names a system, not a person. */
.surface-tag {
	font-family: var(--mono);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--accent);
}

.topbar-right {
	display: flex;
	align-items: center;
	gap: calc(var(--space) * 2);
	margin-left: auto;
	color: var(--ink-2);
}

.topbar-user {
	font-size: 13px;
}

.topbar-menu {
	display: none;
	align-items: center;
	background: none;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	padding: 4px 8px;
	color: var(--ink-2);
	cursor: pointer;
}

/* ---------- The side navigation ---------- */

.shell {
	display: flex;
	align-items: flex-start;
	gap: 0;
}

.nav {
	flex: 0 0 240px;
	width: 240px;
	align-self: stretch;
	min-height: calc(100vh - 52px);
	padding: calc(var(--space) * 2) var(--space);
	background: var(--surface-2);
	border-right: 1px solid var(--line);
}

.nav a {
	display: flex;
	align-items: center;
	gap: var(--space);
	padding: 6px 10px;
	margin-bottom: 2px;
	border-left: 2px solid transparent;
	border-radius: 0 var(--radius) var(--radius) 0;
	color: var(--ink-2);
	text-decoration: none;
	font-size: 13px;
}

.nav a:hover {
	background: var(--surface);
	color: var(--ink);
}

.nav a .icon {
	flex: none;
	color: var(--ink-3);
}

/* The active item is the one place in the shell the accent appears, so the
   reader can find where they are without reading a word. */
.nav a[aria-current="page"] {
	background: var(--surface);
	border-left-color: var(--accent);
	color: var(--ink);
	font-weight: 600;
}

.nav a[aria-current="page"] .icon {
	color: var(--accent);
}

.shell-main {
	flex: 1 1 auto;
	padding: calc(var(--space) * 3) 0 calc(var(--space) * 6);
}

@media (max-width: 900px) {
	.nav {
		display: none;
		position: absolute;
		z-index: 40;
		left: 0;
		top: 52px;
		width: 240px;
		min-height: 0;
		box-shadow: 0 12px 32px -12px rgba(26, 29, 33, 0.35);
	}

	.nav.open {
		display: block;
	}

	.topbar-menu {
		display: inline-flex;
	}
}

/* On a phone the bar keeps the two things that identify the surface and the
   one control that leaves it. The name of the person signed in is on their
   own profile page and does not need to be in the furniture. */
@media (max-width: 640px) {
	.topbar-user {
		display: none;
	}

	/* The same six lines as .visually-hidden above, and it says so rather than
	   repeating them: the label is still read out, it is simply not drawn. */
	.topbar .btn-label {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	/* The bar keeps its height and gives up its room. Two steps of gap and two
	   of padding around a menu button, a name and a sign-out is a third of a
	   360px screen spent on furniture. */
	.topbar {
		gap: var(--space);
		padding: 0 var(--space);
	}

	/* The page gutter drops from three steps to two. Three steps either side and
	   the card's own two inside them take a fifth of a 360px screen before a
	   word is drawn, and every one of those pixels is a name that has to wrap. */
	.wrap {
		padding: 0 calc(var(--space) * 2);
	}

	.shell-main {
		padding-top: calc(var(--space) * 2);
	}
}

/* ---------- The page title ---------- */

/* One voice above the fold: a single h1 and at most one line under it. Nothing
   else is allowed to speak at the top of a page, which is why there is no rule
   here for a second heading or a stacked banner. */
.page-title {
	margin-bottom: calc(var(--space) * 3);
}

.page-title h1 {
	font-family: var(--font-display);
	font-size: 22px;
	font-weight: 600;
	line-height: 1.25;
	letter-spacing: -0.01em;
}

.page-title p {
	margin-top: 4px;
	max-width: 68ch;
	color: var(--ink-2);
}

.page-actions {
	display: flex;
	align-items: center;
	/* Three buttons above the fold is a row on a laptop and three lines on a
	   phone. Without this the third one is simply off the side of the page. */
	flex-wrap: wrap;
	gap: var(--space);
	margin-top: calc(var(--space) * 2);
}

/* ---------- The card ---------- */

.card {
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	padding: calc(var(--space) * 2);
	margin-bottom: calc(var(--space) * 3);
}

.card > h2 {
	font-size: 15px;
	font-weight: 600;
	padding-bottom: var(--space);
	margin-bottom: calc(var(--space) * 2);
	border-bottom: 1px solid var(--line);
}

.card > h2 + p {
	margin-top: calc(var(--space) * -1);
	margin-bottom: calc(var(--space) * 2);
	color: var(--ink-2);
}

/*
 * A CARD WHOSE WHOLE CONTENT IS ONE FULL WIDTH BAND THAT SHOULD REACH ITS RULE,
 * and nothing else. That is the only thing this class is for and it is worth
 * saying plainly, because five views reached for it and not one of them was
 * that: a card holding a heading, a sentence or a field has no full width band
 * in it, and taking the padding off simply stands the type against the border.
 *
 * The tell is a heading flush to the rule with rows indented sixteen pixels
 * beneath it, which is two things belonging to one card starting at two
 * different left edges. Where a card holds prose AND a band that has to reach
 * the rule, the answer is .card-group or an ordinary card with the band pulled
 * out to the edge by a negative margin of the card's own padding, which is what
 * .identifier-row does. It is not this class.
 */
.card-tight {
	padding: 0;
}

/*
 * A GROUP OF FIELDS INSIDE A CARD, drawn as a card of its own on the page's own
 * ground.
 *
 * .card .card-danger already draws exactly this shape with a red rule down its
 * left edge, and the places that wanted the shape WITHOUT the warning reached
 * for .card-tight instead and got the fault above. Recessed onto --bg rather
 * than white on white, because a white block on a white card is a block only
 * its border says is there.
 */
.card .card-group {
	background: var(--bg);
}

/*
 * A HEADING BELOW A CARD'S OWN, which is what a group inside a card carries. It
 * takes the card heading's size and weight and no rule of its own: the rule
 * under .card > h2 separates a card's title from its body, and a second one
 * inside that body is the three-strips fault again.
 */
.card > h3 {
	font-size: 15px;
	font-weight: 600;
	margin-bottom: var(--space);
}

.card > h3 + p {
	margin-top: calc(var(--space) * -0.5);
	margin-bottom: calc(var(--space) * 2);
	max-width: 68ch;
	color: var(--ink-2);
}

/*
 * A CARD WHOSE WHOLE CONTENT IS A TITLE, A SENTENCE AND ONE WAY OUT.
 *
 * The two policy cards on the settings page were drawn with .card-tight, which
 * takes the padding off so a full width table can reach the card's own rule.
 * These hold no table. What it did instead was put the heading hard against the
 * left border with its own rule running the full width underneath, and then the
 * sentence and the button below that with nothing round them: three stacked
 * strips where the reader was being shown one thing. A signpost is one object,
 * so the heading gives up its rule, the sentence sits directly under it, and
 * the card's own padding is the only edge in it.
 *
 * Two classes on the heading so it beats .card > h2 whichever order the two
 * rules end up in.
 */
.card.card-signpost > h2 {
	padding-bottom: 0;
	margin-bottom: 4px;
	border-bottom: 0;
}

.card.card-signpost > h2 + p {
	margin-top: 0;
	margin-bottom: 0;
	max-width: 68ch;
}

.card-signpost .btn-row {
	margin-top: calc(var(--space) * 2);
}

/* ---------- Tables ---------- */

/* The wrapper scrolls, never the page. A table that pans the whole document
   sideways loses the navigation and the reader's place at the same time. */
.table-wrap {
	overflow-x: auto;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--surface);
}

.table {
	width: 100%;
	min-width: 640px;
	table-layout: fixed;
	border-collapse: collapse;
	/* Figures line up down a column, so two reference numbers of the same
	   length are the same width and a mismatch is visible without reading. */
	font-variant-numeric: tabular-nums;
}

.table th,
.table td {
	padding: 8px 12px;
	text-align: left;
	vertical-align: top;
	border-bottom: 1px solid var(--line);
	overflow-wrap: anywhere;
}

.table th {
	font-size: 12px;
	font-weight: 600;
	color: var(--ink-3);
	background: var(--surface-2);
	white-space: nowrap;
}

.table tbody tr:last-child td {
	border-bottom: 0;
}

.table tbody tr:hover td {
	background: var(--bg);
}

/* Selection is the accent wash, the same family as the primary action. */
.table tbody tr[aria-selected="true"] td {
	background: var(--info-bg);
}

.table td.num {
	text-align: right;
	font-family: var(--mono);
}

/*
 * A RECORD WHOSE PROSE IS LONGER THAN A CELL, SPLIT ACROSS TWO ROWS.
 *
 * The scan line keeps one row, so the table can still be read down its columns,
 * and the prose takes a row of its own beneath it at the full width of the
 * table. Both in one cell is what the reminders page did first, and a reminder
 * carrying a real instruction drew a tall narrow column of wrapped text with
 * four empty columns standing beside it for half a screen.
 *
 * The two rows are one record, so the rule between them is dropped and the
 * hover tint is dropped with it: half a record lighting up reads as a fault.
 * The prose is held to a readable measure rather than run to the full 1200px,
 * because a line of ninety characters is the point past which an eye starts
 * losing its place on the way back to the left margin. Full width is what the
 * row has available, not what the sentence should use.
 */
.table tbody tr.row-lead td {
	border-bottom: 0;
	padding-bottom: 2px;
}

.table tbody tr.row-note td {
	padding-top: 0;
	color: var(--ink-2);
}

.table tbody tr.row-lead:hover td,
.table tbody tr.row-note:hover td {
	background: transparent;
}

.table tbody tr.row-note .note-body {
	max-width: 90ch;
	white-space: pre-line;
}

.table caption {
	caption-side: bottom;
	padding: var(--space) 12px;
	color: var(--ink-3);
	font-size: 12px;
	text-align: left;
}

/*
 * A DENSE TABLE ON A NARROW SCREEN SHEDS COLUMNS RATHER THAN PANNING SIDEWAYS.
 *
 * The wrapper above scrolls, which is honest and poor. A list of clients that
 * can only be read by dragging it left and right is a list nobody reads on a
 * phone, and most of what the reader drags past does not decide anything: it
 * describes a record they have already chosen. So a table carrying
 * .table-stack drops the cells marked .col-minor below 760px and keeps the two
 * or three that decide which record is opened next. WHICH ONES THOSE ARE IS A
 * JUDGMENT PER TABLE and is written in a comment above each table, because it
 * is different on every list and is not something a stylesheet can decide.
 *
 * IT IS STILL A TABLE, and that is the whole reason it is done this way rather
 * than the usual way. The usual way is display: block on the rows and cells
 * with the column name pulled back in through content: attr(data-label), and
 * it is wrong here twice over. A display change on a table's parts strips the
 * table role in every engine, so the header-to-cell association a screen
 * reader uses to say "Risk, high" instead of "high" is gone, on a product that
 * turns focus rings back on by hand and refuses to carry a severity in hue
 * alone. And a label living in a stylesheet is a label that cannot go through
 * Lang::t, so a Greek accountant would read Greek column heads at a desk and
 * English ones on a phone, which is the kind of fault nobody finds for months.
 * display: none on a cell takes the cell out and leaves the rest of the table
 * a table, with every remaining cell still owned by the head above it.
 *
 * THE DROPPED COLUMNS ARE REACHABLE AND NOT GONE. Every row that sheds
 * anything carries a link into the record that holds all of it, which is the
 * tap the reader was going to make anyway. The class is on BOTH the th and the
 * td of a column, and the walk asserts that the two counts agree: a class on
 * one and not the other leaves a head standing over a column that is no longer
 * there, and every column after it reading under the wrong name.
 *
 * .table.table-stack rather than .table-stack, because .table-wide sets a
 * floor of 900px further down this file and would otherwise win on source
 * order alone.
 */
@media (max-width: 760px) {
	.table.table-stack {
		min-width: 0;
		table-layout: auto;
	}

	.table-stack .col-minor {
		display: none;
	}

	/* The room the dropped columns give back goes to the cell the reader came
	   for. It carries the record's own name, so it is set at the weight a name
	   is set at everywhere else rather than left at the size of the four figures
	   that used to stand beside it. This is what makes the row read as a record
	   rather than as the stump of a table. */
	.table-stack .cell-lead {
		font-size: 15px;
	}

	.table-stack .cell-lead .row-note,
	.table-stack .cell-lead .muted {
		font-size: 13px;
	}

	/* Two fixed columns that are sized for a desk and are most of a phone. */
	.table.table-stack th.col-actions {
		width: auto;
	}

	.table.table-stack th.tick,
	.table.table-stack td.tick {
		width: auto;
	}

	/* A select in a cell is sized to its own longest option, which is right
	   beside four other columns and wrong when it is one of three: the cell is
	   now most of the screen and the control's own content is what decides how
	   wide the column gets. Told to fill the cell, it stops deciding. */
	.table-stack td select {
		width: 100%;
	}

	/*
	 * The breakdown on a screening result reaches its narrow behaviour here
	 * rather than through the class above, because tests/walk.php asserts its
	 * exact class attribute and a class added to it would fail the walk. It
	 * drops nothing in any case: a breakdown is the arithmetic behind a match
	 * score and every column of it is part of the working.
	 */
	.table.breakdown {
		min-width: 0;
		table-layout: auto;
	}
}

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

/* A button is the same height as a text box, a select and a date field, because
   a filter row puts all four on one line and the row only reads as one row if
   they share a baseline. --control-h is where that height is decided. */
.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	min-height: var(--control-h);
	padding: 5px 15px;
	border: 1px solid var(--line);
	/*
	 * The control corner and not the paper corner. A button standing beside a
	 * text box in a filter row is the same kind of object as the text box, and
	 * two different corners on one row is the reader being told they are two
	 * kinds of thing when they are not.
	 */
	border-radius: var(--radius-control);
	transition: background-color .13s ease, border-color .13s ease;
	background: var(--surface);
	color: var(--ink);
	font-size: 13px;
	font-weight: 600;
	line-height: 1.4;
	text-decoration: none;
	cursor: pointer;
}

/* Every hover below asks that the button is not refused first. A disabled
   button used to lighten under the pointer exactly as a live one does, which is
   an invitation to press something that will not answer. */
.btn:hover:not(:disabled):not([aria-disabled="true"]) {
	background: var(--surface-2);
	color: var(--ink);
}

/* Pressed is the recessed tone, the same one the navigation rail and the table
   head take. There is no lift and no shadow: this product does not have raised
   objects, and a button that jumped under the finger would be the only thing on
   a page of records that moves. */
.btn:active:not(:disabled):not([aria-disabled="true"]) {
	background: var(--surface-2);
}

.btn:disabled,
.btn[aria-disabled="true"] {
	opacity: 0.55;
	cursor: not-allowed;
}

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

.btn-primary {
	background: var(--accent);
	border-color: var(--accent);
	color: var(--on-accent);
}

.btn-primary:hover:not(:disabled):not([aria-disabled="true"]),
.btn-primary:active:not(:disabled):not([aria-disabled="true"]) {
	background: var(--accent-ink);
	border-color: var(--accent-ink);
	color: var(--on-accent);
}

.btn-quiet {
	background: none;
	border-color: transparent;
	color: var(--ink-2);
	font-weight: 400;
}

.btn-quiet:hover:not(:disabled):not([aria-disabled="true"]),
.btn-quiet:active:not(:disabled):not([aria-disabled="true"]) {
	background: var(--surface-2);
	color: var(--ink);
}

.btn-danger {
	background: var(--danger);
	border-color: var(--danger);
	color: var(--on-accent);
}

.btn-danger:hover:not(:disabled):not([aria-disabled="true"]),
.btn-danger:active:not(:disabled):not([aria-disabled="true"]) {
	background: var(--danger-ink);
	border-color: var(--danger-ink);
	color: var(--on-accent);
}

.btn-row {
	display: flex;
	align-items: center;
	gap: var(--space);
	flex-wrap: wrap;
}

/* ---------- Fields and the (i) ---------- */

/* The label, the (i), the control and the explanation, in that order in the
   source and therefore in that order to a screen reader as well as to an eye.
   This used to be a source order of label, (i), explanation, control with four
   CSS order declarations putting it right on screen, which meant the reader
   who cannot see the screen heard the explanation before the thing it explains
   and heard it whether or not it was open. The (i) names its panel with
   aria-controls now, so the two no longer have to be adjacent. */
.field {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
	margin-bottom: calc(var(--space) * 2);
	max-width: 640px;
}

.field > label {
	font-size: 13px;
	font-weight: 600;
	color: var(--ink-2);
}

.field > .field-help {
	width: 100%;
}

.field > input,
.field > select,
.field > textarea,
.field > .switch,
.field > .field-note {
	width: 100%;
}

/* The box a value is typed into is drawn in the Controls section below and not
   here, because a control looks the same whether it stands in a settings field,
   in a filter row, in a table cell or on the client's link page. What used to
   live at this point in the file was a set of rules scoped to .field, so a date
   on the screenings filter and a search box on the subject list were left as
   whatever the browser felt like drawing, on the same screen as a text box that
   had been designed. */

.field .switch {
	display: flex;
	align-items: center;
	gap: var(--space);
	font-size: 13px;
	color: var(--ink-2);
	cursor: pointer;
}

.field-note {
	font-size: 12px;
	color: var(--ink-3);
}

/* A field whose change can cost the reader something. The rule is on the left
   so it reads before the label, and it is the only red on a settings page
   until something actually goes wrong. */
.field-danger {
	padding-left: calc(var(--space) * 1.5);
	border-left: 2px solid var(--danger);
}

/* The (i) is 16px and never grows: it sits inside a flex row beside a label,
   and a flex item left to itself stretches to the row's height, which turned a
   circle into a lozenge on any field whose label wrapped to two lines. */
.field-info {
	display: inline-flex;
	flex: 0 0 auto;
	align-items: center;
	justify-content: center;
	width: 16px;
	height: 16px;
	padding: 0;
	border: 1px solid var(--line);
	border-radius: 50%;
	background: var(--surface);
	color: var(--ink-3);
	cursor: pointer;
}

.field-info:hover {
	border-color: var(--accent);
	color: var(--accent);
}

.field-info[aria-expanded="true"] {
	border-color: var(--accent);
	background: var(--info-bg);
	color: var(--accent);
	/* The pressed (i) and the panel it opened are one object, so the button
	   carries the panel's colour while the panel is open. Without it the reader
	   has to work out which of four buttons produced the paragraph. */
}

/* The explanation is a note written in the margin of the form, so it is ruled
   on its left in the accent and square on that edge: it is attached to the
   field above it rather than floating as a second card. */
.field-help {
	padding: calc(var(--space) * 1.5) 14px;
	background: var(--info-bg);
	border-left: 2px solid var(--accent);
	border-radius: 0 var(--radius) var(--radius) 0;
	color: var(--ink-2);
	font-size: 13px;
	max-width: 68ch;
}

/* ---------- Controls ---------- */

/*
 * EVERY CONTROL IN THE PRODUCT IS DRAWN HERE, once, by element and type rather
 * than by the class of whatever holds it. Four surfaces render these controls,
 * the workspace, the platform desk, the sign-in pages and the client's secure
 * link page, and the reason the rules below are not scoped to any of them is
 * that a compliance officer moves between all four in a morning and a control
 * that changes shape when the page changes is a control they have to learn
 * twice.
 *
 * The direction is the one docs/DESIGN.md settled: a printed record. A control
 * is therefore the ruled blank on a form. It is a white box on the warm ground
 * with a hairline in --line, the same hairline the tables and the cards use,
 * and it has exactly two things to say. Hovered, the rule darkens to --ink-3,
 * which is the only way a reader finds out a box takes typing before they
 * commit to clicking it. Focused, the rule goes to --accent and the shared
 * focus ring lands outside it, so the control the keyboard is on is legible
 * across a screen of thirty fields.
 *
 * Nothing here is drawn by the browser. A native select arrow, a native number
 * spinner and a native checkbox are three different shapes on three operating
 * systems, and a page carrying half a dozen of them cannot be said to have been
 * designed at all: they are the loudest evidence that nobody looked.
 */

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="search"],
input[type="tel"],
input[type="url"],
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"],
input[type="file"]:not(.dropzone),
select,
textarea {
	min-height: var(--control-h);
	max-width: 100%;
	/*
	 * No vertical padding on a single line control, and thirteen pixels of it
	 * either side. The height is --control-h and the value is centred in it, so
	 * a padding of its own would only fight the height; thirteen rather than ten
	 * is the "borders not touching the fonts" complaint answered at its source,
	 * because the gap between a rule and the first letter inside it is the whole
	 * of what makes a box look drawn rather than merely sized.
	 */
	padding: 0 13px;
	background-color: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius-control);
	/* Inwards, never outwards. A control is cut into the page. */
	box-shadow: var(--inset);
	color: var(--ink);
	line-height: 1.4;
	transition: border-color .13s ease, box-shadow .13s ease;
}

/* A reader on a machine set to reduce motion is told about a state by the state
   itself and never by the time it took to arrive. */
@media (prefers-reduced-motion: reduce) {
	input,
	select,
	textarea {
		transition: none;
	}
}

/*
 * SIXTEEN PIXELS IN A CONTROL UNDER A COARSE POINTER, AND IT IS NOT A TASTE.
 *
 * Safari on iOS zooms the whole page when a field smaller than 16px takes
 * focus, and it does not zoom back out. Every text box in this product inherits
 * the 14px interface size, so on an iPhone the act of tapping a field threw the
 * layout sideways and left the reader pinching to find the button they were
 * about to press. On the secure link page that is a client of the firm, on
 * their own phone, being asked for a passport, and it is the first thing they
 * would judge the firm by.
 *
 * It sits with the height rule in tokens.css rather than under a width, for the
 * reason written there: the question is the pointer and never the screen. The
 * interface around the control stays at 14, because what is being answered here
 * is one browser's rule about one element and not a decision about scale.
 */
@media (pointer: coarse) {
	input,
	select,
	textarea {
		font-size: 16px;
	}
}

input[type="text"]:hover:enabled,
input[type="email"]:hover:enabled,
input[type="password"]:hover:enabled,
input[type="number"]:hover:enabled,
input[type="search"]:hover:enabled,
input[type="tel"]:hover:enabled,
input[type="url"]:hover:enabled,
input[type="date"]:hover:enabled,
input[type="time"]:hover:enabled,
input[type="datetime-local"]:hover:enabled,
input[type="month"]:hover:enabled,
select:hover:enabled,
textarea:hover:enabled {
	border-color: var(--ink-3);
}

/*
 * THE HALO IS THE FOCUS INDICATOR, and it replaces the hard outline on anything
 * with a border of its own.
 *
 * :focus-visible above draws a two pixel accent outline two pixels clear of the
 * element, which is right for a link, a tab stop and a button and wrong for a
 * box with a rule of its own: it lands outside the rule, so a focused field in a
 * filter row reached its neighbour, and a focused field in a table cell reached
 * the cell border. This says the same thing in the same colour without leaving
 * the control: the rule goes to the accent, which is the part carrying the
 * contrast, and three pixels of the accent at a fifth sit hard against it, which
 * is the part carrying the size. Both, together, or neither.
 *
 * The tick box and the radio are excluded and keep the outline. They are 16px
 * stamps with no room inside them for a halo, and the outline standing clear of
 * a small square is exactly the treatment a small square needs.
 */
input:not([type="checkbox"]):not([type="radio"]):focus,
select:focus,
textarea:focus {
	outline: none;
	border-color: var(--accent);
	box-shadow: 0 0 0 3px var(--focus-ring);
}

/* A hint is not a value. It is set in --ink-3, the caption ink, and its opacity
   is pinned because Firefox dims a placeholder of its own accord and a hint at
   half of --ink-3 is a hint nobody reads. */
::placeholder {
	color: var(--ink-3);
	opacity: 1;
}

/*
 * THE SELECT DRAWS ITS OWN CHEVRON. The native arrow is a different object on
 * every platform, it cannot be coloured, and next to a designed text box it is
 * the one thing on the row that came from somewhere else. This one is 10 by 6
 * in --ink-3, the same weight as a caption, and it sits far enough in that a
 * long option never runs underneath it.
 */
select {
	padding-right: 33px;
	background-image: var(--select-chevron);
	background-repeat: no-repeat;
	background-position: right 13px center;
	appearance: none;
	-webkit-appearance: none;
	cursor: pointer;
}

/* A list box is not a menu: it shows its options already, so it has nothing to
   point at and needs the room back. */
select[multiple],
select[size]:not([size="1"]) {
	/* A list box has rows rather than one value, so it takes the vertical
	   padding a single line control does not, or its first option sits on its
	   own top rule. */
	padding: 6px 13px;
	background-image: none;
}

/*
 * NO NATIVE SPINNER, ANYWHERE. The two grey arrows are a control for changing a
 * number by one, and almost nothing in this product steps by one: a risk weight
 * is a percentage, a beneficial owner threshold runs to four decimal places, and
 * a retention window is counted in days somebody types. They also change width
 * between browsers, so a row of number fields lined up in one and did not in the
 * next. The min, max and step Ui::field emits still hold; they are validated
 * rather than clicked.
 */
input[type="number"] {
	appearance: textfield;
	-moz-appearance: textfield;
}

input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
	-webkit-appearance: none;
	margin: 0;
}

/* Figures that are compared rather than read: a threshold against a threshold,
   a date against a date. Tabular figures make two numbers of the same length
   the same width, so a difference between them is visible before it is read,
   which is the same reason the tables carry them. */
input[type="number"],
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
	font-variant-numeric: tabular-nums;
}

/* The one part of a date field the browser still owns. It is dimmed to the
   weight of the chevron beside it and comes up to full ink under the pointer,
   so a date and a select read as the same kind of object. */
input::-webkit-calendar-picker-indicator {
	opacity: 0.5;
	cursor: pointer;
}

input::-webkit-calendar-picker-indicator:hover {
	opacity: 1;
}

/* Prose is written in a textarea, so it keeps the reading line height the rest
   of the page has rather than the tight one a single line control needs, and it
   grows downwards only: a textarea dragged wider breaks the column it sits in. */
textarea {
	/* Four lines of prose and its own padding, up from three. A note box that
	   shows three lines invites three lines, and what gets written in this
	   product is the reason a decision was made. */
	min-height: 104px;
	padding: 11px 13px;
	line-height: var(--lh);
	resize: vertical;
}

/*
 * THE FILE CHOOSER. Its button is the browser's own control, in the browser's
 * own language and shape, and it appears on four screens that matter: a client
 * sending a passport, a firm uploading a watchlist, an operator uploading a
 * saved page and the platform installing a release. It is restyled to the exact
 * quiet button used everywhere else, so the thing that opens the file picker
 * looks like every other thing that does something.
 *
 * .dropzone is excluded from the box and from nothing else. That input is the
 * dashed drop target on the documents panel, so it keeps its own dashed border;
 * a type selector beats a class one, and without the exclusion the designed
 * control here would have quietly taken that border away. Its button is the
 * same button as every other, because it is the same act.
 */
input[type="file"]:not(.dropzone) {
	/* The one control whose padding is not zero, because it holds a button
	   rather than a value: four pixels all round the button and the box is
	   --control-h with the button centred in it. */
	padding: 4px 4px 4px 13px;
	line-height: 28px;
	cursor: pointer;
}

input[type="file"] {
	cursor: pointer;
}

input[type="file"]::file-selector-button {
	margin-right: var(--space);
	padding: 4px 12px;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--surface-2);
	color: var(--ink);
	font: inherit;
	font-size: 13px;
	font-weight: 600;
	line-height: 1.4;
	cursor: pointer;
}

input[type="file"]::file-selector-button:hover {
	border-color: var(--accent);
	color: var(--accent);
}

/*
 * THE TICK AND THE DOT, DRAWN RATHER THAN REQUESTED. accent-color, which is
 * what this stylesheet used to ask for, tints the native control and leaves its
 * size, its corner and its tick shape to the platform, so the capability matrix
 * on the team page was a grid of macOS checkboxes and the band choice on a
 * record was a set of macOS radios, both of them rounder and larger than
 * anything else on the page.
 *
 * They stay real inputs. Every one of them is still reached by Tab, toggled by
 * Space, and grouped and arrowed through as a radio group, and the shared focus
 * ring lands on them like everything else, because the only thing taken away is
 * the drawing.
 */
input[type="checkbox"],
input[type="radio"] {
	flex: 0 0 auto;
	width: var(--stamp);
	height: var(--stamp);
	margin: 0;
	padding: 0;
	background-color: var(--surface);
	background-position: center;
	background-repeat: no-repeat;
	border: 1px solid var(--ink-3);
	appearance: none;
	-webkit-appearance: none;
	cursor: pointer;
}

/* Two pixels, the radius a badge takes. A tick box is a stamp on a form. */
input[type="checkbox"] {
	border-radius: 2px;
}

input[type="radio"] {
	border-radius: 50%;
}

input[type="checkbox"]:hover:enabled,
input[type="radio"]:hover:enabled {
	border-color: var(--accent);
}

input[type="checkbox"]:checked {
	background-color: var(--accent);
	background-image: var(--check-tick);
	border-color: var(--accent);
}

/* The dot is the accent showing through a ring of the card's own white, which
   is one paint rather than a second element, and it stays exactly centred at
   any zoom because it is drawn from the border inwards. */
input[type="radio"]:checked {
	background-color: var(--accent);
	border-color: var(--accent);
	box-shadow: inset 0 0 0 3px var(--surface);
}

/*
 * WHAT CANNOT BE CHANGED MUST NOT LOOK LIKE A BOX YOU CAN TYPE IN, or the
 * reader tries, and the reader who tries twice stops trusting the screen. The
 * recessed tone is the same one the table head and the navigation rail use, so
 * a locked control reads as furniture rather than as a field that failed.
 *
 * A readonly field is one the screen is showing you, such as the address a test
 * mail goes to. A disabled one is a field this account or this state may not
 * use, and the not-allowed cursor is the only warning it gets before the click.
 */
input:disabled:not([type="checkbox"]):not([type="radio"]),
select:disabled,
textarea:disabled,
input[readonly],
textarea[readonly] {
	background-color: var(--surface-2);
	color: var(--ink-2);
	border-color: var(--line);
	cursor: not-allowed;
}

input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
	background-color: var(--surface-2);
	border-color: var(--line);
	cursor: not-allowed;
}

/* A ticked box that cannot be unticked still has to read as ticked. The tick is
   white, so the ground under it goes to caption ink rather than to the recessed
   tone that would have hidden it: this is the owner's row on the team page,
   where every capability is on and none of them can be taken away. */
input[type="checkbox"]:checked:disabled,
input[type="radio"]:checked:disabled {
	background-color: var(--ink-3);
	border-color: var(--ink-3);
}

input[type="radio"]:checked:disabled {
	box-shadow: inset 0 0 0 3px var(--surface-2);
}

/*
 * A CONTROL THE SERVER OR THE BROWSER HAS REFUSED. aria-invalid is what the
 * markup says when a field has been answered and rejected; :user-invalid is the
 * browser's own verdict on a required or malformed value, and it holds off
 * until the reader has actually touched the field, so an empty sign-in form is
 * not red before anybody has typed a character. Red on a control means this
 * one, here, and nothing else on these screens uses it.
 */
[aria-invalid="true"],
input:user-invalid,
select:user-invalid,
textarea:user-invalid {
	border-color: var(--danger);
}

[aria-invalid="true"]:focus,
input:user-invalid:focus,
select:user-invalid:focus,
textarea:user-invalid:focus {
	border-color: var(--danger);
}

/*
 * Chrome paints a filled field in its own pale blue and there is no property
 * that turns it off, so the surface is painted back in with an inset shadow
 * large enough to cover the box. The sign-in page is where a password manager
 * fills two fields at once, and it is the first screen anybody sees.
 */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
	-webkit-box-shadow: 0 0 0 100px var(--surface) inset;
	-webkit-text-fill-color: var(--ink);
}

/*
 * AND THE HALO SURVIVES THE PAINT. The rule above is a box-shadow, the focus
 * indicator is a box-shadow, and the later one wins outright: a password
 * manager filling the sign-in form left the one field a keyboard was on with
 * its border in the accent and no ring at all, on the first screen anybody
 * sees. A shadow takes a LIST, so the paint and the halo are both written here
 * rather than one of them quietly replacing the other.
 */
input:-webkit-autofill:focus {
	-webkit-box-shadow: 0 0 0 100px var(--surface) inset, 0 0 0 3px var(--focus-ring);
	box-shadow: 0 0 0 100px var(--surface) inset, 0 0 0 3px var(--focus-ring);
}

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

/* One message at the top of the content, never a stack of banners. */
.flash {
	padding: var(--space) 12px;
	margin-bottom: calc(var(--space) * 2);
	background: var(--surface);
	border: 1px solid var(--line);
	border-left: 3px solid var(--ok);
	border-radius: var(--radius);
	color: var(--ink);
}

.flash-error {
	border-left-color: var(--danger);
}

.form-note {
	margin-top: var(--space);
	font-size: 13px;
	color: var(--ok);
}

.form-note.is-error {
	color: var(--danger);
}

/* ---------- The sandbox banner ----------
   It sits above every workspace page rather than on one screen, because the
   danger of a sandbox is that its results look exactly like real ones. Loud
   enough to be read before the content, quiet enough that somebody working in a
   sandbox all day is not fighting it. */

.notice {
	border: 1px solid var(--line);
	border-left: 3px solid var(--line);
	border-radius: 4px;
	padding: 12px 14px;
	margin-bottom: 16px;
	font-size: 0.94rem;
	line-height: 1.5;
}

.notice-warn {
	border-left-color: var(--warn);
}

.notice strong {
	display: block;
	margin-bottom: 2px;
}

/* ---------- Alert levels ---------- */

.alert-level-urgent,
.alert-level-warning,
.alert-level-info {
	border-left: 3px solid var(--line);
	padding-left: 12px;
}

.alert-level-urgent {
	border-left-color: var(--danger);
}

.alert-level-warning {
	border-left-color: var(--warn);
}

.alert-level-info {
	border-left-color: var(--accent);
}

/* ---------- The drawer ---------- */

/* A record opens beside the list it was found in, never instead of it: the
   reader keeps their place in the queue while they read one row of it. */
.drawer {
	position: fixed;
	inset: 0;
	z-index: 60;
	visibility: hidden;
	pointer-events: none;
}

.drawer.open {
	visibility: visible;
	pointer-events: auto;
}

.drawer-scrim {
	position: absolute;
	inset: 0;
	background: rgba(26, 29, 33, 0.35);
	opacity: 0;
	transition: opacity 180ms ease;
}

.drawer.open .drawer-scrim {
	opacity: 1;
}

.drawer-panel {
	position: absolute;
	top: 0;
	right: 0;
	width: 480px;
	max-width: 100%;
	height: 100%;
	overflow-y: auto;
	padding: calc(var(--space) * 3);
	background: var(--surface);
	border-left: 1px solid var(--line);
	box-shadow: -16px 0 40px -24px rgba(26, 29, 33, 0.5);
	transform: translateX(100%);
	transition: transform 180ms cubic-bezier(0.2, 0.6, 0.2, 1);
}

.drawer.open .drawer-panel {
	transform: translateX(0);
}

.drawer-panel > [data-drawer-close] {
	position: absolute;
	top: var(--space);
	right: var(--space);
}

body.drawer-open {
	overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
	.drawer-panel,
	.drawer-scrim {
		transition: none;
	}
}

/* ---------- The exception queue ---------- */

/* The workspace home. One row per thing that needs a person today: the level
   as a colour bar, what it is, why it is here, and the one button that deals
   with it. Nothing in a row is decoration. */
.queue {
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius);
}

.queue-item {
	display: flex;
	/* The one button that deals with the row sits hard right while it fits and
	   drops under the detail when it does not, which is the first thing to go
	   at 360px on the page a compliance officer opens first every morning. */
	flex-wrap: wrap;
	align-items: flex-start;
	gap: calc(var(--space) * 2);
	padding: 12px calc(var(--space) * 2);
	border-bottom: 1px solid var(--line);
	border-left: 3px solid var(--line);
}

.queue-item:last-child {
	border-bottom: 0;
}

.queue-item.alert-level-urgent {
	border-left-color: var(--danger);
}

.queue-item.alert-level-warning {
	border-left-color: var(--warn);
}

.queue-item.alert-level-info {
	border-left-color: var(--accent);
}

.queue-body {
	flex: 1 1 auto;
}

.queue-title {
	font-weight: 600;
}

.queue-detail {
	margin-top: 4px;
	color: var(--ink-2);
	font-size: 13px;
}

.queue-item .btn {
	flex: none;
	margin-left: auto;
}

/* ---------- Empty states ---------- */

/* An empty screen names the next thing to press. A dead end is a defect. */
.empty {
	padding: calc(var(--space) * 5) calc(var(--space) * 3);
	background: var(--surface);
	border: 1px dashed var(--line);
	border-radius: var(--radius);
	text-align: center;
	color: var(--ink-2);
}

.empty p {
	max-width: 52ch;
	margin: 0 auto calc(var(--space) * 2);
}

/* ---------- Badges ---------- */

/*
 * A BADGE IS A STAMP, AND A STAMP HAS ROOM ROUND THE WORD IN IT.
 *
 * One pixel of vertical padding put the word hard against its own rule top and
 * bottom, which is the "borders touching the fonts" complaint in its purest
 * form: at 12px the letters with descenders were touching the line under them.
 * Three pixels and a line height of 1.45 is the least that stops it, and it is
 * still a stamp rather than a button, because the corner stays at two pixels
 * and the height stays well under a control's.
 *
 * The three status badges are washes rather than white boxes with a coloured
 * rule. A coloured outline on white is legible and it is not FINDABLE: on a row
 * carrying three badges the reader has to read all three to learn which one is
 * the state. A tint is found before it is read. Each one is its own colour on
 * its own wash and measures above five to one, which is why --warn was darkened
 * when these arrived: they are read on the tint and never on white.
 *
 * Colour is never the only thing a badge says. Every one of them carries the
 * word as well, so a reader who cannot separate the green from the red loses
 * nothing at all.
 */
.badge {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	padding: 3px 8px;
	border: 1px solid var(--line-soft);
	border-radius: 2px;
	background: var(--sunk);
	color: var(--ink-2);
	font-size: 12px;
	font-weight: 600;
	line-height: 1.45;
	white-space: nowrap;
}

.badge-ok {
	border-color: var(--ok-line);
	background: var(--ok-bg);
	color: var(--ok);
}

.badge-warn {
	border-color: var(--warn-line);
	background: var(--warn-bg);
	color: var(--warn);
}

.badge-danger {
	border-color: var(--danger-line);
	background: var(--danger-bg);
	color: var(--danger);
}

/*
 * THE FOURTH CHIP: SOMETHING IS IN HAND.
 *
 * An alert somebody has read and not closed was drawn as the plain grey chip,
 * which is the same chip as "no state recorded", so the one act a reviewer
 * performs on a queue of forty left no mark a reader could find. It is the
 * accent wash, which is the family this product already uses for a selected row
 * and for the (i) panel, so it says "this one is spoken for" without adding a
 * fourth hue to the product.
 */
.badge-info {
	border-color: var(--accent);
	background: var(--info-bg);
	color: var(--accent);
}

/* ---------- Key and value lists ---------- */

/* A record page is read, not scanned, so the label sits in a fixed column and
   the value gets the rest. Values wrap; the label never does. */
.kv {
	display: grid;
	grid-template-columns: 180px minmax(0, 1fr);
	/*
	 * The label and its value sit on ONE baseline. The label is 13px and the
	 * value is 14px, and a grid stretches its items by default, so the two were
	 * set on two different lines within the same row: a column of figures beside
	 * a column of labels, neither aligned to the other. Baseline is the whole
	 * fix and it costs nothing anywhere else, because every kv in this product
	 * is a label beside a value.
	 */
	align-items: baseline;
	gap: calc(var(--space) * 1.5) calc(var(--space) * 2);
}

/*
 * A LABEL MAY CARRY AN (i), AND AN (i) NEEDS AIR.
 *
 * The button was written straight after the label text with nothing between
 * them, so a 16px circle sat hard against the last letter of the word it
 * explains. The label is a flex row now, which gives the gap, and any
 * explanation panel opened inside it takes a line of its own at the label
 * column's full width rather than being squeezed beside the button.
 */
.kv dt {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 6px;
	color: var(--ink-3);
	font-size: 13px;
}

.kv dt > .field-help {
	flex-basis: 100%;
	margin-top: 4px;
}

.kv dd {
	margin: 0;
	overflow-wrap: anywhere;
}

/*
 * A KV WHOSE VALUES ARE FIGURES RATHER THAN FACTS.
 *
 * Three counts on the retention card were read in a 180px label column, so each
 * label wrapped to two lines and each explanation opened into a 180px trench.
 * When the value is a number the label needs the room and the number needs
 * almost none, so the proportion is turned round: the label column takes what
 * is left and the figures share one narrow column at the right, where they line
 * up against each other and a difference between them can be seen rather than
 * read.
 */
.kv-figures {
	grid-template-columns: minmax(0, 1fr) auto;
}

.kv-figures dd {
	font-variant-numeric: tabular-nums;
	font-weight: 600;
	text-align: right;
}

@media (max-width: 640px) {
	.kv {
		grid-template-columns: minmax(0, 1fr);
		gap: 0 0;
	}

	.kv dd {
		margin-bottom: var(--space);
	}

	/* A figure is narrow at every width, so this one keeps its two columns
	   rather than dropping a number onto a line of its own under the label it
	   belongs to. */
	.kv-figures {
		grid-template-columns: minmax(0, 1fr) auto;
		gap: calc(var(--space) * 1.5) calc(var(--space) * 2);
	}

	.kv-figures dd {
		margin-bottom: 0;
	}
}

/* ---------- The sign-in card ---------- */

/*
 * CENTRED WITH auto MARGINS AND NOT WITH align-items, because a flex item
 * centred by alignment and taller than its container OVERFLOWS AT BOTH ENDS and
 * the top end cannot be scrolled to. The two-factor enrolment page is the
 * tallest card here by a distance, a heading, two sentences, a 192px QR symbol,
 * the secret, a field and a button, and on a phone in landscape it is taller
 * than the viewport: the QR code was reachable and the heading above it was not.
 * Auto margins push instead of centring, so the overflow lands at the bottom
 * where a scrollbar can reach it.
 *
 * 100svh after 100vh for the same family of reason: on a phone 100vh is the
 * viewport with the browser's own bars pretending not to be there, so a card
 * sized to it sits partly underneath them. Browsers that do not know svh keep
 * the line above.
 */
.auth-body {
	display: flex;
	justify-content: center;
	min-height: 100vh;
	min-height: 100svh;
	padding: calc(var(--space) * 3);
}

.auth {
	width: 100%;
	max-width: 400px;
	margin: auto;
}

.auth-brand {
	margin-bottom: calc(var(--space) * 3);
}

.auth-brand .surface-tag {
	display: block;
	margin-bottom: 2px;
}

.auth-brand .auth-name {
	display: block;
	font-family: var(--font-display);
	font-size: 26px;
	font-weight: 600;
	letter-spacing: 0.01em;
}

.auth form,
.auth .auth-panel {
	padding: calc(var(--space) * 3);
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius);
}

.auth h1 {
	font-family: var(--font-display);
	font-size: 19px;
	font-weight: 600;
	margin-bottom: var(--space);
}

.auth p {
	color: var(--ink-2);
	margin-bottom: calc(var(--space) * 2);
	max-width: 46ch;
}

.auth .field {
	max-width: none;
}

/* The action and the way out sit on one line, the button sized to its own
   words. A button stretched across the card would be the widest thing on the
   page and would say nothing more for the extra width. */
.auth-actions {
	display: flex;
	align-items: center;
	justify-content: space-between;
	/* The button and the way out share a line while they fit. On the narrowest
	   phone in use they do not, and a forgotten-password link running off the
	   side of a sign-in card is a person locked out. */
	flex-wrap: wrap;
	gap: calc(var(--space) * 2);
	margin-top: calc(var(--space) * 3);
}

.auth-actions .btn-primary {
	padding: 7px 16px;
}

.auth .auth-alt {
	margin: 0;
	font-size: 13px;
}

/* A submit button that has to read as the sentence it replaced. Signing out is
   a POST now, so the way back from the code page is a button where a link used
   to be, and the reader should not be able to tell. */
.link-button {
	padding: 0;
	border: 0;
	background: none;
	font: inherit;
	font-size: 13px;
	color: var(--accent);
	text-decoration: underline;
	text-underline-offset: 2px;
	cursor: pointer;
}

.link-button:hover {
	color: var(--accent-ink);
}

/* The sign-out form in the bar is a wrapper and nothing else. Without this it
   is a block in a flex row and pushes the button off the line the name sits
   on. */
.topbar-out {
	display: flex;
	margin: 0;
}

/* The one caption under the card. It is true, it is the promise of the
   product, and it is the last thing read before signing in. */
.auth-foot {
	margin-top: calc(var(--space) * 2);
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.02em;
	color: var(--ink-3);
	text-align: center;
}

.auth-secret {
	display: block;
	padding: var(--space) 12px;
	margin-bottom: calc(var(--space) * 2);
	background: var(--surface-2);
	border-radius: var(--radius);
	font-family: var(--mono);
	font-size: 13px;
	letter-spacing: 0.12em;
	word-break: break-all;
	text-align: center;
}

.auth-qr {
	display: flex;
	justify-content: center;
	margin-bottom: calc(var(--space) * 2);
}

.auth-qr svg {
	width: 192px;
	height: 192px;
}

.code-input {
	font-family: var(--mono);
	font-size: 20px;
	letter-spacing: 0.4em;
	text-align: center;
}

/*
 * THE FIRST THING ANYBODY EVER SEES OF THIS PRODUCT, ON THE SMALLEST SCREEN IT
 * IS SEEN ON. Three steps of body padding and three more of card padding is
 * forty eight pixels of nothing either side of a 360px screen, which is more
 * than a seventh of it, spent before a word is drawn. Both drop a step.
 *
 * The action goes full width here and nowhere else. Everywhere else in this
 * product a button is sized to its own words, deliberately, because a stretched
 * button says nothing more for the extra width. A sign-in card is the one place
 * that is wrong: there is exactly one thing to do on the page, the reader is
 * holding the phone in one hand, and the whole width is the easiest target
 * there is. The way out keeps its own size underneath it.
 */
@media (max-width: 640px) {
	.auth-body {
		padding: calc(var(--space) * 2);
	}

	.auth form,
	.auth .auth-panel {
		padding: calc(var(--space) * 2);
	}

	.auth-actions {
		justify-content: flex-start;
	}

	.auth-actions .btn-primary {
		width: 100%;
	}
}

/* ---------- The platform desk ---------- */

/* Every rule below was earned by a screen on the desk. They are here rather
   than in a page, because a block in a view comes later in the document and
   beats this file on equal specificity, and the first narrow fix written that
   way is the end of the design system. */

/* A group of fields that can cost the platform something, gathered inside its
   own card with one sentence above them. Same red rule as a dangerous field,
   for the same reason: it is the only red on the page until something is
   actually wrong. */
.card-danger {
	border-left: 3px solid var(--danger);
}

.card .card-danger {
	background: var(--bg);
	margin-bottom: calc(var(--space) * 3);
}

/* Inside the danger block the field's own red rule is a second line saying
   the same thing, one indent to the right of the first. The block has said
   it. */
.card-danger .field-danger {
	padding-left: 0;
	border-left: 0;
}

.card-danger > p {
	margin-bottom: calc(var(--space) * 2);
	max-width: 68ch;
	color: var(--ink-2);
}

/* One credential and the two actions that belong to it, ruled off from the
   next so a Save press cannot be read as belonging to the box above it. */
.cred {
	padding-bottom: calc(var(--space) * 2);
	margin-bottom: calc(var(--space) * 2);
	border-bottom: 1px solid var(--line);
}

.cred:last-child {
	padding-bottom: 0;
	margin-bottom: 0;
	border-bottom: 0;
}

/* A field inside a table cell: the column is the width it gets, and the
   spacing below it belongs to the row rather than to the field. */
.table .field {
	max-width: none;
	margin-bottom: var(--space);
}

/* Stored text read exactly as it is: a crontab line to copy, a block of JSON
   from the history. It wraps rather than scrolling, because a line of it can
   be longer than any panel this product has. */
.json-block {
	padding: var(--space) 12px;
	margin: 0;
	background: var(--surface-2);
	border-radius: var(--radius);
	font-family: var(--mono);
	font-size: 12px;
	line-height: 1.45;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
}

/* Before and after, side by side. Two explicit columns rather than a fit,
   because the pair is the point: one column with the other underneath it
   makes the reader hold the first in their head. */
.json-pair {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: calc(var(--space) * 2);
}

/* Two explicit columns of JSON at 148px each are two columns of one character.
   Below 760 the before goes above the after, which is the order they are read
   in anyway. */
@media (max-width: 760px) {
	.json-pair {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* The drawer holds a fragment with headings of its own, and a heading with no
   rule of its own falls back to the browser's, which is twice the size of
   anything else on the page. */
.drawer-body > h2 {
	font-family: var(--font-display);
	font-size: 17px;
	font-weight: 600;
	margin-bottom: calc(var(--space) * 2);
	padding-right: calc(var(--space) * 4);
}

.drawer-body h3 {
	margin-bottom: 4px;
	font-size: 12px;
	font-weight: 600;
	color: var(--ink-3);
}

.drawer-body .kv {
	margin-bottom: calc(var(--space) * 3);
}

/* A badge that is also the way to deal with what it counts. It keeps the
   badge's colour rather than taking the link colour, because the colour is
   the status and losing it would make a red count read as an ordinary link. */
a.badge {
	text-decoration: none;
}

a.badge:hover {
	background: var(--surface-2);
	color: inherit;
}

a.badge-danger:hover {
	color: var(--danger);
}

/* The action on an attention row sits hard right whether it is a bare button
   or a form wrapped around one. */
.queue-item form {
	flex: none;
	margin-left: auto;
}

/* ---------- The workspace ---------- */

/* A column whose whole content is one box: centred, and only as wide as the
   heading above it, so the permission names keep the width the reader is
   actually reading. */
.table th.tick,
.table td.tick {
	width: 104px;
	text-align: center;
	white-space: nowrap;
}

/* The column that holds the buttons and the sentence that stands in for one.
   Under a fixed table layout every column is otherwise the same width, and
   the widest thing on the row would get the same room as a status badge. */
.table th.col-actions {
	width: 230px;
}

/* Seven columns of names, addresses, dates and actions need more room than
   the shared floor gives a table, and the wrapper around it is what scrolls.
   Crushing every column to one word a line instead is how a list of people
   becomes unreadable on a laptop. */
.table-wide {
	min-width: 900px;
}

/* A control in a table cell belongs to its own column. A select left at its
   natural width overhangs the next column under a fixed table layout, and the
   role select was printing itself across the status beside it. */
.table td select {
	max-width: 100%;
}

/* The answer to a tick is printed in the width of a checkbox column, so it
   wraps and shrinks rather than running out of the table and being read as
   half a word. A refusal is longer than an agreement and grows the row, which
   is the right way round: the row that went wrong is the one to notice. */
.table td.tick .form-note {
	font-size: 11px;
	line-height: 1.35;
	white-space: normal;
}

/* Where a row cannot offer an action it says why, in the place the button
   would have been. It is quieter than the name beside it and sits above the
   buttons that are still there, because it explains the ones that are not. */
.row-note {
	max-width: 46ch;
	margin-bottom: var(--space);
	color: var(--ink-2);
	font-size: 13px;
}

/*
 * A CHIP IN A CELL IS NEVER WIDER THAN THE CELL.
 *
 * .badge carries white-space: nowrap, which is right in a flex row where a chip
 * has room to sit beside its neighbours and wrong in a table: .table is
 * table-layout: fixed, so every column gets the width the layout gives it and
 * nothing in a cell can ask for more. A chip that cannot wrap has a minimum
 * width of its whole phrase, so it simply drew OVER the column beside it.
 *
 * That was reported from a live installation: "The client is doing it now" in
 * the identity check's own column was printing across "Passed" in the column
 * after it, so two values of one row were on top of each other and a reader had
 * no way of telling which column either belonged to. It is the worst kind of
 * table fault for exactly that reason, and it is not one page's bug: every
 * status chip in every table in this product was drawn the same way and had
 * simply never met a label long enough to show it.
 *
 * So it is fixed once, here, for every table. The chip wraps inside its own
 * outline rather than escaping it, which keeps the thing the design depends on:
 * a chip is found by its shape before it is read, and a shape that leaks into
 * the next column has no boundary to be found by. max-width is the guard for a
 * single word longer than the column, which wrapping alone cannot help.
 *
 * Not solved by shortening the label. The label is right where it stands on its
 * own, and a column that can only hold short words is a column that will meet a
 * long one in another language: Greek is longer than English almost everywhere.
 */
.table td .badge {
	max-width: 100%;
	white-space: normal;
}

/* An (i) panel inside a table cell explains a column rather than a field, so
   it takes the room a field would have given it rather than the line height
   of the cell. */
.table td > .field-help {
	margin-top: var(--space);
	white-space: normal;
}

/* ---------- The record pages ---------- */

/* The name of the record and the three things that can be done to it, on one
   line above the sections. It is a page title with actions rather than a card,
   because the name is the one voice above the fold and a card around it would
   make it a heading inside something else. */
.record-header {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	justify-content: space-between;
	gap: var(--space);
	margin-bottom: calc(var(--space) * 3);
}

.record-header .page-title {
	margin-bottom: 0;
}

.record-header .page-title p {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space);
}

.record-header .page-actions {
	margin-top: 0;
	flex-wrap: wrap;
}

/* A section is a card with an anchor, because every action on these pages
   sends the reader back to the section they acted in. */
.record-section {
	display: block;
	margin-bottom: calc(var(--space) * 3);
	padding: calc(var(--space) * 2);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--surface);
	scroll-margin-top: calc(var(--space) * 2);
}

.record-section > h3 {
	margin-top: calc(var(--space) * 3);
	font-size: 14px;
	font-weight: 600;
}

.record-section > p {
	max-width: 68ch;
	margin-bottom: calc(var(--space) * 2);
	color: var(--ink-2);
}

.record-section .table-wrap {
	margin-bottom: var(--space);
}

.section-head {
	display: flex;
	/* The heading and the buttons beside it. On a narrow screen the buttons
	   take a line of their own rather than squeezing the heading into one word
	   a line. */
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space);
	margin-bottom: var(--space);
}

.section-head h2 {
	font-size: 15px;
	font-weight: 600;
}

.section-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space);
}

/* The inline editor. Two columns above 900px, because a record carries thirty
   fields and one column of them is a page nobody reaches the bottom of. */
.kv-edit {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 0 calc(var(--space) * 3);
}

@media (min-width: 900px) {
	.kv-edit {
		grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	}
}

.kv-edit > * {
	min-width: 0;
}

/* A form that sits under the table it adds to rather than in a card of its
   own. The rule above it is what says the two belong together. */
.inline-form {
	margin-top: calc(var(--space) * 2);
	padding-top: calc(var(--space) * 2);
	border-top: 1px solid var(--line);
}

/* The create page: two forms side by side above 900px, one under the other
   below it. Explicit columns rather than a fit, so a narrow window never
   leaves half a row of white space. */
.record-columns {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: calc(var(--space) * 3);
}

@media (min-width: 900px) {
	.record-columns {
		grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	}
}

.record-columns > * {
	min-width: 0;
}

/* The filters on the list. The same field grid as an editor, because they are
   the same controls and a filter that looked different would read as something
   other than a form. */
.filters {
	display: grid;
	/*
	 * Below 760 the number of columns is not a decision, it is whatever fits:
	 * one on a phone and two on a tablet or a narrow window, out of one rule
	 * and without a fourth breakpoint. Seven filters in a single column made a
	 * card taller than the list it filters, on the one page where the filters
	 * are the reason somebody opened it.
	 *
	 * The min() inside the minmax is the guard: a bare minmax(13rem, 1fr) in a
	 * container narrower than 13rem resolves to 13rem and pans the page.
	 */
	grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
	gap: 0 calc(var(--space) * 3);
}

@media (min-width: 760px) {
	.filters {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

.filters > * {
	min-width: 0;
}

.filters .btn-row {
	grid-column: 1 / -1;
}

.pager {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space);
	margin-top: calc(var(--space) * 2);
}

.pager-here {
	font-weight: 600;
}

/* A set of values read all at once rather than one by one: the countries of a
   role, where the point is the shape of the list. */
.chips {
	display: flex;
	flex-wrap: wrap;
	gap: 4px;
}

.chip {
	display: inline-flex;
	align-items: center;
	/* The same three pixels the badge takes, and for the same reason: at 12px
	   one pixel put the descenders on the rule under them. */
	padding: 3px 8px;
	border: 1px solid var(--line-soft);
	border-radius: 2px;
	background: var(--sunk);
	font-size: 12px;
	line-height: 1.45;
}

.country-role {
	padding: calc(var(--space) * 1.5) 0;
	border-bottom: 1px solid var(--line-soft);
}

.country-role:last-child {
	border-bottom: 0;
}

.country-role-name {
	margin-bottom: 4px;
	color: var(--ink-3);
	font-size: 13px;
}

/* The name lookup's answers. They sit under the box that produced them and
   take no room at all until there are any. */
.search-choices {
	margin: 4px 0 0;
	padding: 0;
	list-style: none;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--surface);
}

.search-choices li {
	padding: 6px var(--space);
	border-bottom: 1px solid var(--line-soft);
}

.search-choices li:last-child {
	border-bottom: 0;
}

.search-chosen {
	margin-top: 4px;
	color: var(--ink-2);
	font-size: 13px;
}

/* What somebody wrote down. A pinned note keeps an accent rule on its left,
   which is the only place on a record page the accent appears. */
.notes {
	margin: 0;
	padding: 0;
	list-style: none;
}

.note {
	padding: calc(var(--space) * 1.5) 0;
	border-bottom: 1px solid var(--line-soft);
}

.note:last-child {
	border-bottom: 0;
}

.note-pinned {
	padding-left: var(--space);
	border-left: 2px solid var(--accent);
}

.note-head {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space);
	margin-bottom: 4px;
}

.note-body {
	max-width: 68ch;
	overflow-wrap: anywhere;
}

/* A pair of records that may be one: both names, why, and the three ways out. */
.duplicate {
	padding: calc(var(--space) * 1.5) 0;
	border-bottom: 1px solid var(--line-soft);
}

.duplicate:last-child {
	border-bottom: 0;
}

.duplicate-names {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space);
}

.duplicate-reasons {
	margin: 4px 0 var(--space);
	padding-left: calc(var(--space) * 2);
	color: var(--ink-2);
	font-size: 13px;
}

/*
 * What an AI read off a document. It is washed in the accent's own pale tone,
 * the same wash the (i) panel takes, because it is the product speaking rather
 * than the record: everything inside it is a proposal until somebody ticks it.
 */
.proposal {
	margin: var(--space) 0;
	padding: calc(var(--space) * 1.5);
	border-radius: var(--radius);
	background: var(--info-bg);
}

.proposal-title {
	font-weight: 600;
}

.proposal-meta,
.proposal-skip {
	color: var(--ink-2);
	font-size: 13px;
}

.proposal-picks {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space);
	margin: var(--space) 0 4px;
}

.proposal-rows {
	margin: 0 0 var(--space);
	padding: 0;
	list-style: none;
}

.proposal-row {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 0 calc(var(--space) * 2);
	padding: 4px 0;
	border-bottom: 1px solid var(--line);
}

@media (min-width: 760px) {
	.proposal-row {
		grid-template-columns: minmax(0, 16rem) minmax(0, 1fr) minmax(0, 1fr);
	}
}

.proposal-row > * {
	min-width: 0;
}

.proposal-row:last-child {
	border-bottom: 0;
}

.proposal-label {
	display: flex;
	align-items: center;
	gap: 6px;
}

.proposal-value {
	overflow-wrap: anywhere;
}

.proposal-current {
	color: var(--ink-3);
	font-size: 13px;
}

.proposal-parties ul {
	margin: 4px 0;
	padding-left: calc(var(--space) * 2);
}

/* A reading that failed is not a proposal, so it does not wear the wash that
   says something is being offered. */
.proposal-failed {
	background: var(--surface-2);
}

/* The upload box. Dashed until something is dragged over it, when the border
   goes solid and says the drop will be taken. */
.dropzone {
	padding: calc(var(--space) * 1.5);
	border: 1px dashed var(--line);
	border-radius: var(--radius);
	background: var(--surface);
}

.dropzone.is-dragging {
	border-style: solid;
	border-color: var(--accent);
	background: var(--info-bg);
}

/* The one badge that is a deadline rather than a state. It is the only red on
   a record page, and it is red because an expired passport is the one thing on
   the row somebody has to act on today. */
.badge-expired {
	border-color: var(--danger);
	background: var(--surface);
	color: var(--danger);
}

/* The key to the document kinds under the upload form: a list read once and
   then never again, so it is set smaller than the form above it. */
.kind-key {
	margin-top: calc(var(--space) * 2);
	color: var(--ink-2);
	font-size: 13px;
}

.json-pair h4 {
	margin-bottom: 4px;
	color: var(--ink-3);
	font-size: 12px;
}

/* ---------- Screening ---------- */

/* A list of runs or of anything else that is already a line of its own: no
   bullets and no indent, because the line carries a link, a badge and a
   sentence and a marker in front of it would be a fourth thing to read. */
.plain-list {
	margin: 0;
	padding: 0;
	list-style: none;
}

.plain-list li {
	padding: var(--space) 0;
	border-bottom: 1px solid var(--line-soft);
}

.plain-list li:last-child {
	border-bottom: 0;
}

/*
 * THE HEAD OF A SCREENING RESULT: WHAT WAS SCREENED, AND WHAT IT WAS JUDGED BY.
 *
 * The page used to say all of it in one paragraph held to 68 characters, with
 * the whole right half of a 1200px page empty beside it. The complaint was not
 * the width of the measure and widening it would have been the wrong answer:
 * past about ninety characters an eye loses its place coming back to the left
 * margin, so a 1200px line of prose reads worse than a 600px one.
 *
 * The fault was that it was never prose. Three different kinds of fact were
 * welded into one sentence: what was screened and when, the figures it was
 * judged by, and what the run cost. The first is a sentence and stays one. The
 * other two are labelled figures and are laid out as figures, in the space that
 * was empty, beside the name and the status. Nothing is dropped, nothing is
 * summarised and nothing becomes a tile with a big number on it: this is a
 * record an auditor reads, not a dashboard.
 */
.screening-head {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	justify-content: space-between;
	gap: calc(var(--space) * 3);
}

/* The name, the status, the sentence and the two ways out. It takes the room
   left over and its prose keeps the measure .page-title p already gives it. */
.screening-head-main {
	flex: 1 1 34em;
	min-width: 0;
}

/*
 * A BLOCK OF LABELLED FIGURES, NOT A ROW OF TILES.
 *
 * Five numbers an auditor may ask about years from now: which policy version
 * judged this, what it counted as a hit, where it floored the name, how many
 * entries it compared and how long that took. Each is a label and a figure on
 * one baseline, in the recessed tone, at the size everything else on the page
 * is. Nothing is enlarged: a figure drawn three times its neighbours would be
 * this page claiming one of these five matters more than the others, and which
 * one matters depends entirely on the question being asked of it.
 */
.figure-set {
	flex: 0 1 auto;
	display: grid;
	grid-template-columns: auto auto;
	align-items: baseline;
	gap: 6px calc(var(--space) * 2);
	margin: 0;
	padding: calc(var(--space) * 1.5) calc(var(--space) * 2);
	background: var(--sunk);
	border: 1px solid var(--line-soft);
	border-radius: var(--radius);
}

.figure-set dt {
	color: var(--ink-3);
	font-size: 12px;
}

.figure-set dd {
	margin: 0;
	font-variant-numeric: tabular-nums;
	font-weight: 600;
	text-align: right;
	white-space: nowrap;
}

/*
 * WHAT WAS SEARCHED, AS A SET.
 *
 * Ten lists each with a version label, read as an aligned set rather than as
 * ten clauses of one sentence. This is the coverage statement and it is the
 * evidence behind the claim that a clear result can be produced again years
 * later, so every version label is printed in full and in the mono face, which
 * is where this product puts any string compared character by character.
 *
 * Columns flow to the width available, so ten lists are a short wide block
 * rather than a tall narrow one, and they are auto-fill against a real minimum
 * rather than a fixed count: a firm screening against three lists gets three
 * across and not three stranded in the first of three columns. Ten lists are
 * never ten cards. On paper it is one column, so a name and the version beside
 * it can never be separated by a column break.
 */
.coverage {
	display: grid;
	/* min() against the container, or a single column resolves to 21em in a
	   space narrower than that and takes the page sideways with it. */
	grid-template-columns: repeat(auto-fill, minmax(min(21em, 100%), 1fr));
	column-gap: calc(var(--space) * 4);
	margin: 0;
	padding: 0;
	list-style: none;
}

.coverage-row {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space);
	padding: 6px 0;
	border-bottom: 1px solid var(--line-soft);
}

.coverage-name {
	min-width: 0;
}

.coverage-version {
	font-family: var(--mono);
	font-size: 12px;
	letter-spacing: 0.02em;
	color: var(--ink-2);
}

/* A list this organisation can no longer read, or a copy that is no longer on
   file. It is a fact about the evidence rather than a fault, so it is said in
   the caption ink and in words rather than left as a blank. */
.coverage-gone {
	font-size: 12px;
	color: var(--ink-3);
}

/* A warning on a result is a fact about the answer rather than an alarm, so it
   is prose in the reading colour and never a coloured banner. A page of
   stacked banners on a screen whose whole job is careful reading would be the
   loudest thing on it. */
.screening-warning {
	margin: 0 0 var(--space);
	color: var(--ink-2);
}

/* One hit. The card is the paper the argument is written on, and the head is
   the line that stays visible when the argument is folded away. */
.hit-head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--space) calc(var(--space) * 2);
}

.hit-name {
	flex: 1 1 14rem;
	font-family: var(--font-display);
	font-size: 16px;
	text-align: left;
}

.hit-scores {
	display: flex;
	/* They wrap onto two lines rather than overflowing, and each keeps its own
	   word under it. What must never happen is the two reading as one figure:
	   a match score answers whether this is the same party and a hit risk
	   answers how serious it would be if it were, and nothing in this product
	   adds them, averages them or shows one as the other. */
	flex-wrap: wrap;
	gap: calc(var(--space) * 2);
}

/* The two numbers, each under its own word. They are two columns and never one
   number, because a match score and an alert order answer two different
   questions and nothing in this product adds them. */
.hit-score {
	display: flex;
	flex-direction: column;
	font-variant-numeric: tabular-nums;
}

.hit-score-label {
	color: var(--ink-3);
	font-size: 12px;
}

.hit-tags {
	display: flex;
	flex-wrap: wrap;
	gap: 4px;
}

.hit-body {
	margin-top: calc(var(--space) * 2);
}

/* The sentence that matters most on the page: this pair was decided, and the
   publisher has changed the entry since. It gets the one rule of colour a hit
   is allowed. */
.hit-changed {
	margin: 0 0 var(--space);
	padding-left: var(--space);
	border-left: 2px solid var(--warn);
	color: var(--ink);
}

.hit-decided {
	margin: 0 0 var(--space);
	color: var(--ink-2);
}

.breakdown td {
	vertical-align: top;
}

/* WHAT THE ENTRY ITSELF SAYS, above the arithmetic that scored it.

   It is deliberately not a card. A card inside the hit's own card draws a
   second box around something that is not a second object, and the panel
   flattens into boxes inside boxes with no hierarchy left. A rule down the left
   says "this is the list speaking rather than us", which is the only
   distinction the reader needs here. */
.entry-says {
	margin: 0 0 calc(var(--space) * 2);
	padding-left: var(--space);
	border-left: 2px solid var(--line);
}

.entry-says-head {
	margin: 0 0 4px;
	font-size: 14px;
}

.entry-says-sub {
	margin: var(--space) 0 4px;
	font-size: 13px;
}

.entry-says-note {
	margin: 0 0 var(--space);
	color: var(--ink-2);
	font-size: 13px;
}

/* Label and value in pairs. The grid gives every row the same label column so
   values line up down the list, which is what lets a reader compare two entries
   at a glance; a dt sizing itself to its own text does not. */
.entry-facts {
	margin: 0;
	display: grid;
	gap: 2px 0;
}

.entry-fact {
	display: grid;
	grid-template-columns: minmax(120px, 190px) minmax(0, 1fr);
	gap: 0 var(--space);
	align-items: baseline;
}

.entry-fact dt {
	color: var(--ink-2);
	font-size: 13px;
}

.entry-fact dd {
	margin: 0;
	/* A name off a sanctions list can be long and, in some scripts, carries no
	   spaces at all, so it wraps inside its own column rather than pushing the
	   panel sideways. */
	overflow-wrap: anywhere;
}

.entry-fact dd .muted,
.entry-relations .muted {
	color: var(--ink-2);
	font-size: 12px;
}

.entry-relations {
	margin: 0;
	padding: 0;
	list-style: none;
	display: grid;
	gap: 3px 0;
}

.entry-relations li {
	overflow-wrap: anywhere;
}

/* The relation's own word, which is the part a reader scans down the list for. */
.entry-relation-type {
	color: var(--ink-2);
	font-size: 12px;
	text-transform: uppercase;
	letter-spacing: .04em;
	margin-right: 4px;
}

.entry-more {
	font-size: 13px;
}

/* What the identity numbers did, and where a missing factor's weight went.
   Read once under the table that raised the question. */
.breakdown-note {
	margin: var(--space) 0 0;
	color: var(--ink-2);
	font-size: 13px;
}

/* Where the written comparison lands, in place of the button that asked for it.
   The box holds a paragraph and three lists the view draws whether there is an
   answer or not, so it is never empty and cannot lean on :empty for its
   collapse. It carries the hidden attribute until there is something in it, and
   the reset in this file answers that with display: none. */
.hit-note {
	margin: calc(var(--space) * 2) 0;
	color: var(--ink-2);
}

.hit-note.is-error {
	color: var(--danger);
}

.hit-note-text {
	margin: 0;
}

/* The field by field half of the note: what agrees, what differs, and what only
   one of the two records carries. Each is a short list under its own name,
   because a reviewer reads them against the breakdown above and a run-on
   paragraph of the same facts cannot be checked line by line. */
.hit-note-points {
	margin-top: var(--space);
}

.hit-note-points ul {
	margin: 4px 0 0;
	padding-left: calc(var(--space) * 2);
}

.hit-note-label {
	font-weight: 600;
}

/* A label for a group that is not a control: the two scores, the weight total,
   a checklist of scopes. It had TWO NAMES until this pass, .field-legend here
   and .field-label on the platform desk, and the second had no rule at all, so
   the heading over the fourteen API scopes on an organisation's page was
   drawing as body text. Both spellings are now this one.
   It reads as a field label because it is one, but it names a reading rather
   than an input and so cannot be a label element. */
.field-legend {
	font-weight: 600;
}

/* One identity number and the two fields that qualify it, side by side on a
   wide screen, because the three are read as one row. */
/*
 * ONE IDENTITY NUMBER AND THE TWO FIELDS THAT QUALIFY IT, as a band reaching
 * both rules of the card it sits in.
 *
 * The negative margin is the card's own padding given back, so the row's RULE
 * runs the full width while the row's TEXT starts on the same left edge as the
 * heading above it. The card used to be .card-tight for this, which reached the
 * rule by standing the heading against it as well.
 */
.identifier-row {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 0 calc(var(--space) * 3);
	margin: 0 calc(var(--space) * -2);
	padding: var(--space) calc(var(--space) * 2);
	border-bottom: 1px solid var(--line-soft);
}

/* The card's own rule is directly under the last row, so a rule of its own
   would be two lines a pixel apart. The same thing .table already says with
   tbody tr:last-child. */
.identifier-row:last-child {
	border-bottom: 0;
}

@media (min-width: 760px) {
	.identifier-row {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

.identifier-row > * {
	min-width: 0;
}

/*
 * WHAT IXXO HUB SENT, AND WHAT IS ON THE RECORD NOW.
 *
 * These four classes were in the markup with no rule anywhere behind them, so
 * a proposal drew as two identical cells and a reader comparing them had only
 * their position to go on. The whole act on this panel is a comparison, so the
 * two sides are told apart the way this product tells anything apart: the value
 * being replaced is set in the caption ink, the value proposed is set in the
 * reading ink, and neither depends on a colour to say which is which.
 */
.hub-from {
	color: var(--ink-3);
}

.hub-to {
	color: var(--ink);
	font-weight: 600;
}

/* What Hub proposes, ruled off from the sentence explaining the section. The
   script reaches this block through its data attribute, so the class beside it
   had no rule and the proposal ran straight on from the prose above it. */
.hub-proposal {
	margin-top: calc(var(--space) * 2);
	padding-top: calc(var(--space) * 2);
	border-top: 1px solid var(--line-soft);
}

/* An answer this product has no field for. It is kept rather than discarded, so
   it is ruled off from the proposal above it rather than run on. */
.hub-unmapped {
	margin-top: calc(var(--space) * 3);
	padding-top: calc(var(--space) * 2);
	border-top: 1px solid var(--line-soft);
}

/* A label and its value where the label is somebody else's field name rather
   than a word this product owns, so it is given room to be long. */
.detail-list {
	display: grid;
	grid-template-columns: minmax(0, 14rem) minmax(0, 1fr);
	align-items: baseline;
	gap: 4px calc(var(--space) * 2);
}

.detail-list dt {
	color: var(--ink-3);
	font-size: 13px;
	overflow-wrap: anywhere;
}

@media (max-width: 640px) {
	.detail-list {
		grid-template-columns: minmax(0, 1fr);
	}

	.detail-list dd {
		margin-bottom: var(--space);
	}
}

/* ---------- Adverse media ---------- */

/* The three readings under the headline: what kind of thing it is, how relevant
   the assistant thought it was, and how it reads. They are a second line of the
   same head rather than a new block, so they sit tight under the first. */
.media-facts {
	margin-top: -2px;
}

/*
 * WHAT THE ASSISTANT SET ASIDE, DRAWN AS SOMETHING SET ASIDE.
 *
 * There was no rule for this class, so the articles the assistant thought were
 * about somebody else drew exactly like the ones it thought were about this
 * party: same weight, same ink, same everything, under a heading somebody has
 * to have read to know the difference. That is the one thing this design
 * document says a status may never be, a difference the reader has to hunt for.
 *
 * It is quieted and not hidden, and the heading above it says in words what it
 * is, because setting something aside is a guess about a name rather than a
 * decision anybody made, and the person holding the passport is the one who
 * gets to overrule it.
 */
.media-set-aside .media-row,
.media-set-aside .media-row .alert-name {
	color: var(--ink-2);
}

.media-set-aside .media-row {
	border-left: 3px solid var(--line-soft);
}

/* A finding somebody has already decided about stops competing with the ones
   nobody has looked at. The chip beside the headline still carries the word, so
   this is never the only thing saying what happened. */
.media-row.is-dismissed .alert-name {
	color: var(--ink-2);
	font-weight: 400;
}

/* One provider's answer, ruled off from the next one's. Two providers running
   together with nothing between them read as one list from one source, which is
   the one thing a second opinion must never look like. */
.second-opinion-provider + .second-opinion-provider {
	margin-top: calc(var(--space) * 3);
	padding-top: calc(var(--space) * 2);
	border-top: 1px solid var(--line-soft);
}

/* ---------- Ownership ---------- */

/* The map and the editor side by side, because a fault seen in the picture is
   corrected in the form beside it rather than on another screen. They stack
   under 900px, the same breakpoint the navigation rail collapses at: two
   columns of a structure diagram on a phone is two columns of nothing. */
.ownership-frame {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: calc(var(--space) * 3);
	align-items: start;
	margin-bottom: calc(var(--space) * 3);
}

@media (max-width: 900px) {
	.ownership-frame {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* The editor is a record section rather than a card, so it keeps the spacing
   it has on the record page and the two screens draw the same form. */
.ownership-editor > .record-section {
	margin-bottom: 0;
}

.ownership-map {
	overflow-x: auto;
}

/* Drawn at its natural size, with the box around it scrolling to reach the rest
   of it.

   max-width: 100% here defeated that container's own overflow: a structure
   thirty one boxes wide was squeezed into the column rather than overflowing
   it, which drew the whole map twenty six pixels tall and every name in it as a
   smear. min-width is what stops a layout shrinking a replaced element to fit,
   and it is min-content rather than a percentage on purpose: a percentage would
   stretch a two box structure to the width of the column, which is the opposite
   fault and is the one this rule was written for in the first place. */
.own-svg {
	display: block;
	min-width: min-content;
	margin: 0 auto;
}

/* A box is a record, so it is drawn as paper on the desk: the white surface,
   the one rule weight this product has around it, and nothing else. The shape
   carries the meaning rather than the colour, so a map read in print or by
   somebody who cannot tell two hues apart still says which box is a person. */
.own-node rect {
	fill: var(--surface);
	stroke: var(--line);
	stroke-width: 1;
}

.own-node {
	cursor: pointer;
}

.own-node:focus {
	outline: none;
}

.own-node:focus rect {
	stroke: var(--accent);
	stroke-width: 2;
}

/* A beneficial owner is the answer this whole page exists to give, so its box
   is the one drawn heavier. Green rather than the accent, because the accent
   belongs to the primary action, the focus ring and selection alone, and
   because the table under the map stamps the same party with badge-ok. */
.own-node-ubo rect {
	stroke: var(--ok);
	stroke-width: 2;
}

/* Anything with an open question against it wears the warning colour, which is
   the colour the question itself is written in further down the page. It comes
   after the beneficial owner rule on purpose: a beneficial owner with no
   evidence on file is a question first. */
.own-node-alert rect {
	stroke: var(--warn);
	stroke-width: 2;
}

.own-node-name {
	fill: var(--ink);
	font-family: var(--font);
	font-size: 12px;
	text-anchor: middle;
}

.own-node-meta {
	fill: var(--ink-3);
	font-family: var(--font);
	font-size: 11px;
	text-anchor: middle;
}

.own-edge {
	fill: none;
	stroke: var(--ink-3);
	stroke-width: 1;
}

/* A link with no document behind it is drawn in the warning colour, because a
   shareholding nobody has seen a paper for is the commonest thing wrong with a
   structure, and the map is where it should be visible before it is read. */
.own-edge-unverified {
	stroke: var(--warn);
}

/* A link that has ended is dashed. The walk still counts one that ends today,
   so a reader has to be able to see which line is on its last day. */
.own-edge-ended {
	stroke-dasharray: 4 3;
}

/* The link that closes a circle, which is the fault itself rather than a line
   with something wrong about it: it is the heaviest line on the map, in the
   colour the question about it is written in below, and it is the only one that
   carries an arrow, because it is the only one that does not run downwards. */
.own-edge-cycle {
	stroke: var(--warn);
	stroke-width: 2;
}

.own-arrow {
	fill: var(--warn);
}

.own-edge-label-cycle {
	fill: var(--warn);
}

.own-edge-label {
	fill: var(--ink-2);
	font-family: var(--font);
	font-size: 11px;
	text-anchor: middle;
	font-variant-numeric: tabular-nums;
}

/* What the map says about itself: one line, in words, under the picture. */
.own-note {
	margin: var(--space) 0 0;
	color: var(--ink-2);
}

.own-note-warn {
	color: var(--warn);
}

.own-answer {
	margin: var(--space) 0 0;
	color: var(--ink-2);
}

.own-answer.is-error {
	color: var(--danger);
}

/* The key to the shapes, on one line while it fits. */
.own-legend {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space) calc(var(--space) * 2);
	margin: calc(var(--space) * 2) 0 0;
	padding: 0;
	list-style: none;
	color: var(--ink-3);
	font-size: 12px;
}

.own-legend li {
	display: flex;
	align-items: center;
	gap: calc(var(--space) / 2);
}

.own-key {
	display: inline-block;
	width: 18px;
	height: 12px;
	background: var(--surface);
	border: 1px solid var(--line);
}

.own-key-company {
	border-radius: 2px;
}

.own-key-person {
	border-radius: 6px;
}

.own-key-ubo {
	border: 2px solid var(--ok);
	border-radius: 6px;
}

.own-key-alert {
	border: 2px solid var(--warn);
	border-radius: 2px;
}

/* A line rather than a box, so the key for an edge looks like an edge. */
.own-key-unverified {
	height: 0;
	border: 0;
	border-top: 2px solid var(--warn);
}

/* What the register on a record page adds up to, printed under the links it
   was counted from. It is a sentence rather than a stamp, because a total over
   a hundred is recorded and never refused: the colour is the whole of the
   warning, and it is the colour the open question about the same fault is
   written in further down the page. */
.declared-total {
	margin: var(--space) 0 0;
	color: var(--ink-2);
}

.declared-total-warn {
	color: var(--warn);
}

/* The beneficial owners in the drawer: the name, the share and the count of
   anything open against them, on one line while the three of them fit. It is
   keyed to that list by name and not to the drawer it sits in: written as
   .drawer-panel .plain-list li it laid out every list any fragment drops into
   a drawer, on both shells, including the audit row the platform desk opens
   there, and none of those is three things meant to sit on one line. */
.ubo-list li {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: calc(var(--space) / 2);
}

/* ---------- Print ---------- */

/* A printed record is evidence, so the furniture goes and the content stays. */
@media print {
	.nav,
	.topbar,
	.btn {
		display: none;
	}

	body {
		background: #ffffff;
	}

	.card,
	.table-wrap {
		border: 0;
	}

	/*
	 * A FOLDED DISCLOSURE IS STILL EVIDENCE, so it is forced open on paper.
	 *
	 * A task's explanation, an evidence snapshot and the before and after of a
	 * timeline row are all folded on screen, because a case read for what
	 * happened would otherwise be buried in detail. On paper there is nothing to
	 * press, so a folded panel is content that went missing from the record. The
	 * class sits on exactly those panels rather than on every [hidden] element,
	 * because a form drawn hidden until somebody asks for it is not evidence and
	 * must not appear at all.
	 */
	.case-fold[hidden] {
		display: block !important;
	}

	/*
	 * Nothing that is a control prints. A form on paper is a set of empty boxes
	 * where a reader expects an answer, and the drawer, the flash line and the
	 * (i) buttons are furniture of the same kind as the navigation above.
	 */
	form,
	.drawer,
	.flash,
	.field-info {
		display: none;
	}

	/*
	 * Every section starts on a page it can finish. A section longer than a page
	 * still breaks, which is the browser's own fallback and the right one: the
	 * alternative is a timeline of three hundred rows squeezed onto one sheet.
	 */
	.record-section {
		break-inside: avoid;
		page-break-inside: avoid;
		border: 0;
		padding: 0;
	}

	.section-head h2 {
		break-after: avoid;
		page-break-after: avoid;
	}

	/*
	 * The coverage set is one column on paper. Two columns fit at print width
	 * and would put a list name and the version label that names it either side
	 * of a column break, which is the one pairing on this page that must never
	 * be separated: the version label is the whole of the evidence.
	 */
	.coverage {
		grid-template-columns: minmax(0, 1fr);
	}

	/* The figures print beside the name, exactly as they read on screen, so
	   what an auditor is handed carries the policy version and the cost of the
	   run without turning the page. */
	.figure-set {
		background: none;
		border: 1px solid var(--line);
	}
}

/* ---------- Overflow guards ---------- */

/* Nothing may pan the page sideways. A flex or grid child defaults to a
   minimum width of its content, which is how one long reference number in one
   cell moves the whole layout. */
img,
svg {
	max-width: 100%;
}

pre {
	overflow-x: auto;
}

.shell > *,
.topbar > *,
.hit-head > *,
.field > *,
.queue-item > *,
.kv > *,
.btn-row > *,
.ownership-frame > *,
.page-actions > * {
	min-width: 0;
}

/* ---------- Risk: the band, the factor table and the band strip ---------- */

/*
 * FOUR BANDS, FOUR COLOURS FROM THE PALETTE AND NOT ONE NEW VALUE.
 * docs/DESIGN.md decided --ok, --warn and --danger in 0.1.0 and nothing outside
 * tokens.css may invent a colour. Low is ok, very high is danger, high is warn,
 * and medium takes the ordinary ink: a medium band is the commonest answer on a
 * real book of clients, and a fourth colour shouting it would leave nothing
 * louder for the two that matter. Unassessed keeps the plain badge, because it
 * is the absence of an answer rather than a quiet one.
 */
.risk-band {
	font-variant-caps: all-small-caps;
	letter-spacing: 0.04em;
}

.risk-band-low {
	border-color: var(--ok);
	background: var(--surface);
	color: var(--ok);
}

.risk-band-medium {
	border-color: var(--ink-3);
	background: var(--surface);
	color: var(--ink-2);
}

.risk-band-high {
	border-color: var(--warn);
	background: var(--surface);
	color: var(--warn);
}

.risk-band-very-high {
	border-color: var(--danger);
	background: var(--danger);
	color: var(--on-accent);
}

/*
 * The band, the score and the two screening statuses on one line. They are one
 * block on purpose: spec 7.1 says a sanctions position and a politically
 * exposed person are facts printed BESIDE the band and at the same size, and a
 * rule that let one of them drop to a second row would be the layout quietly
 * taking that back. An (i) panel opened inside it takes a whole row of its own.
 */
.risk-head {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space);
	margin-bottom: var(--space);
}

.risk-head > * {
	min-width: 0;
}

.risk-head > .field-help {
	flex-basis: 100%;
}

.risk-score {
	font-family: var(--font-display);
	font-size: 22px;
	line-height: 1;
}

.risk-score-of {
	margin-left: 6px;
	color: var(--ink-3);
	font-family: var(--font);
	font-size: 12px;
}

.risk-computed,
.risk-floors {
	font-size: 13px;
}

/* The factor that set its category, marked by the one wash of the accent the
   design system already uses for a selected row. */
.risk-driver > td {
	background: var(--info-bg);
}

/*
 * THE CONTRIBUTION BAR. It is a percentage of the whole hundred point scale and
 * never of the widest row on this record, so two assessments read against each
 * other mean the same thing. It sits under the number rather than instead of
 * it: the bar is for seeing where a score came from without reading six
 * figures, and the figure is still the answer.
 */
.contribution-bar {
	display: block;
	width: 100%;
	height: 3px;
	margin-top: 4px;
	background: var(--surface-2);
	border-radius: 2px;
}

.contribution-bar::before {
	content: "";
	display: block;
	width: var(--w, 0%);
	height: 100%;
	background: var(--accent);
	border-radius: 2px;
}

/* The read-only factor tables on the policy page: a value and its points, dense,
   two columns, so nineteen tables can be read rather than scrolled. */
.risk-table {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 2px calc(var(--space) * 2);
	font-size: 13px;
}

.risk-table li {
	display: flex;
	justify-content: space-between;
	gap: var(--space);
	border-bottom: 1px dotted var(--line);
}

/*
 * The band strip on the policy editor: the four ranges the three thresholds
 * make, redrawn as they are typed. It is a picture of what has been typed and
 * never the check, which is why a band with no room left in it is marked rather
 * than removed.
 */
.band-strip {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 4px;
	width: 100%;
}

.band-strip-seg {
	display: flex;
	flex-direction: column;
	gap: 4px;
	padding: var(--space) 10px;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	font-size: 12px;
}

.band-strip-seg.is-error {
	border-color: var(--danger);
	color: var(--danger);
}

/*
 * The five band counts on the home page. Explicit columns rather than auto-fit,
 * so five counts are five columns and never four and a gap.
 */
.band-counts {
	display: grid;
	grid-template-columns: repeat(5, minmax(0, 1fr));
	gap: var(--space);
	margin: 0 0 var(--space);
	padding: 0;
	list-style: none;
}

.band-count {
	display: flex;
	flex-direction: column;
	gap: 4px;
	padding: calc(var(--space) * 1.5) calc(var(--space) * 2);
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	color: var(--ink-2);
	text-decoration: none;
	transition: border-color .13s ease;
}

.band-count strong {
	font-family: var(--font-display);
	font-size: 24px;
	line-height: 1;
	color: var(--ink);
}

.band-count:hover {
	border-color: var(--accent);
}

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

/*
 * FIVE TILES ON ONE ROW ARE ONE OBJECT, SO THEY ARE DRAWN ONE WAY.
 *
 * The very high tile used to be a solid block of red beside four plain outlined
 * boxes. .risk-band-very-high sets a background and the three bands beside it
 * set only a border-colour and an ink, both of which lose to .band-count's own
 * shorthand further down the file, so exactly one of the five was painted and
 * the other four were not. One figure shouted and the rest whispered for no
 * reason a reader could infer, which is the worst thing a row of counts can do:
 * it says the loud one is the answer and the quiet ones are furniture, when all
 * five are the same fact about five different bands.
 *
 * So all four bands are washes of the same weight and the scale carries the
 * meaning: ok for low, ordinary paper for medium, amber for high, red for very
 * high, and the plain tile for a band nobody has assessed. The tint is never
 * the only thing said: every tile is a NUMBER above the BAND'S OWN NAME, and a
 * reader who cannot separate the amber from the red reads the two words and
 * loses nothing.
 *
 * Two classes rather than one, so this beats .band-count without depending on
 * where either rule sits in the file. The band class reaches a tile only where
 * the count is above nought, which app/public/views/app/home.php decides, so
 * nothing here paints an empty band and the row stays quiet on an ordinary
 * morning.
 */
.band-count.risk-band-low {
	background: var(--ok-bg);
	border-color: var(--ok-line);
	color: var(--ok);
}

.band-count.risk-band-medium {
	background: var(--sunk);
	border-color: var(--line);
	color: var(--ink-2);
}

.band-count.risk-band-high {
	background: var(--warn-bg);
	border-color: var(--warn-line);
	color: var(--warn);
}

.band-count.risk-band-very-high {
	background: var(--danger-bg);
	border-color: var(--danger-line);
	color: var(--danger);
}

/* The figure wears the band's own colour with it. Without this the number
   inside a washed tile stayed in .band-count strong's ordinary ink, which is
   the same collision the solid red tile had, one layer down. */
.band-count.risk-band-low strong,
.band-count.risk-band-high strong,
.band-count.risk-band-very-high strong {
	color: inherit;
}

/* The form that replaces the computed band, which opens under the two buttons
   that offer it. It is ruled off from them, because a form appearing with
   nothing between it and the control that opened it reads as part of that
   control. The script finds it by its data attribute, which is why the class
   had no rule of its own. */
.risk-override {
	margin-top: calc(var(--space) * 2);
	padding-top: calc(var(--space) * 2);
	border-top: 1px solid var(--line-soft);
}

/* The four bands as a choice, on the override form. */
.risk-bands {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--space) * 2);
	width: 100%;
}

.risk-band-choice {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	cursor: pointer;
}

@media (max-width: 760px) {
	/* Two columns rather than five. A count squeezed to the width of its own
	   border is a number nobody can read. */
	.band-counts {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}

	.band-strip,
	.risk-table {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* ---------- The subject's secure link page ---------- */

/*
 * A single column on warm paper, wider than the sign-in card and narrower than
 * the workspace, because the reader is filling a form in rather than glancing at
 * a list. It borrows the tokens and nothing else: this page never draws a menu,
 * a table or a badge, so it has no rules for any of them.
 *
 * IT IS DRAWN FOR A PHONE FIRST AND WIDENED AFTERWARDS, WHICH IS THE OPPOSITE
 * OF EVERY OTHER SURFACE HERE, and the reason is who is holding it. Everywhere
 * else the reader is a compliance officer at a desk with a mouse and a keyboard
 * and thirty fields on screen. Here the reader is a client of the firm, who did
 * not choose this software, who is standing up, and who is being asked to
 * photograph a passport. They will do that on a phone, and this is the only
 * page in the product wearing the firm's own name rather than IXXO's: a
 * stranger judges an accountant by it.
 *
 * SIXTEEN PIXELS AND NOT FOURTEEN. The interface scale is small because these
 * screens are dense; this page is not an interface, it is four paragraphs a
 * stranger has to read and believe before they hand over an identity document,
 * and prose read at arm's length on a phone is read at sixteen. The measure
 * stays where it was, so the line length is unchanged and only the size moves.
 */
.linkpage-body {
	font-size: 16px;
	padding: calc(var(--space) * 3) calc(var(--space) * 2);
}

.linkpage {
	width: 100%;
	max-width: 620px;
	margin: 0 auto;
	display: flex;
	flex-direction: column;
	gap: calc(var(--space) * 2);
}

.linkpage-brand {
	margin: 0;
	font-family: var(--font-display);
	font-size: 22px;
	font-weight: 600;
	letter-spacing: 0.01em;
}

.linkpage-card {
	padding: calc(var(--space) * 3);
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius);
}

.linkpage-card h1 {
	font-family: var(--font-display);
	font-size: 20px;
	font-weight: 600;
	margin-bottom: var(--space);
}

.linkpage-card p {
	color: var(--ink-2);
	max-width: 60ch;
}

.linkpage-card p + p {
	margin-top: var(--space);
}

/* What the officer wrote, set apart from what the product says, so a reader can
   tell a person's sentence from a template. */
.linkpage-message {
	margin-top: calc(var(--space) * 2);
	padding-left: calc(var(--space) * 2);
	border-left: 2px solid var(--line);
	white-space: normal;
}

/* One line per item the server answered on the last submit. Accepted and
   refused are told apart by a word and by a colour on the rule, never by colour
   alone. */
.linkpage-notes {
	margin: 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: var(--space);
}

.linkpage-notes li {
	padding: var(--space) calc(var(--space) * 2);
	background: var(--surface);
	border: 1px solid var(--line);
	border-left-width: 3px;
	border-radius: var(--radius);
	color: var(--ink-2);
}

.linkpage-notes li.is-ok {
	border-left-color: var(--ok);
}

.linkpage-notes li.is-bad {
	border-left-color: var(--danger);
}

.linkpage-item {
	padding-top: calc(var(--space) * 3);
	border-top: 1px solid var(--line);
}

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

.linkpage-item + .linkpage-item {
	margin-top: calc(var(--space) * 3);
}

.linkpage-item h2 {
	font-size: 15px;
	font-weight: 600;
	margin-bottom: 4px;
}

.linkpage-tick {
	margin-left: var(--space);
	font-size: 12px;
	font-weight: 600;
	color: var(--ok);
	text-transform: none;
}

.linkpage-detail {
	color: var(--ink-2);
	max-width: 60ch;
	margin-bottom: calc(var(--space) * 2);
}

/* What has already arrived, by name. It is never a link: a client who uploaded
   a passport must not be able to read it back from a page reachable by whoever
   holds the address. */
.linkpage-have {
	margin: 0 0 calc(var(--space) * 2);
	padding-left: 18px;
	color: var(--ink-2);
}

.linkpage-item .field {
	max-width: none;
	margin-bottom: calc(var(--space) * 2);
}

/* The control this page is FOR. On a phone its own button is what opens the
   camera, so it takes the whole width rather than being sized to the name of a
   file nobody has chosen yet. */
.linkpage-item input[type="file"] {
	width: 100%;
	max-width: 100%;
}

.linkpage-choice {
	display: flex;
	gap: var(--space) calc(var(--space) * 3);
	flex-wrap: wrap;
}

/* A yes or no is two tap targets, and a 22px stamp is not one. The label is
   stretched to the full control height so the finger gets the whole word, the
   box and the room around both, which is what a label was always for and what
   nothing was giving it space to be. */
.linkpage-choice label {
	display: inline-flex;
	align-items: center;
	min-height: var(--control-h);
	padding-right: var(--space);
	gap: var(--space);
	cursor: pointer;
}

.linkpage-actions {
	display: flex;
	align-items: center;
	gap: calc(var(--space) * 2);
	flex-wrap: wrap;
	margin-top: calc(var(--space) * 3);
	padding-top: calc(var(--space) * 3);
	border-top: 1px solid var(--line);
}

.linkpage-actions .btn-primary {
	padding: 7px 16px;
}

/*
 * THE PHONE, WHICH IS WHERE THIS PAGE ACTUALLY LIVES.
 *
 * The card gives back a step of padding, because three steps inside two steps
 * of body gutter is a fifth of a 360px screen. The submit goes full width and
 * the sentence beside it goes underneath: there is one thing to do on this page
 * and it is the last thing on it, and a client who has just photographed a
 * passport should not have to aim.
 *
 * The file chooser is the control this page is FOR, so it is given the whole
 * width rather than sized to the name of a file nobody has chosen yet. On a
 * phone its own button is what opens the camera.
 *
 * A yes or no is two tap targets, and a 22px stamp is not one. The label around
 * each is stretched to the full control height so the finger has the whole
 * word, the box and the space around both, which is what the label was always
 * for and what nothing was giving it room to be.
 */
@media (max-width: 640px) {
	.linkpage-card {
		padding: calc(var(--space) * 2);
	}

	.linkpage-actions {
		flex-direction: column;
		align-items: stretch;
		gap: var(--space);
	}

	.linkpage-actions .btn-primary {
		width: 100%;
	}
}

/*
 * AN ITEM THAT HAS ALREADY ARRIVED STOPS ASKING FOR ATTENTION.
 *
 * There was no rule for this class anywhere, so a request with four items of
 * which three were already received drew four identical blocks, and the one
 * thing the client still had to do looked exactly like the three they had
 * finished. The word Received was there, in green, at the end of a heading,
 * which is precisely the kind of difference a reader has to hunt for.
 *
 * It is quieted rather than folded away or removed. A client has to be able to
 * see that what they sent last week is still recorded as sent, or they send it
 * again; and the tick keeps its own word beside the heading, so this is never
 * the only thing carrying the state.
 */
.linkpage-item.is-done > h2,
.linkpage-item.is-done .linkpage-detail {
	color: var(--ink-3);
}

.linkpage-note,
.linkpage-foot {
	font-size: 13px;
	color: var(--ink-3);
	max-width: 60ch;
}

.linkpage-foot {
	margin: 0;
	text-align: center;
}

/* ---------- Cases: the head, the checklist, the requests, the timeline ---------- */

/*
 * THE CASE HEAD. The client's name is the one voice above the fold, so the title
 * block and the two export buttons sit on one line and the buttons give way
 * first: a name is what the page is about and a button is what a reader does
 * next, in that order.
 */
.case-head {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--space);
	margin-bottom: calc(var(--space) * 2);
}

.case-head > * {
	min-width: 0;
}

.case-head .page-title {
	margin-bottom: 0;
}

/*
 * What a decided case did, set off from the facts above it by the one rule
 * weight this product has. It is not a banner and carries no colour: a decision
 * is the quietest and the most important thing on the page, and a coloured box
 * around it would make it read as a warning about itself.
 */
.case-decided {
	margin-top: calc(var(--space) * 2);
	padding-top: calc(var(--space) * 2);
	border-top: 1px solid var(--line);
}

/* The two selects that arrange the work, side by side while they fit. They are
   controls rather than facts, so they sit under the facts and never among them. */
.case-controls {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: 0 calc(var(--space) * 3);
	margin-top: calc(var(--space) * 2);
	padding-top: calc(var(--space) * 2);
	border-top: 1px solid var(--line);
}

.case-controls > * {
	min-width: 0;
}

.case-controls .field {
	min-width: 14rem;
}

/* One task, and the line it is read on: the tick, the title, the state, the
   disclosure. Everything wraps rather than truncates, because a checklist item
   cut off in the middle is a checklist item nobody can act on. */
.case-task {
	padding: var(--space) 0;
}

.case-task-line {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--space);
}

.case-task-line > * {
	min-width: 0;
}

.case-task-title {
	font-weight: 600;
	overflow-wrap: anywhere;
}

/* A finished line stays legible and stops competing. Not struck through: a task
   set aside as not applicable was never done, and one typeface trick cannot mean
   both things at once. */
.case-task-done .case-task-title {
	font-weight: 400;
	color: var(--ink-2);
}

.case-task-detail {
	max-width: 68ch;
	margin: 4px 0 0;
	color: var(--ink-2);
}

.case-request,
.case-evidence {
	padding: var(--space) 0;
}

/* A snapshot that failed its own fingerprint. It is the warning colour and not
   the danger one: nothing has been lost, and what the sentence says is that this
   copy cannot be shown as evidence until somebody looks at why. */
.case-evidence-broken {
	max-width: 68ch;
	color: var(--warn);
}

/*
 * THE ONE-TIME ADDRESS. It is the pale accent wash the (i) panel uses, which is
 * the only tone in the system that says "read this now" without saying anything
 * is wrong, and it is the only place in the workspace a credential is ever
 * printed. It breaks anywhere, because a 64 character token in a fixed width
 * face would otherwise pan the page sideways.
 */
.case-link-panel {
	margin-bottom: calc(var(--space) * 2);
	padding: calc(var(--space) * 2);
	background: var(--info-bg);
	border-radius: var(--radius);
}

.case-link-url {
	overflow-wrap: anywhere;
	word-break: break-all;
}

/* The three decisions, read as one group. They wrap on a narrow window rather
   than shrinking, because the word on the button is the whole of the choice. */
.case-decisions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space);
}

/*
 * A line the client wrote, from outside the workspace. The left rule is the same
 * 3px mark the exception queue uses for a level, in the accent rather than in a
 * warning colour: it is not a fault, it is a different author, and that is the
 * one thing about these rows a reader must not miss.
 */
.case-timeline-outside td:first-child {
	border-left: 3px solid var(--accent);
}

/* The count on the home page that leads, in the accent, because it is the one
   the reader can act on first. Nothing else on that page borrows the accent. It
   is a wash on the same rule as the four bands, so a row holding a lead and a
   band is a row of one kind of tile. */
.band-count-lead {
	background: var(--info-bg);
	border-color: var(--accent);
	color: var(--accent);
}

.band-count-lead strong {
	color: inherit;
}

/* The print view has no shell, so the page gives itself the gutter the shell
   would otherwise have given it. */
.print-page {
	padding: calc(var(--space) * 3) 0;
}

/*
 * THE LETTERHEAD ON A DOCUMENT THAT LEAVES THE FIRM.
 *
 * The firm's own name sits above the case and is ruled off from it, because a
 * reader outside the workspace has to know whose file this is before they read a
 * word of what is in it. It is set in the display face and above the heading,
 * which is what a letterhead is: the heading says what the document is, and this
 * says who wrote it.
 *
 * The registered name under it is quieter and is drawn only where it differs
 * from the trading name, so a firm that has set no brand at all reads one line
 * rather than the same name twice.
 */
.print-masthead {
	border-bottom: 1px solid var(--line);
	margin-bottom: calc(var(--space) * 2);
	padding-bottom: var(--space);
}

.print-brand {
	font-family: var(--font-display);
	font-size: 1.4rem;
	font-weight: 600;
	margin: 0;
}

.print-registered {
	color: var(--ink-3);
	font-size: 0.85rem;
	margin: calc(var(--space) / 4) 0 0;
}

/* ---------- Monitoring ---------- */

/*
 * The eight kinds of alert and the two counts on the home page, in the same
 * row-of-numbers component the bands already use. The column count is written
 * out for each rather than left to auto-fit, because auto-fit leaves a half
 * empty last row whose gap reads as a missing number.
 */
.band-counts-4 {
	grid-template-columns: repeat(4, minmax(0, 1fr));
}

.band-counts-2 {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

/*
 * One alert is a sentence with a few facts above it, not a row of columns:
 * nobody compares two alerts down a column, they read one and decide. So it is
 * a list, and the two presses sit at the end of the line where the eye finishes
 * rather than in a column of their own.
 */
.alert-list {
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * THE ROW HAS AIR IN IT, AND THE TWO CHIPS ARE WHAT IT IS TRIAGED ON.
 *
 * Eight pixels of padding and a full weight rule made every alert the same
 * height as the sentence inside it and the whole list one grey block: the
 * morning's work read as a wall of text with two grey badges on each line. Two
 * things fix it and neither is decoration. The row gets sixteen pixels top and
 * bottom, which is the rhythm that lets an eye find where one alert ends and
 * the next begins without reading either. And the rule between two rows drops
 * to --line-soft, because a list of sentences ruled at full weight is a table,
 * and these are not columns anybody compares down.
 *
 * The chips carry the two questions a reader actually triages on: what kind of
 * thing happened, and whether anybody has it. Both are words first and tints
 * second (see the badge block above), so nothing here depends on separating a
 * red from a green.
 */
.alert-row {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--space);
	padding: calc(var(--space) * 2) 0;
	border-bottom: 1px solid var(--line-soft);
}

.alert-row:last-child {
	border-bottom: 0;
}

.alert-body {
	min-width: 0;
	flex: 1 1 24em;
}

/* The name, the two chips and the moment. The name is the thing being looked
   for, so it leads and it is the only part in the ordinary ink weight. */
.alert-head {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space);
	margin-bottom: 6px;
}

.alert-name {
	font-weight: 600;
}

/* When it was raised, at the end of the line rather than among the chips: it is
   the one fact on the row nobody scans for. */
.alert-when {
	margin-left: auto;
	font-size: 12px;
	font-variant-numeric: tabular-nums;
}

/* The row supplies its own rhythm now, so the sentence inside it stops
   supplying a second one on top. */
.alert-row .row-note {
	margin-bottom: 0;
}

/* The three ways out of one alert: the record, the screening it came from and
   the case it opened. Small, because they are where to go next rather than what
   happened. */
.alert-links {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--space) * 2);
	margin-top: var(--space);
	font-size: 12px;
}

.alert-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}

/*
 * When each kind of run last finished. A list of lines rather than a table,
 * because the six rows have nothing to compare down a column and each carries a
 * sentence of its own explaining what that run is for.
 */
.run-strip {
	margin: 0;
	padding: 0;
	list-style: none;
}

.run-line {
	padding: calc(var(--space) * 1.5) 0;
	border-bottom: 1px solid var(--line-soft);
}

.run-line:last-child {
	border-bottom: 0;
}

.run-kind {
	font-weight: 600;
}

/*
 * A failure, named. It is the warning amber and not the danger red: red in this
 * product is the destructive button and the urgent queue row, and a run that
 * could not compare two records out of four hundred is a thing to look at rather
 * than an emergency. A page that shouted in red every morning would teach a
 * reader to stop seeing red on the morning it mattered.
 */
.run-failed {
	color: var(--warn);
	font-weight: 600;
}

/* The frequency chooser inside a table cell. Sized to its own longest word
   rather than to the column, because a control stretched across a cell reads as
   a text field somebody should type in. */
.freq-select {
	max-width: 18em;
}

@media (max-width: 760px) {
	/* Two columns rather than four. A count squeezed to the width of its own
	   border is a number nobody can read. */
	.band-counts-4 {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/* THE OUTSTANDING WORK, ARRANGED RATHER THAN RANKED.

   Most rows of the arrangement are one band at full width. One row is two bands
   SIDE BY SIDE, and the whole point of it is that neither reads as the loser:
   "a question this firm never asked" and "a date this firm has already passed"
   are two kinds of failure rather than two rungs of one ladder, and the owner
   settled that himself. So the two columns are one grid of equal fractions, and
   nothing about either one is drawn heavier than the other: same heading, same
   rule above it, same type, same spacing.

   Mobile first, and the collapse is the reason the heading is not optional.
   Below 760px the two stack, and a stack puts one of them first; a reader on a
   phone must be able to see that they are two named groups rather than one long
   list, so the heading is drawn at every width and never folded into the first
   row. */
.na-row {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: calc(var(--space) * 2);
}

@media (min-width: 760px) {
	.na-row-split {
		grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	}
}

.na-row > * {
	min-width: 0;
}

/* The heading of one column. It carries a rule above it rather than a size, so
   the two peers cannot end up in different weights the day somebody restyles a
   heading level. */
.na-head {
	border-top: 1px solid var(--line);
	padding-top: var(--space);
	margin: 0 0 var(--space) 0;
	font-size: 1em;
	font-weight: 600;
}

/* The sentence under a heading that says what the band means, and the one that
   says a column is empty. Both are the quiet weight, because they explain the
   list rather than being part of it. */
.na-note {
	color: var(--ink-3);
	margin: calc(var(--space) * -0.5) 0 var(--space) 0;
}

/* ---------- The desk's trend ---------- */

/*
 * THIRTY DAYS OF SCREENINGS ON THE PLATFORM DESK, DRAWN AS INLINE SVG.
 *
 * No library and no external request, for the reason nothing here fetches a
 * font either: this product is installed on a firm's own server and its screens
 * are about sanctioned and politically exposed people, so a request to a third
 * party would tell that party which pages a compliance officer opened and when.
 * A chart of how much work an installation is doing is the last thing to send
 * anywhere.
 *
 * THE COLOURS ARE HERE AND NOT IN THE MARKUP, which is the whole reason the
 * shapes carry classes. An SVG presentation attribute cannot read a custom
 * property, so a fill written in the view would be a hex code in a view, and
 * this design has exactly one place a colour may be written down.
 */
.trend {
	margin: 0;
}

/*
 * The viewBox does the scaling, so the element takes the width it is given and
 * the height follows. A fixed height would squash the plot on a phone and leave
 * the labels overlapping the bars, which is the one thing a chart may not do.
 */
.trend-chart {
	display: block;
	width: 100%;
	height: auto;
}

/*
 * A BAR IS THE FIGURE ITSELF, WHICH IS WHY IT MAY TAKE THE ACCENT.
 *
 * The accent is reserved for the primary action, the focus ring and selection,
 * and nothing DECORATIVE is ever painted in it. A column here is not an ornament
 * on a number, it IS the number: reading its height is the only way this figure
 * is read at all. That is the same borrowing .band-count-lead already makes when
 * it paints the one count a reader can act on first, and it is the reason the
 * chart carries no second colour: one series is one colour, and a hue that
 * varied down the row would be saying something about the days that is not true.
 */
.trend-bar {
	fill: var(--accent);
}

/*
 * The rule at the top of the plot and the one across the middle. --line is the
 * only rule weight in this product and a chart is not the place to invent a
 * second one.
 */
.trend-grid {
	stroke: var(--line);
	stroke-width: 1;
}

/* The baseline is the axis rather than a gridline, so it takes the reading ink
   at the caption's weight: a nought line drawn in the same hairline as the
   guides above it leaves the columns floating. */
.trend-baseline {
	stroke: var(--ink-3);
	stroke-width: 1;
}

/*
 * The figures on both axes. 11px is below the interface's own 14, which is right
 * for an axis and wrong for anything a reader has to compare one character at a
 * time, and tabular figures keep two dates and two counts the same width so a
 * label cannot shift the mark it belongs to.
 */
.trend-tick {
	fill: var(--ink-3);
	font-family: var(--font);
	font-size: 11px;
	font-variant-numeric: tabular-nums;
}

/*
 * SEVERITY IS CARRIED THREE WAYS ON THE QUEUE AND THIS IS THE THIRD.
 *
 * The stripe on the left of a queue row is a colour, and roughly one man in
 * twelve cannot reliably separate the red from the amber. The chip beside the
 * date carries the level's own WORD, which is the second, and this is the
 * weight: an urgent row's title is set heavier than the rest, so a reader
 * skimming the queue sees which rows matter before they read a word of any of
 * them. Scoped to the level and never to the row, so a warning and an
 * information row still read as one kind of thing.
 */
.queue-item.alert-level-urgent .queue-title {
	font-weight: 700;
}
