/*
 * Copyright © 2026 Spinium Property Management OÜ - All rights reserved.
 * Author: Timo J. Rinne <tri@iki.fi>
 *
 * Unreleased and unlicensed. Any use is explicitly prohibited without a prior
 * written license from Spinium Property Management OÜ (https://spinium.com).
 * See COPYING.
 */

/* The console shell every operator's console in this repository wears
 * (ROADMAP.md §59).
 *
 * The palette, the dark rail down the left and the way it collapses, the cards,
 * the tables, the forms, the buttons and the footer mark. Not any one product's
 * pages: those stay in each product's own `web/static/admin.css`, which is
 * linked after this one.
 *
 * Shared rather than copied, and the reason is not economy — it is the same
 * reason `docstyle.js` is shared by the three generated manuals (§58.3).
 * Consoles in one repository that look different for no reason are a repository
 * that looks like several. An operator moving between the bank simulator and
 * the identity simulator is the same person doing the same job, and should not
 * have to learn where the navigation is twice.
 *
 * Deliberately plain and dense: these are operators' consoles for simulators,
 * and every pixel spent on decoration here is one not spent on the per-bank
 * skins (ROADMAP.md §8.2), which are where the visual work belongs. A console
 * should be unmistakably *not* a bank, so that a screenshot is never ambiguous
 * about which surface it came from.
 */

:root {
	color-scheme: light dark;
	--bg: #f4f5f7;
	--fg: #16181d;
	--muted: #666a75;
	--card: #ffffff;
	--line: #e0e2e8;
	--line-soft: #eceef2;
	--accent: #1d5cff;
	--accent-dark: #1748cc;
	--danger: #c43d3d;
	--danger-dark: #a83232;
	--notice-bg: #fff7e0;
	--notice-line: #e5cf8a;
	--notice-fg: #6b5410;

	/* The rail (§28.10). Dark in both themes, like the masthead it replaces:
	 * the console must be unmistakably not a bank at a glance, and the one
	 * permanently dark edge is what does that in a screenshot. */
	--nav-full: 244px;
	--nav-rail: 66px;
	--nav-w: var(--nav-full);
	--nav-bg: #10151f;
	--nav-fg: #e8eaf0;
	--nav-muted: #8b93a5;
	--nav-hover: rgba(255, 255, 255, 0.07);
	--nav-sel: rgba(255, 255, 255, 0.12);
	--nav-line: rgba(255, 255, 255, 0.09);
	--nav-accent: #5b8cff;
	--nav-ease: 170ms cubic-bezier(.4, 0, .2, 1);
}

html.nav-collapsed { --nav-w: var(--nav-rail); }

@media (prefers-color-scheme: dark) {
	:root {
		--bg: #14161b;
		--fg: #e6e7ec;
		--muted: #949aa8;
		--card: #1e2128;
		--line: #303540;
		--line-soft: #262a33;
		--notice-bg: #33301f;
		--notice-line: #5f5730;
		--notice-fg: #e3d69c;
	}
}

* { box-sizing: border-box; }

body {
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
	margin: 0;
	background: var(--bg);
	color: var(--fg);
}

/* -- the rail (ROADMAP.md §16.1, §28.10) --------------------------------- *
 *
 * Fixed to the left edge and the full height of the window, so the list does
 * not scroll away from under the operator when a ledger page is three screens
 * long. The content column reserves its width with padding on <body> rather
 * than a margin on <main>, which leaves main's own `auto` margins free to keep
 * centring the page in whatever space is left. One number moves, and
 * everything follows it. */

.sidenav {
	position: fixed; inset: 0 auto 0 0; z-index: 30;
	width: var(--nav-w);
	display: flex; flex-direction: column;
	background: var(--nav-bg); color: var(--nav-fg);
	border-right: 1px solid var(--nav-line);
	transition: width var(--nav-ease);
	overflow: hidden;
}
.sidenav .ico-defs { position: absolute; }
.sidenav .ico {
	width: 17px; height: 17px; flex: 0 0 17px;
	fill: none; stroke: currentColor; stroke-width: 1.4;
	stroke-linecap: round; stroke-linejoin: round;
}

body.has-nav { padding-left: var(--nav-w); transition: padding-left var(--nav-ease); }
main { max-width: 1400px; margin: 24px auto; padding: 0 24px; }

/* Everything that is a word rather than a symbol fades and collapses together.
 * Width and opacity rather than `display: none`: the label stays in the
 * accessibility tree, so the rail reads the same to a screen reader open or
 * shut, and there is something to animate. */
.sidenav .navlabel, .sidenav .brand-text, .sidenav .who-text, .sidenav .ext {
	white-space: nowrap; overflow: hidden;
	transition: opacity 110ms linear, width var(--nav-ease);
}
html.nav-collapsed .sidenav .navlabel,
html.nav-collapsed .sidenav .brand-text,
html.nav-collapsed .sidenav .who-text,
html.nav-collapsed .sidenav .ext { opacity: 0; width: 0; }

/* -- top: the mark and the switch -- */
.sidenav-top {
	display: flex; align-items: center; gap: 10px;
	padding: 14px 12px; min-height: 58px;
	border-bottom: 1px solid var(--nav-line);
}
.sidenav .brand {
	display: flex; align-items: center; gap: 10px; min-width: 0;
	color: var(--nav-fg); text-decoration: none; font-size: 15px; font-weight: 600;
	letter-spacing: -0.01em; flex: 1 1 auto;
}
.sidenav .brand-mark {
	flex: 0 0 30px; width: 30px; height: 30px; border-radius: 9px;
	display: grid; place-items: center;
	background: linear-gradient(140deg, var(--nav-accent), #2d4bd0);
	color: #fff; font-size: 13px; font-weight: 700; letter-spacing: -0.03em;
}
.sidenav .brand .sub { font-weight: 400; color: var(--nav-muted); font-size: 12px; margin-left: 5px; }
.nav-toggle {
	flex: 0 0 28px; width: 28px; height: 28px; padding: 0; margin: 0;
	display: grid; place-items: center;
	background: transparent; color: var(--nav-muted);
	border: 1px solid transparent; border-radius: 8px; cursor: pointer;
	transition: background var(--nav-ease), color var(--nav-ease);
}
.nav-toggle:hover { background: var(--nav-hover); color: var(--nav-fg); }
.nav-toggle .ico { transition: transform var(--nav-ease); }
html.nav-collapsed .nav-toggle .ico { transform: rotate(180deg); }
/* Shut, the switch takes the whole rail: a 28px target centred under a 30px
 * mark is the one control an operator has to hit to get the words back. */
html.nav-collapsed .sidenav-top { flex-direction: column; gap: 8px; padding: 12px 0; }
html.nav-collapsed .sidenav .brand { flex: 0 0 auto; justify-content: center; }

/* -- who is signed in -- */
.sidenav-who {
	display: flex; align-items: center; gap: 10px; min-width: 0;
	padding: 12px; margin: 0; border-bottom: 1px solid var(--nav-line);
}
.sidenav .who-mark {
	flex: 0 0 30px; width: 30px; height: 30px; border-radius: 50%;
	display: grid; place-items: center;
	background: var(--nav-sel); color: var(--nav-fg);
	font-size: 11px; font-weight: 700; letter-spacing: .02em;
	border: 1px solid var(--nav-line);
}
.sidenav .who-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.sidenav .who-label { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--nav-muted); }
.sidenav .who-name { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
.sidenav .who-user { font-size: 11px; color: var(--nav-muted); overflow: hidden; text-overflow: ellipsis; }
html.nav-collapsed .sidenav-who { justify-content: center; padding: 12px 0; }

/* -- the sections -- */
.nav-main {
	flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
	padding: 10px 8px; display: flex; flex-direction: column; gap: 1px;
}
html.nav-collapsed .nav-main { padding: 10px 0; align-items: center; }
.nav-main::-webkit-scrollbar { width: 8px; }
.nav-main::-webkit-scrollbar-thumb { background: var(--nav-line); border-radius: 4px; }

.navitem {
	position: relative;
	display: flex; align-items: center; gap: 11px;
	padding: 8px 10px; border-radius: 9px;
	color: var(--nav-muted); text-decoration: none;
	font-size: 13.5px; font-weight: 500; line-height: 1.2;
	border: 0; background: transparent; width: 100%; text-align: left;
	font-family: inherit; cursor: pointer;
	transition: background var(--nav-ease), color var(--nav-ease);
}
html.nav-collapsed .navitem { width: 44px; justify-content: center; padding: 10px 0; }
.navitem:hover { background: var(--nav-hover); color: var(--nav-fg); }
.navitem:focus-visible { outline: 2px solid var(--nav-accent); outline-offset: -2px; }
.navitem.active { background: var(--nav-sel); color: #fff; font-weight: 600; }
/* The bar is what makes the current section findable at a glance in a list of
 * fifteen; the background alone reads as a hover that got stuck. */
.navitem.active::before {
	content: ""; position: absolute; left: -8px; top: 50%; transform: translateY(-50%);
	width: 3px; height: 18px; border-radius: 0 3px 3px 0; background: var(--nav-accent);
}
html.nav-collapsed .navitem.active::before { left: -11px; }
.navitem .ext { font-size: 10px; color: var(--nav-muted); margin-left: auto; }

/* -- a collapsible group (ROADMAP.md §28.11) --
 *
 * `<details>`/`<summary>`, so it opens and closes with no script: the console
 * is server-rendered and every page of it works with scripting off, and a
 * disclosure that needs JavaScript would be the one control here that does not.
 *
 * The summary IS a `.navitem`, so it sits in the list without a second set of
 * rules that would drift from the first. */
/* A block in the column, not `display: contents`. Contents would let the
 * summary and the items sit as siblings of the sections — tidier in the
 * abstract — but how a browser hides the closed half of a `<details>` whose own
 * box has been removed is not the same everywhere, and this is a control that
 * has to work rather than one that has to be elegant. */
.navgroup { display: block; }
html.nav-collapsed .navgroup { width: 44px; }
.navgroup-head { list-style: none; user-select: none; }
/* Safari still draws the disclosure triangle without this, inside the padding,
 * where it reads as a bullet that belongs to the icon. */
.navgroup-head::-webkit-details-marker { display: none; }
.navgroup-head::marker { content: ""; }
.ico-caret {
	margin-left: auto; width: 13px; height: 13px; flex: 0 0 13px;
	color: var(--nav-muted); transition: transform var(--nav-ease);
}
.navgroup[open] > .navgroup-head .ico-caret { transform: rotate(180deg); }
/* Collapsed to the rail there is no room for a caret and nothing it could
 * point at: the label it belongs to is not being drawn. */
html.nav-collapsed .ico-caret { display: none; }

.navgroup-items { display: flex; flex-direction: column; gap: 1px; }
/* Indented, and hung off a line that says what the indent means. Two pixels of
 * left padding would read as a rendering accident. */
html:not(.nav-collapsed) .navgroup-items {
	margin: 1px 0 2px 20px; padding-left: 10px;
	border-left: 1px solid var(--nav-line);
}
.navitem-child { font-size: 13px; padding-top: 6px; padding-bottom: 6px; }
/* A bullet where a section has an icon. A child is not a section and should not
 * look like one; the dot is enough to say "an item", and the width matches the
 * icon it stands in for so the labels line up. */
.navbullet {
	width: 17px; height: 17px; flex: 0 0 17px;
	display: inline-flex; align-items: center; justify-content: center;
}
.navbullet::before {
	content: ""; width: 5px; height: 5px; border-radius: 50%;
	background: currentColor; opacity: .55;
}
.navitem-child:hover .navbullet::before,
.navitem-child.active .navbullet::before { opacity: 1; }

/* Collapsed to the rail the group behaves exactly as it does open: the icon is
 * the summary, clicking it reveals the bullet under it, and both carry the
 * tooltips the rail relies on at that width. Deliberately NOT forced open —
 * that was tried, by overriding `display` on the closed half, and it does
 * nothing: a browser hides the shut part of a `<details>` with
 * `content-visibility`, not with `display`, so the rule looked right and the
 * section was simply unreachable with the rail collapsed. One behaviour at both
 * widths is also one behaviour to learn. */
html.nav-collapsed .navitem-child { width: 44px; justify-content: center; padding: 8px 0; }
/* The bullet carries the whole meaning here, so it is drawn a little stronger
 * than it is beside a label. */
html.nav-collapsed .navbullet::before { width: 6px; height: 6px; opacity: .75; }

/* -- the personal group, and the way out -- *
 *
 * Everything above is the simulator; these are the operator. The separation is
 * a rule of the console, not decoration: an operator who has finished with
 * Faults must not find Sign out looking like the next section along. */
.sidenav-foot { flex: 0 0 auto; padding: 8px; border-top: 1px solid var(--nav-line); }
html.nav-collapsed .sidenav-foot { padding: 8px 0; display: flex; flex-direction: column; align-items: center; }
.nav-personal { display: flex; flex-direction: column; gap: 1px; }
html.nav-collapsed .nav-personal { align-items: center; }
.navitem-personal { color: var(--nav-muted); font-weight: 500; }
.navitem-personal:hover { background: var(--nav-hover); color: var(--nav-fg); }

.nav-signout { margin: 6px 0 0 0; padding-top: 6px; border-top: 1px dashed var(--nav-line); }
html.nav-collapsed .nav-signout { width: 44px; }
.navitem-signout { color: var(--nav-muted); font-size: 13.5px; }
.navitem-signout:hover { background: rgba(196, 61, 61, 0.18); color: #ff9a9a; }

/* -- narrow windows -- *
 *
 * Under 960px the content column keeps reserving only the rail, and expanding
 * lays the full list OVER the page instead of shoving it sideways. A console
 * that reflows its tables every time somebody checks where they are is a
 * console people stop checking. */
.sidenav-scrim {
	position: fixed; inset: 0; z-index: 25; background: rgba(0, 0, 0, .45);
	backdrop-filter: blur(1px);
}
.sidenav-scrim[hidden] { display: none; }
@media (max-width: 960px) {
	body.has-nav { padding-left: var(--nav-rail); }
	html:not(.nav-collapsed) .sidenav { box-shadow: 0 0 40px rgba(0, 0, 0, .55); }
}
@media (min-width: 961px) { .sidenav-scrim { display: none; } }

@media (prefers-reduced-motion: reduce) {
	.sidenav, body.has-nav, .sidenav .navlabel, .sidenav .brand-text,
	.sidenav .who-text, .sidenav .ext, .nav-toggle, .nav-toggle .ico, .navitem,
	.ico-caret {
		transition: none;
	}
}

.card {
	background: var(--card); border: 1px solid var(--line); border-radius: 12px;
	padding: 22px; margin-bottom: 20px;
}
.login-wrap { min-height: 70vh; display: flex; align-items: center; justify-content: center; padding: 20px; }
.login-wrap .card { width: 100%; max-width: 400px; }

h1 { font-size: 19px; margin: 0 0 16px 0; letter-spacing: -0.01em; }
h2 { font-size: 15px; margin: 18px 0 8px 0; }

label { display: block; font-size: 13px; margin-bottom: 6px; color: var(--muted); }
input, select, textarea {
	width: 100%; padding: 10px 12px; border: 1px solid var(--line);
	border-radius: 6px; font-size: 15px; margin-bottom: 12px;
	background: var(--bg); color: inherit;
}
input:focus, select:focus, textarea:focus {
	outline: 2px solid var(--accent); outline-offset: 0; border-color: transparent;
}

button {
	padding: 10px 14px; background: var(--accent); color: #fff;
	border: 0; border-radius: 6px; font-size: 15px; font-weight: 500; cursor: pointer;
}
button:hover { background: var(--accent-dark); }
button.secondary { background: transparent; color: var(--accent); border: 1px solid var(--accent); }
/* Something that cannot be undone should not look like Save. */
button.danger { background: var(--danger); color: #fff; }
button.danger:hover { background: var(--danger-dark); }
button.full { width: 100%; }
button.linkbutton {
	width: auto; margin: 0; padding: 0; background: none; border: none;
	color: inherit; font: inherit; text-decoration: underline; cursor: pointer;
}

.muted { color: var(--muted); font-size: 13px; }
.error { color: var(--danger); font-size: 13px; margin: 8px 0; }
.notice {
	background: var(--notice-bg); border: 1px solid var(--notice-line); color: var(--notice-fg);
	border-radius: 8px; padding: 10px 14px; margin-bottom: 14px; font-size: 14px;
}

/* Tables live inside a card, so anything wider than the card scrolls in place
   rather than spilling over its edge. */
.tablewrap { overflow-x: auto; max-width: 100%; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
table th {
	text-align: left; font-weight: 500; color: var(--muted);
	padding: 8px 10px; border-bottom: 1px solid var(--line);
}
table td { padding: 8px 10px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
/* Raw JSON, URLs and IBANs have no spaces to break at, which is what made the
   table wider than its card in the first place. */
table td code { overflow-wrap: anywhere; word-break: break-word; }
table td code.nowrap { white-space: nowrap; overflow-wrap: normal; word-break: normal; }

code, .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; }
a { color: var(--accent); }

ul { margin: 0; padding-left: 20px; }
li { margin: 6px 0; }

.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 18px; }
.metric { font-size: 30px; font-weight: 600; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }

/* Enrolment: a QR code needs its white quiet zone whatever the page around it
   looks like, and the key beside it is meant to be typed by hand when a camera
   is not an option. */
.qr-frame {
	background: #fff; padding: 12px; border-radius: 10px; display: inline-block;
	border: 1px solid var(--line); line-height: 0;
}
.qr-frame svg { width: 200px; height: 200px; display: block; }
.secretbox {
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px;
	overflow-wrap: anywhere; text-align: center;
	background: rgba(127,127,127,.10); padding: 8px 10px; border-radius: 6px;
	margin: 12px 0;
}

/* A row of related actions that should not each be on their own line. */
.buttonrow { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.buttonrow button { margin: 0; }
button:disabled { opacity: .5; cursor: not-allowed; }

/* Status words in a table. Colour alone never carries the meaning — the word is
   the message, the colour only makes it findable. */
.status-ok { color: #1e7d43; font-weight: 600; }
.status-stale { color: #8a6d1a; font-weight: 600; }
.status-bad { color: var(--danger); font-weight: 600; }
@media (prefers-color-scheme: dark) {
	.status-ok { color: #5bbd84; }
	.status-stale { color: #d3b45c; }
}

/* Alpine only takes over once it has parsed the page; without this, every
   x-show block flashes visible on first paint. */
[x-cloak] { display: none !important; }

/* -- the footer mark ----------------------------------------------------- *
 *
 * Small, quiet, and at the bottom: the admin UI should be unmistakably *not* a
 * bank, and a prominent logo on an operator's console would spend attention
 * that belongs in the per-bank skins.
 *
 * The wordmark in the asset is dark navy, which is invisible on the dark
 * background. Rather than editing the asset — it is retrieved verbatim from
 * spinium.com and a local edit would drift — the whole mark is lightened in
 * dark mode. Hue rotation puts the blue back after the inversion.
 */
.sitefoot {
	display: flex; align-items: center; justify-content: center;
	gap: 14px; flex-wrap: wrap;
	margin: 40px auto 26px auto; padding: 0 20px;
	color: var(--muted); font-size: 12px;
}
.sitefoot img { display: block; opacity: .75; }
.sitefoot a:hover img { opacity: 1; }

@media (prefers-color-scheme: dark) {
	.sitefoot img { filter: invert(1) hue-rotate(180deg); opacity: .7; }
}

/* A page that keeps up says what it did (ROADMAP.md §71).
 *
 * Both consoles wear these now. The identity simulator's console has redrawn
 * itself since ROADMAP-VSIM.md §24 and said NOTHING while it did — a table
 * quietly became a different table, and a reader who had been looking at a row
 * had no way to know it had moved. The net-bank has marked its changes since
 * §61.7; sharing one script is what makes sharing the two rules that go with it
 * worth doing.
 *
 * The highlight fades and then it is gone: a permanent mark would still be
 * there tomorrow, saying that something changed at a moment nobody remembers.
 * `prefers-reduced-motion` gets the same information without the movement. */
.live-changed {
	animation: live-fade 2.4s ease-out 1;
	border-radius: 2px;
}
@keyframes live-fade {
	from { background: color-mix(in srgb, var(--accent) 26%, transparent); }
	to   { background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
	.live-changed { animation: none;
					background: color-mix(in srgb, var(--accent) 14%, transparent); }
}

/* And when a redraw is HELD — an operator is typing, or an edit row is open —
 * the page says so rather than silently going stale. */
.live-waiting {
	background: color-mix(in srgb, var(--accent) 8%, var(--card));
	border-left: 3px solid var(--accent);
	padding: 6px 12px; margin: 0 0 12px 0; font-size: 13px;
	color: var(--muted);
}
