/*
 * UWU ID — stylesheet.
 *
 * Implements the design board delivered 2026-09-10 (Claude Design project
 * "OAuth Login ออกแบบเว็บ", mirrored at docs/design/uwu-id-design-board.html):
 * a white card on a cool grey ground, 9px controls / 12px cards, one blue
 * (#2f66ea, the Urban Wealth Union logo blue) carrying every affordance, and
 * IBM Plex Sans Thai for both scripts. The primary button is the only solid
 * blue object on the page.
 *
 * It replaces the "Industry" system of 2026-09-08 (square corners, hairline
 * frames, transparent cards, "+" registration marks, Barlow Condensed). The
 * owner chose to move the whole product at once rather than leave the three
 * redesigned pages looking foreign to the other 43 — so the change lives in
 * the token and component layers here, and the views were not rewritten.
 *
 * Deviations from the board, each deliberate:
 *  1. Fonts self-hosted from /fonts (UX-SPEC §10.4 / our CSP) and split by
 *     unicode-range; the board's Google Fonts link is preview-only.
 *  2. The board's 390px artboard frame (14px radius, 1px border) is a phone
 *     mock, not a page element — the real page is full-bleed --bg with a
 *     centred column.
 *  3. Its sixteen neutral alphas collapse to five tokens.
 *  4. Focus, hover and disabled states are undrawn on the board; they are
 *     derived here from its own palette.
 *  5. No dark mode, and a white ground rather than the board's #f4f6fa: the
 *     owner asked for a single palette, white and blue (2026-09-11).
 *  6. Controls keep a 44px minimum touch target (UX-SPEC §9).
 *
 * Mobile-first, no framework, every page works without client JS.
 */

/* ---------- fonts (self-hosted — UX-SPEC §10.4 forbids external CDNs) ----------
   IBM Plex Sans Thai carries both scripts, so Thai and Latin share one family
   and one metric: the 2026-09-10 board's own constraint ("ฟอนต์ IBM Plex Sans
   Thai + IBM Plex Sans เสิร์ฟจาก origin เราเอง"). Split by unicode-range so a
   Thai page never downloads a Latin file it cannot use, and the other way
   round — ~29 KB per weight actually fetched, not 117 KB. */

@font-face {
	font-family: "IBM Plex Sans Thai";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("/fonts/plex-thai-400-latin.woff2") format("woff2");
	unicode-range:
		U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Sans Thai";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("/fonts/plex-thai-400-thai.woff2") format("woff2");
	unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}

@font-face {
	font-family: "IBM Plex Sans Thai";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("/fonts/plex-thai-500-latin.woff2") format("woff2");
	unicode-range:
		U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Sans Thai";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("/fonts/plex-thai-500-thai.woff2") format("woff2");
	unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}

@font-face {
	font-family: "IBM Plex Sans Thai";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url("/fonts/plex-thai-600-latin.woff2") format("woff2");
	unicode-range:
		U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Sans Thai";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url("/fonts/plex-thai-600-thai.woff2") format("woff2");
	unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}

@font-face {
	font-family: "IBM Plex Sans Thai";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url("/fonts/plex-thai-700-latin.woff2") format("woff2");
	unicode-range:
		U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Sans Thai";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url("/fonts/plex-thai-700-thai.woff2") format("woff2");
	unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}

/* ---------- tokens ---------- */

:root {
	color-scheme: light;

	/* Palette — Claude Design board 2026-09-10 (t0 "โทเคนสีที่เสนอปรับ").
	   The blue is the Urban Wealth Union logo blue: white on --accent is
	   4.9:1 (AA). --accent as TEXT is only sanctioned on --accent-tint at
	   15px+ bold, which is why tinted controls print --accent-ink instead. */
	/* Owner 2026-09-11: one palette, white and blue. The board's cool grey
	   ground is gone — page and card are both white, and the card is told
	   apart by its hairline alone (no shadow: on white a shadow only reads as
	   dirt). --danger stays the one non-blue hue, because an error that is
	   the same colour as the accent is not an error. */
	--bg: #ffffff;
	--surface: #ffffff;
	--ink: #14181f;

	/* One neutral, sixteen alphas on the board — collapsed to the five steps
	   the components actually distinguish. */
	/* #322 (UX-07/UX-27) contrast floor, computed against white (WCAG 2.2):
	   TEXT tokens must clear 4.5:1 (WCAG 1.4.3) — soft 62% = 4.98:1,
	   faint 60% = 4.68:1 — and CONTROL borders 3:1 (WCAG 1.4.11) —
	   line-strong 48% = 3.20:1, mark 52% = 3.60:1. The decoration line
	   (--line, 12%) stays light on purpose: separators may be quiet. */
	--ink-muted: rgb(20 24 31 / 62%);
	--ink-soft: rgb(20 24 31 / 62%);
	--ink-faint: rgb(20 24 31 / 60%);
	--line: rgb(20 24 31 / 12%);
	--line-strong: rgb(20 24 31 / 48%);
	--mark: rgb(20 24 31 / 52%);

	--accent: #2f66ea;
	--accent-hover: #1f4fc4;
	--accent-tint: #eaf0ff;
	--accent-ink: #1b3f96;
	--accent-contrast: #ffffff;
	--accent-100: var(--accent-tint);
	--accent-600: var(--accent);
	--accent-700: var(--accent-hover);
	--accent-800: var(--accent-ink);

	--link: var(--accent-hover);
	--link-hover: #14357f;

	--notice-bg: var(--accent-tint);
	--notice-ink: var(--accent-ink);
	--alert-rule: var(--accent-hover);
	--alert-bg: var(--accent-tint);

	--danger: #8c2f26;
	--danger-hover: #6f251e;
	--danger-tint: #fbf0ee;
	--danger-line: rgb(140 47 38 / 35%);
	--danger-contrast: #ffffff;

	--muted-bg: #eef0f4;
	/* A near-white panel for quiet blocks — a neutral, not a second hue. */
	--panel: #f5f7fa;

	--apple-bg: #000000;
	--apple-ink: #ffffff;

	--space-1: 4px;
	--space-2: 7px;
	--space-3: 10px;
	--space-4: 14px;
	--space-6: 20px;
	--space-8: 28px;

	/* The board is a rounded system: 9px on controls, 12px on cards, 7px on
	   the small rows inside a popover. */
	--radius: 9px;
	--radius-sm: 7px;
	--radius-lg: 12px;

	--shadow-card: none;
	--shadow-pop: 0 8px 24px rgb(20 24 31 / 16%);

	--font:
		"IBM Plex Sans Thai", "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", "Leelawadee UI",
		Thonburi, sans-serif;
	/* One family for everything: the board sets no display face, and a
	   condensed Latin heading over Thai body text never matched anyway. */
	--font-heading: var(--font);
	--font-mono: ui-monospace, Menlo, Consolas, monospace;
}

* {
	box-sizing: border-box;
}

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

body {
	margin: 0;
	background: var(--bg);
	color: var(--ink);
	font-family: var(--font);
	font-size: 15px;
	line-height: 1.55;
}

h1,
h2,
h3 {
	font-family: var(--font-heading);
	font-weight: 600;
	line-height: 1.15;
	letter-spacing: -0.015em;
	margin: 0 0 var(--space-2);
}

a {
	color: var(--link);
	text-underline-offset: 3px;
}

a:hover {
	color: var(--link-hover);
	opacity: 1;
}

:focus {
	outline: none;
}

:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}

::selection {
	background: color-mix(in srgb, var(--accent) 30%, transparent);
}

p {
	margin: 0 0 var(--space-3);
}

/* ---------- icons ----------
   Font Awesome Pro 7 marks, inlined into the HTML by `src/views/components/
   Icon.tsx` — never the webfont and never the Kit script, both of which the
   CSP (T-094) blocks silently and the 1,000-simultaneous-login budget of
   DESIGN-BRIEF §7 cannot afford.

   The glyph is `fill: currentColor` in the markup, so it inherits whatever
   colour the surrounding control sets — `.provider-apple` paints white on
   black with no icon rule of its own. `display: block` kills the inline
   baseline gap that would otherwise offset the icon inside a flex button. */

.icon {
	display: block;
	flex: none;
}

/* D-071 — where the marks sit.

   Every one of these is `aria-hidden` in the markup: the text beside it says
   the same thing, and a screen reader must not read the picture twice. They
   are sized BELOW their own line-height on purpose — a mark as tall as the
   words competes with them, and these headings are meant to be read, not
   decorated. Colour is always inherited, never set here, so a mark can never
   out-shout the one blue the 2026-09-10 board allows. */

/* Section headings: 16px mark against 17px text, optically centred by the
   flex row rather than by the text baseline (the Plex Thai ascender would
   otherwise push the mark visibly high). */
.account-section-title {
	display: flex;
	align-items: center;
	gap: var(--space-2);
}

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

/* Account sub-menu. `inline-flex`, not the old `inline-block`, so the mark
   and the label stay one unbreakable unit when the bar wraps on a phone. */
.account-nav-link {
	display: inline-flex;
	align-items: center;
	gap: 7px;
}

/* Banners: the mark aligns to the FIRST line, not the middle, because a
   two-line banner with a centred icon reads as if the icon belonged to
   neither line. */
.banner {
	display: flex;
	align-items: flex-start;
	gap: 9px;
}

.banner-icon {
	margin-top: 1px;
}

.banner-body {
	flex: 1;
	min-width: 0;
}

/* List rows (devices, passkeys): the only marks in the system that carry
   information the text does not repeat at a glance — form factor, and
   whether a passkey is synced. 20px, one step up, because here the shape IS
   the thing being scanned. */
.row-icon {
	color: var(--ink-muted);
	width: 20px;
	height: 20px;
}

/* ---------- surfaces ----------
   The 2026-09-08 system framed cards, figures and primary buttons as wireframe
   objects with a "+" registration mark at each corner, drawn with eight
   gradients on one pseudo-element. The 2026-09-10 board has no marks: depth
   comes from a white fill, a hairline and one soft shadow. `.bp` survives as
   the class documents and figures already carry. */

.bp,
.card,
.account-section {
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-card);
}

/* ---------- page + card shell (pages 1–6, 12–14) ---------- */

/* Brand row (board 2026-09-10): 22px blue square with a 7px white notch in
   its top-right corner, then the wordmark. Two elements, no image. */

.brand {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0 0 4px;
}

.brand-mark {
	width: 22px;
	height: 22px;
	flex: none;
	border-radius: 5px;
	background: var(--accent);
	display: flex;
	align-items: flex-start;
	justify-content: flex-end;
	padding: 3px;
}

.brand-mark::after {
	content: "";
	width: 7px;
	height: 7px;
	background: var(--accent-contrast);
}

.brand-name {
	font-size: 15px;
	font-weight: 700;
	line-height: 1;
	letter-spacing: 0.02em;
	color: var(--ink);
}

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

@supports (min-height: 100svh) {
	.page {
		min-height: 100svh;
	}
}

.auth-wrap {
	flex: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: var(--space-8) var(--space-4);
}

.card {
	position: relative;
	padding: 22px 20px;
	width: 100%;
	max-width: 24.5rem;
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
}

.card > * {
	margin-bottom: 0;
}

.card-title {
	font-family: var(--font-heading);
	font-size: 20px;
	font-weight: 600;
	line-height: 1.35;
	text-wrap: pretty;
	margin: 0;
}

.card-text {
	color: var(--ink-muted);
	font-size: 13.5px;
	line-height: 1.5;
	margin: 0;
}

/* ---------- app-arrival card header (#255, T-256) ----------
   When the person arrives from an app, the app takes over the card: small
   grey eyebrow ("กำลังเข้าสู่ระบบไปยัง"), the app's icon when one was
   uploaded, the app name as the big title, and UWU ID demoted to a small
   grey subtitle ("ด้วย UWU ID"). Same tokens as .card-text — the two small
   lines are metadata, not content. */

.card-eyebrow,
.card-subtitle {
	color: var(--ink-muted);
	font-size: 13.5px;
	line-height: 1.5;
	margin: 0;
}

.card-arrival {
	display: flex;
	flex-direction: column;
	gap: 4px;
}

/* The app's icon: square, same rounding discipline as the brand mark and
   the consent tile (5/22 and 9/38 — the ~0.24 ratio), drawn in CSS terms
   so it follows the card's spacing, never floats. */
.app-icon {
	display: block;
	width: 48px;
	height: 48px;
	flex: none;
	border-radius: 12px;
	object-fit: cover;
}

/* Inside the consent tile the icon fills the 38px box the board drew
   (border + 9px rounding stay on the tile itself). */
.app-tile .app-icon {
	width: 100%;
	height: 100%;
	border-radius: inherit;
}

/* The admin preview sits inline in a sentence — tile-sized on purpose. */
.app-icon-preview {
	width: 38px;
	height: 38px;
	border-radius: 9px;
	vertical-align: middle;
}

/* The app name inside "to continue to X" is the one thing on that line the
   user must actually read (board 3a). */
.card-text b,
.card-text strong {
	color: var(--ink);
	font-weight: 600;
}

.card-footer {
	border-top: 1px solid var(--line);
	margin-top: var(--space-1);
	padding-top: 16px;
	font-size: 13px;
	color: var(--ink-muted);
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2) var(--space-4);
	justify-content: center;
}

@media (min-width: 30rem) {
	.card {
		padding: 32px 28px;
	}

	.card-title {
		font-size: 27px;
	}
}

/* ---------- account shell (pages 7–11) ---------- */

.account-shell {
	max-width: 46rem;
	margin: 0 auto;
	padding: var(--space-8) var(--space-4) 3rem;
	width: 100%;
}

.account-title {
	font-family: var(--font-heading);
	font-size: 27px;
	margin: 0 0 var(--space-4);
}

.account-nav {
	display: flex;
	gap: var(--space-1);
	overflow-x: auto;
	border-bottom: 1px solid var(--line);
	margin-bottom: var(--space-6);
}

.account-nav-link {
	display: inline-block;
	padding: var(--space-2) var(--space-4);
	color: var(--ink-muted);
	text-decoration: none;
	white-space: nowrap;
	font-family: var(--font-heading);
	font-size: 15px;
}

.account-nav-link:hover {
	color: var(--ink);
	background: color-mix(in srgb, var(--ink) 7%, transparent);
	opacity: 1;
}

.account-nav-link.is-active {
	color: var(--ink);
	box-shadow: inset 0 -2px 0 var(--accent);
}

/* UX-17/#322: on small screens the ACCOUNT nav wraps as a 2×2 grid instead
   of a horizontal scroller whose last tab ("ข้อมูลของฉัน") sat past the
   viewport edge. Scoped to `.account-user-nav` — the class the account
   shell adds; the admin nav keeps the scrolling row. */
@media (max-width: 30rem) {
	.account-nav.account-user-nav {
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: 0 var(--space-2);
		overflow-x: visible;
	}

	.account-nav.account-user-nav .account-nav-link {
		white-space: normal;
		text-align: center;
		padding: var(--space-2) var(--space-1);
	}
}

.account-content {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.account-section {
	position: relative;
	background: transparent;
	border: 1px solid var(--line);
	border-radius: 0;
	padding: var(--space-6);
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
}

.account-section-title {
	font-family: var(--font-heading);
	font-size: 17px;
	margin: 0;
}

/* list row: name, secondary detail, one action */

.row-list {
	display: flex;
	flex-direction: column;
}

.row {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-3) 0;
	border-bottom: 1px solid var(--line);
}

.row:last-child {
	border-bottom: none;
}

.row-main {
	flex: 1;
	min-width: 0;
}

.row-title {
	font-family: var(--font-heading);
	font-size: 16px;
}

.row-sub {
	color: var(--ink-muted);
	font-size: 12px;
}

/* Provider avatar on the sign-in-methods rows (T-072, #90): served by our
   proxy (/account/avatar/:id), never a provider CDN URL. */
.row-avatar {
	width: 40px;
	height: 40px;
	flex: none;
	object-fit: cover;
	border-radius: 0;
	border: 1px solid var(--line);
}

/* ---------- buttons ---------- */

.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	font-family: var(--font-heading);
	font-weight: 600;
	font-size: 15px;
	line-height: 1.2;
	padding: 13px 16px;
	border-radius: var(--radius);
	border: 1px solid var(--line-strong);
	background: var(--surface);
	color: var(--ink);
	cursor: pointer;
	text-decoration: none;
	min-height: 44px;
}

.btn svg {
	display: block;
}

/* A submit button that must read as one of the sentence's own links (D-064:
   the sign-in page's "verify email" way out is a form POST now — the address
   travels in the body, never in a URL). Same type, colour and underline the
   plain <a> gets from the rules above; only the platform's button chrome is
   stripped. The global :focus-visible ring applies unchanged. */
.link-btn {
	display: inline;
	font: inherit;
	font-weight: inherit;
	letter-spacing: inherit;
	padding: 0;
	border: 0;
	background: none;
	color: var(--link);
	text-decoration: underline;
	text-underline-offset: 3px;
	cursor: pointer;
}

.link-btn:hover {
	color: var(--link-hover);
}

/* Board 3c: a submitted form's button says what it is doing and stops looking
   pressable — flat grey, no pointer. Set by the client bundle on submit. */
.btn[aria-busy="true"],
.btn:disabled {
	background: var(--muted-bg);
	border-color: var(--line-strong);
	color: var(--ink-faint);
	cursor: default;
}

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

.btn-primary:hover {
	background: var(--accent-600);
	border-color: var(--accent-600);
	color: var(--accent-contrast);
	opacity: 1;
}

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

.btn-secondary:hover {
	background: color-mix(in srgb, var(--ink) 7%, transparent);
	color: var(--ink);
	opacity: 1;
}

.btn-secondary:active {
	background: color-mix(in srgb, var(--ink) 14%, transparent);
}

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

.btn-danger:hover {
	background: var(--danger-hover);
	border-color: var(--danger-hover);
	color: var(--danger-contrast);
	opacity: 1;
}

.btn[disabled] {
	opacity: 0.45;
	cursor: not-allowed;
}

/* Countdown suffix inside a disabled resend button (issue #89, T-072): the
   number is [data-countdown] for the client bundle, which hides the suffix
   and re-enables the button at zero. */
.btn-countdown {
	font-size: 13px;
	font-weight: 400;
	color: var(--ink-muted);
}

/* Resend form parked below the action row so its Turnstile widget sits off
   the main path (issue #89): hidden fields render nothing, only the widget
   is visible. */
.otp-resend-form {
	gap: var(--space-2);
}

/* Declared before `.provider-list form` below so the cascade reads in
   ascending specificity (biome noDescendingSpecificity). */
form {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
}

/* #305 (T-288) — the alias row's removal form is folded away until the row's
   own "remove alias" control is activated. <details> opens that one row only
   and needs no JavaScript (D-007); the summary is painted as the danger
   button it replaces, so the row keeps a single action at rest. Same
   disclosure shape as .provider-soon / .help-out: marker removed, the
   summary carries the styling itself. Declared after the bare `form` rule
   above for the same ascending-specificity reason. */
.alias-remove {
	min-width: 0;
}

.alias-remove > summary {
	list-style: none;
	cursor: pointer;
}

.alias-remove > summary::-webkit-details-marker {
	display: none;
}

.alias-remove > form {
	margin-top: var(--space-2);
	max-width: 100%;
}

/* ---------- provider buttons ---------- */

.provider-btn {
	width: 100%;
	justify-content: center;
}

.provider-btn:hover {
	background: color-mix(in srgb, var(--ink) 7%, transparent);
	color: var(--ink);
	opacity: 1;
}

/* Apple HIG: black button, white glyph and label (inverted in dark mode). */
.provider-apple {
	background: var(--apple-bg);
	color: var(--apple-ink);
	border-color: var(--apple-bg);
}

.provider-apple:hover {
	background: var(--apple-bg);
	color: var(--apple-ink);
	opacity: 0.85;
}

.provider-icon {
	display: inline-flex;
	align-items: center;
	flex: none;
}

.provider-icon svg {
	display: block;
}

/* Board 3a/3c: two columns at every width — four provider names fit a 390px
   card side by side, and a single column pushed the sign-up row off-screen.

   D-072: the grid is now three explicit ROWS rather than one flowing list
   (2 · 3 · 1, owner 2026-09-12). Explicit because the rows mean something —
   the two big identity providers, then the chat apps, then the national ID
   on its own — and a flowing grid would reshuffle that grouping the moment
   one provider is paused. */
/* `minmax(0, 1fr)` and `min-width: 0`, not a bare `1fr`: the card is a flex
   column, so both the row stack and every grid track default to a floor of
   their own min-content — three buttons whose labels refuse to shrink push
   the whole block out past the card's right edge. Letting the tracks reach
   zero hands the shrinking back to the labels, which is what the ellipsis
   rule below is for. */
.provider-rows {
	display: grid;
	gap: var(--space-2);
	min-width: 0;
}

.provider-list {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: var(--space-2);
	min-width: 0;
	/* `start`, not the default `stretch`: opening the "not available yet"
	   note under LINE makes that grid row taller, and stretched neighbours
	   would grow with it — two buttons silently changing size because a
	   third one was opened. Each button keeps its own 44px instead. */
	align-items: start;
}

.provider-list-1 {
	grid-template-columns: minmax(0, 1fr);
}

.provider-list-3 {
	grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* D-072 — a provider that is on the page but not connected yet.

   The <summary> is painted exactly like its live neighbours minus the ink:
   muted text and a dashed edge say "this one is different" before it is
   touched, and the marker triangle is removed so it does not read as a
   disclosure widget sitting in a row of buttons. What it opens is one plain
   sentence, not a sign-in. */
/* The grid item here is the <details>, not the <summary> that is painted as
   a button — so the zero floor has to go on the wrapper too, or this one
   cell refuses to shrink and shoves the row past the card. */
.provider-soon {
	min-width: 0;
}

.provider-soon > summary {
	list-style: none;
	cursor: pointer;
	color: var(--ink-muted);
	border-style: dashed;
}

.provider-soon > summary::-webkit-details-marker {
	display: none;
}

.provider-soon > summary:hover {
	background: color-mix(in srgb, var(--ink) 5%, transparent);
	color: var(--ink-muted);
}

.provider-soon-note {
	margin: 6px 0 0;
	font-size: 12px;
	color: var(--ink-muted);
	text-align: center;
}

/* The ThaiD tile is a full-colour brand asset, not a currentColor glyph —
   rounded like the app icon it is, and never recoloured by the button. */
.provider-image {
	display: block;
	border-radius: 4px;
}

.provider-list form {
	display: block;
}

.provider-list .provider-btn {
	width: 100%;
	padding: 11px 8px;
	font-size: 13px;
	font-weight: 500;
	min-height: 42px;
}

/* A mark-only button (owner 2026-09-12). It keeps the full 44px touch target
   and the full column width — only the words are gone, so the target never
   shrinks to the size of the glyph. The mark steps up to the label's old
   optical weight, since it is now carrying the row alone. */
.provider-btn-icon {
	padding-inline: 4px;
	min-width: 0;
}

.provider-btn-icon .icon,
.provider-btn-icon .provider-image {
	width: 20px;
	height: 20px;
}

/* Passkey is not one of the four: it is the method that needs no provider at
   all, so it gets the tinted full-width treatment above the grid. Its label
   prints --accent-ink, never --accent: the board sanctions accent-on-tint
   text only at 15px+ bold, and this is 14px. */
.passkey-block .provider-passkey {
	width: 100%;
	background: var(--accent-tint);
	border-color: color-mix(in srgb, var(--accent) 45%, transparent);
	color: var(--accent-ink);
	font-size: 14px;
}

.passkey-block .provider-passkey:hover {
	background: color-mix(in srgb, var(--accent) 16%, var(--accent-tint));
	color: var(--accent-ink);
	opacity: 1;
}

.provider-divider {
	display: flex;
	align-items: center;
	gap: 10px;
	margin: 6px 0 2px;
	font-size: 11.5px;
	line-height: 1;
	color: var(--ink-soft);
}

.provider-divider::before,
.provider-divider::after {
	content: "";
	flex: 1;
	height: 1px;
	background: var(--line);
}

/* T-110: a provider switched off leaves no hole — the sentence that replaces
   it is what stops a user hunting for a button that is simply gone
   (DESIGN-BRIEF §6.10, board 3c). */
.provider-note {
	font-size: 12px;
	line-height: 1.5;
	color: var(--ink-soft);
	margin: 0;
	padding: 2px 2px 0;
}

/* ---------- forms ---------- */

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

.field-label {
	display: block;
	font-size: 12.5px;
	font-weight: 500;
	color: var(--ink);
}

.input {
	width: 100%;
	min-height: 44px;
	padding: 12px 13px;
	font: inherit;
	/* #322 (UX-27): 16px keeps iOS from zooming the field on focus and is
	   the WCAG-referenced comfortable body size for inputs. */
	font-size: 16px;
	color: var(--ink);
	caret-color: var(--accent);
	background: var(--surface);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
}

.input:hover {
	border-color: var(--mark);
}

/* The board draws no focus ring; the 2px accent ring is the smallest thing
   that still satisfies UX-SPEC §9 without redrawing the box (which would
   shift the field by a pixel and make the form jump as you tab). #322: the
   ring is the accent itself, not the pale tint — a focus indicator needs
   3:1 against its surroundings (WCAG 2.4.11), and tint-on-white is 1.2:1. */
.input:focus-visible {
	border-color: var(--accent);
	outline: 2px solid var(--accent);
	outline-offset: 0;
}

.input::placeholder {
	color: var(--ink-faint);
	opacity: 1;
}

/* Board 3b: the invalid field goes 1.5px solid danger and its LABEL turns
   danger too, so the error is legible before you reach the message. */
.input[aria-invalid="true"] {
	border: 1.5px solid var(--danger);
}

.field:has(.input[aria-invalid="true"]) > .field-label,
.field:has(.input[aria-invalid="true"]) .field-label {
	color: var(--danger);
}

/* #325 (UX-12): show/hide password. The wrapper span and the button are
   created by the client bundle — no JavaScript, no button, the field renders
   exactly as before. The button is a real control: visible label, 3:1 border,
   the global focus ring, and a 32px+ hit area inside the 44px field. */
.input-pass-wrap {
	display: block;
	position: relative;
}

.input-pass-wrap .input {
	padding-right: 6.5rem;
}

.input-pass-toggle {
	position: absolute;
	top: 50%;
	right: 7px;
	transform: translateY(-50%);
	min-height: 32px;
	padding: 4px 10px;
	font: inherit;
	font-size: 12.5px;
	font-weight: 600;
	color: var(--accent-ink);
	background: var(--surface);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius-sm);
	cursor: pointer;
}

.input-pass-toggle:hover {
	background: var(--accent-tint);
}

/* Very narrow phones: the two Thai labels are short, but keep the field
   text clear of the button even with long English strings. */
@media (max-width: 22rem) {
	.input-pass-wrap .input {
		padding-right: 7.5rem;
	}
}

/* OTP entry (UX-SPEC §6.3): one wide field for six digits — big characters,
   generous letter spacing, centered. Mirrors the big code block in email E1. */
.input-otp {
	font-family: var(--font-mono);
	font-size: 20px;
	font-weight: 600;
	line-height: 1;
	padding: 14px 0;
	letter-spacing: 0.45em;
	text-align: center;
	text-indent: 0.45em; /* optical centering: offsets the trailing gap */
}

.input-otp::placeholder {
	letter-spacing: 0.45em;
}

/* #282: the manual-entry secret on the TOTP setup card — the "Secret key"
   label must sit on its own line above the code, not run into it, and the
   grouped code must wrap instead of overflowing a narrow phone. */
.totp-secret {
	margin: 0 0 var(--space-3);
}

.totp-secret-label {
	display: block;
	margin-bottom: 6px;
	font-size: 12.5px;
	font-weight: 500;
	color: var(--ink-muted);
}

.totp-secret-value {
	display: block;
	font-family: var(--font-mono);
	font-size: 14px;
	line-height: 1.5;
	letter-spacing: 0.06em;
	overflow-wrap: anywhere;
}

/* Birthdate entry (UX-SPEC §6.4): three no-JS controls in one row — day,
   month-name dropdown, 4-digit CE year. The month dropdown takes the space
   left over; the row wraps on very narrow screens rather than shrinking the
   44px touch targets. */
.birthdate-row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2);
	border: 0;
	padding: 0;
	margin: 0;
	min-width: 0;
}

/* D-067: all three are dropdowns now, so they are sized by their widest
   option, not by the digits you would have typed. The bases add up to more
   than a narrow card, which is deliberate — the row wraps and the year (the
   longest label in Thai: "2001 (พ.ศ. 2544)") takes the second line whole. */
.birthdate-row .input-day {
	flex: 0 1 6.5rem;
	min-width: 0;
}

.birthdate-row .input-month {
	flex: 1 1 9rem;
	min-width: 0;
}

.birthdate-row .input-year {
	flex: 1 1 11rem;
	min-width: 0;
}

.field-hint {
	color: var(--ink-muted);
	/* #322 (UX-27): the hint carries rules the user must act on (write-once
	   birthdate, password rules) — 13px is the floor for "important" text.
	   It wraps rather than overflows: the row layouts around it are fluid. */
	font-size: 13px;
	margin: 0;
}

/* Field-level error: paragraph-size accent text needs the deep ramp step. */
.field-error {
	color: var(--danger);
	font-size: 12px;
	line-height: 1.45;
	margin: 0;
}

/* Form-level error (board 3b): a danger-tinted box, not a left rule. It sits
   above the fields because on a wrong password the reason must be readable
   before the eye returns to the box it has to retype. */
.field-error[data-form-error] {
	background: var(--danger-tint);
	border: 1px solid var(--danger-line);
	border-radius: var(--radius);
	padding: 12px 14px;
	font-size: 13px;
	font-weight: 600;
	line-height: 1.4;
}

.field-error[data-form-error]:empty {
	display: none;
}

/* Board 3b splits the box in two: the red line names what went wrong, the ink
   line says what to do about it. */
.field-error-title {
	margin: 0;
	color: var(--danger);
	font-weight: 600;
	line-height: 1.4;
}

.field-error-hint {
	margin: 3px 0 0;
	color: var(--ink-muted);
	font-size: 12.5px;
	font-weight: 400;
	line-height: 1.5;
}

/* "I really cannot get in" — the routes out, folded away so the person who
   just mistyped is not handed a wall of options (board 3b). <details> needs
   no JavaScript. */
.help-out {
	margin-top: var(--space-1);
	border-top: 1px solid var(--line);
	padding-top: var(--space-4);
}

.help-out > summary {
	list-style: none;
	cursor: pointer;
	font-size: 13px;
	font-weight: 500;
	line-height: 1.3;
	color: var(--ink);
}

.help-out > summary::-webkit-details-marker {
	display: none;
}

.help-out > summary::after {
	content: " ▾";
	font-size: 9px;
	color: var(--ink-soft);
}

.security-task > summary::after {
	content: "▾";
	flex: none;
	color: var(--ink-muted);
}

.help-out[open] > summary::after {
	content: " ▴";
}

.help-out ul {
	margin: 10px 0 0;
	padding-left: 1.15em;
	font-size: 12.5px;
	line-height: 1.6;
	color: var(--ink-muted);
}

.checkbox {
	display: flex;
	align-items: flex-start;
	gap: var(--space-2);
	font-size: 13px;
}

.checkbox input {
	flex: none;
	width: 16px;
	height: 16px;
	margin: 3px 0 0;
	accent-color: var(--accent);
	border-radius: var(--radius-sm);
}

.checkbox-label {
	font-size: 13px;
}

.turnstile {
	min-height: 65px;
}

/* Why the submit button under a widget is disabled (owner QA 2026-09-09).
   Server-rendered `hidden`; the client bundle fills and reveals it. */
.turnstile-status {
	margin: 0;
	font-size: 11px;
	color: var(--ink-muted);
}

/* Checkbox (left) + helper link (right) row under a field (design board P1,
   D-017 [T-051]: remember-me checkbox on the left, link on the right). */
.form-aside {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: var(--space-2);
	font-size: 12px;
	margin: 0;
}

.form-remember {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	cursor: pointer;
}

.form-remember input[type="checkbox"] {
	margin: 0;
}

/* Passkey sits on its own full-width row above the provider grid. */
.passkey-block {
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
}

.passkey-block .field-error:empty {
	display: none;
}

/* ---------- consent screens (design board t1/t2) ----------
   The board's row list makes its own dividers: a 1px flex gap over a dark
   container that shows through, clipped by overflow — so no row carries a
   border and there is never a rule above the first or below the last. */

.row-list {
	display: flex;
	flex-direction: column;
	gap: 1px;
	background: var(--line);
	border: 1px solid var(--line);
	border-radius: 10px;
	overflow: hidden;
	margin: 0;
	padding: 0;
	list-style: none;
}

.row-list > li,
.row {
	background: var(--surface);
	padding: 11px 13px;
}

.row-title {
	font-size: 13.5px;
	font-weight: 500;
	line-height: 1.4;
	color: var(--ink);
	margin: 0;
}

.row-sub {
	font-size: 12px;
	line-height: 1.45;
	color: var(--ink-muted);
	margin: 2px 0 0;
}

.section-heading {
	font-size: 12.5px;
	font-weight: 600;
	line-height: 1.2;
	color: var(--ink);
	margin: 0 0 9px;
}

/* The two apps, face to face — the board opens /consent with this so the
   question ("who is asking, and of whom") is answered before any words. The
   app has no logo in our data, so its tile is the initial the board's own
   note requires as the fallback. */
.consent-lockup {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 12px;
	margin: 0;
}

.consent-lockup .brand-mark {
	width: 38px;
	height: 38px;
	border-radius: 9px;
	padding: 5px;
}

.consent-lockup .brand-mark::after {
	width: 11px;
	height: 11px;
}

.app-tile {
	width: 38px;
	height: 38px;
	flex: none;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 9px;
	border: 1px solid var(--line-strong);
	background: var(--panel);
	font-size: 16px;
	font-weight: 600;
	line-height: 1;
	color: var(--ink-muted);
	text-transform: uppercase;
}

.consent-link {
	font-size: 15px;
	line-height: 1;
	color: var(--ink-faint);
}

/* Who you are about to connect, and the way out of it. */
.identity-line {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-wrap: wrap;
	gap: 8px;
	margin: 0;
	font-size: 12.5px;
	line-height: 1.4;
	color: var(--ink-muted);
	text-align: center;
}

.identity-line b {
	color: var(--ink);
	font-weight: 600;
}

/* #228 A3-F2: "switch account" posts to /sign-out (only the POST route
   exists) — the form must not break the inline row, and the button stays
   the quiet link the board drew, not a heavyweight .btn. */
.identity-line .signout-form {
	display: contents;
}

.identity-line .signout-form button {
	appearance: none;
	border: 0;
	background: none;
	padding: 0;
	font: inherit;
	color: var(--link);
	text-decoration: underline;
	text-underline-offset: 3px;
	cursor: pointer;
}

.identity-line .signout-form button:hover {
	color: var(--link-hover);
}

/* A quiet panel, no border: used for what the app CANNOT do and for what
   declining costs. Both are reassurance, and reassurance that shouts is not
   reassurance. */
.quiet-note {
	margin: 0;
	padding: 11px 13px;
	border-radius: var(--radius);
	background: var(--panel);
	font-size: 12.5px;
	line-height: 1.55;
	color: var(--ink-muted);
}

.quiet-note b {
	color: var(--ink);
	font-weight: 600;
}

.legal-footer {
	margin: 0;
	padding-top: 14px;
	border-top: 1px solid var(--line);
	font-size: 12px;
	line-height: 1.6;
	color: var(--ink-muted);
}

/* Board 1c: on a wide screen the consent card widens and its two halves sit
   side by side instead of stacking — the decision is easier to weigh when the
   grant and its limits are in one field of view. The board's own note says the
   columns collapse back to 1a's single stack on mobile, which is the default
   here rather than an override. */
.consent-wide {
	max-width: 41rem;
}

@media (min-width: 48rem) {
	.consent-columns {
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: 22px;
		align-items: start;
	}

	.consent-wide .consent-lockup {
		justify-content: flex-start;
	}

	.consent-wide .identity-line {
		justify-content: flex-start;
		text-align: left;
	}
}

/* Board 1a: "ปุ่มสองปุ่มขนาดเท่ากัน สูงเท่ากัน ไม่มีปุ่มใดเป็น link เล็ก ๆ" —
   refusing must cost exactly as much effort as agreeing. */
.decision-actions {
	display: flex;
	flex-direction: column;
	gap: 9px;
}

.decision-actions form {
	display: contents;
}

.decision-actions .btn {
	width: 100%;
}

@media (min-width: 30rem) {
	.decision-actions {
		flex-direction: row-reverse;
		gap: 12px;
	}

	.decision-actions form {
		display: block;
		flex: 1;
	}
}

/* Board 2a: the document is readable IN the card, scrolling inside its own
   frame — .doc-shell is a full page and cannot live inside one. */
.doc-scroll {
	border: 1px solid var(--line-strong);
	border-radius: 10px;
	padding: 14px;
	max-height: 190px;
	overflow-y: auto;
	background: var(--panel);
}

.doc-scroll > :last-child {
	margin-bottom: 0;
}

.doc-scroll p,
.doc-scroll li {
	font-size: 12.5px;
	line-height: 1.75;
	color: var(--ink);
}

/* Board 2b: old date struck through, arrow, new date bold — the whole change
   in one glance. Both labels are identical on purpose; the contrast lives in
   the values. */
.date-change {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 14px;
	border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
	border-radius: 10px;
	background: var(--accent-tint);
}

.date-change > div {
	flex: 1;
}

.date-label {
	font-size: 11.5px;
	font-weight: 500;
	line-height: 1.2;
	color: var(--ink-soft);
	margin: 0 0 4px;
}

.date-old {
	font-size: 14px;
	font-weight: 600;
	line-height: 1.3;
	color: var(--ink-soft);
	text-decoration: line-through;
	margin: 0;
}

.date-new {
	font-size: 14px;
	font-weight: 700;
	line-height: 1.3;
	color: var(--ink);
	margin: 0;
}

.date-arrow {
	flex: none;
	font-size: 17px;
	line-height: 1;
	color: var(--accent);
}

/* Board 2a/2b: consent is an act, so it gets its own box to tick — never a
   pre-ticked one, and never a button that means "I read it" by itself. */
.consent-check {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	padding: 12px 13px;
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	font-size: 13px;
	line-height: 1.5;
	color: var(--ink);
	cursor: pointer;
}

.consent-check input {
	width: 20px;
	height: 20px;
	flex: none;
	margin: 1px 0 0;
	accent-color: var(--accent);
}

.consent-check.is-invalid {
	border: 1.5px solid var(--danger);
}

/* #280: the onboarding step marker (StepIndicator.tsx) — three numbered
   circles plus the "ขั้น n จาก 3" label. With no rules the spans collapse
   into "123ขั้น 3 จาก 3", which reads as one word; here each number gets its
   own fixed-size circle, the current step carries the accent fill (the one
   solid blue object rule), steps already passed fade, and the label sits
   apart from the digits. aria-current and the label text stay in the JSX. */
.step-indicator {
	display: flex;
	align-items: center;
	gap: 8px;
}

.step-dot {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: none;
	width: 26px;
	height: 26px;
	border: 1px solid var(--line-strong);
	border-radius: 50%;
	background: var(--panel);
	font-size: 12.5px;
	font-weight: 600;
	line-height: 1;
	color: var(--ink-muted);
}

.step-dot.is-current {
	border-color: var(--accent);
	background: var(--accent);
	color: var(--accent-contrast);
}

.step-dot.is-done {
	border-color: var(--line);
	background: none;
	color: var(--ink-faint);
}

.step-label {
	margin-left: 4px;
	font-size: 12.5px;
	font-weight: 500;
	color: var(--ink-muted);
}

/* A short state label above a heading ("STATUS: DECLINED") — quiet, uppercase,
   never competing with the heading it introduces. */
.eyebrow {
	font-size: 11px;
	font-weight: 500;
	line-height: 1;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--mark);
	margin: 0;
}

/* A document's own nature (sensitive data, a renewal). Separate from the
   admin `.tag` chip below on purpose: this one sits above a heading and is the
   one place a third and fourth hue are allowed — and only as a 1px border over
   a near-white fill. */
.doc-tag {
	display: inline-block;
	align-self: flex-start;
	font-size: 11px;
	font-weight: 600;
	line-height: 1;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	padding: 6px 9px;
	border-radius: 6px;
	border: 1px solid var(--line-strong);
	color: var(--ink-muted);
	background: var(--panel);
}

.doc-tag-sensitive {
	color: var(--danger);
	background: var(--danger-tint);
	border-color: var(--danger-line);
}

.doc-tag-renewal {
	color: #7a4a08;
	background: #fdf3e3;
	border-color: rgb(122 74 8 / 28%);
}

/* ---------- banners ---------- */

.banner {
	border-radius: var(--radius);
	border: 1px solid transparent;
	padding: 12px 14px;
	font-size: 13px;
}

.banner p:last-child {
	margin-bottom: 0;
}

.banner-success {
	background: var(--notice-bg);
	border-color: color-mix(in srgb, var(--accent) 30%, transparent);
	color: var(--notice-ink);
}

.banner-warning,
.banner-busy {
	background: var(--alert-bg);
	border-color: var(--alert-rule);
	color: var(--accent-ink);
}

/* Board 3b: the page-level error level the old banner set never had — a
   danger-tinted box with a bold title line above the explanation. Its title
   is the only red text; the body stays ink so it reads as instruction, not
   as more alarm. */
.banner-danger {
	background: var(--danger-tint);
	border-color: var(--danger-line);
	color: var(--ink-muted);
}

.banner-danger > :first-child {
	color: var(--danger);
	font-weight: 600;
	line-height: 1.4;
}

/* T-113 (D-055 ข้อ 10): the impersonation status bar — full-width, sticky,
   so the state stays in view while scrolling the impersonated account. */
.impersonation-banner {
	position: sticky;
	top: 0;
	z-index: 10;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-2);
	flex-wrap: wrap;
}

.impersonation-banner p {
	margin: 0;
}

.impersonation-banner .btn {
	font-size: 12px;
	padding: 6px 12px;
}

/* ---------- tags ---------- */

.tag {
	display: inline-flex;
	align-items: center;
	font-size: 11px;
	letter-spacing: 0.02em;
	padding: 3px 10px;
	border-radius: var(--radius-sm);
}

.tag-accent {
	background: var(--accent-100);
	color: var(--accent-800);
}

.tag-outline {
	border: 1px solid var(--accent);
	color: var(--accent-700);
}

/* ---------- form actions ---------- */

.form-actions {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
}

/* A button wrapped in its own <form> (each POST needs one) must still be the
   flex item, or it sizes to its text while an unwrapped <a class="btn"> beside
   it stretches — which is why the two sign-out buttons on /account/sessions
   were different widths. */
.form-actions form {
	display: contents;
}

@media (min-width: 30rem) {
	.form-actions {
		flex-direction: row;
		justify-content: flex-end;
	}
}

/* stacked actions inside cards stay full width on mobile */
.card .btn {
	width: 100%;
}

@media (min-width: 30rem) {
	.card .btn {
		width: auto;
		min-width: 10rem;
	}

	/* `min-width` must be reset too, not just `width`: `.card .btn` above
	   gives every button a 10rem floor, which three provider buttons on one
	   row (D-072) blow straight through — 3 × 160px overflows a 392px card,
	   and the row silently spills past its right edge from 480px up. The
	   two-column layout only ever fit by luck. */
	.card .btn-block,
	.card .provider-btn {
		width: 100%;
		min-width: 0;
	}
}

.btn-block {
	width: 100%;
}

/* ---------- language bar ----------
   Board 2026-09-10: a pill above the card that says which language you are
   reading and opens a small panel of the others. Built on <details> so it
   works with JavaScript off, which is also why the panel is absolutely
   positioned — it must not push the card down when it opens. */

.lang-bar {
	display: flex;
	/* Owner 2026-09-11: keep the pill above the card but pinned right,
	   matching the card's own --space-4 horizontal spacing below. */
	justify-content: flex-end;
	padding: var(--space-4) var(--space-4) 0;
}

.lang-switch {
	position: relative;
}

.lang-current {
	display: flex;
	align-items: center;
	gap: 7px;
	list-style: none;
	cursor: pointer;
	padding: 9px 13px;
	border-radius: 8px;
	background: var(--surface);
	border: 1px solid var(--line-strong);
	font-size: 13px;
	font-weight: 500;
	line-height: 1;
	color: var(--ink-muted);
}

/* Safari keeps its own disclosure triangle unless this is spelled out. */
.lang-current::-webkit-details-marker {
	display: none;
}

.lang-current b {
	font-weight: 600;
	color: var(--ink);
}

.lang-caret {
	font-size: 9px;
	line-height: 1;
	color: var(--ink-soft);
}

.lang-menu {
	position: absolute;
	z-index: 5;
	top: calc(100% + 6px);
	/* Anchored to the switch's right edge: with the bar pinned right, a
	   center-anchored menu could overflow the viewport's right edge. */
	right: 0;
	min-width: 11.6rem;
	background: var(--surface);
	border: 1px solid var(--line-strong);
	border-radius: 10px;
	box-shadow: var(--shadow-pop);
	padding: 6px;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.lang-menu a {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 10px;
	padding: 10px 11px;
	border-radius: var(--radius-sm);
	font-size: 13.5px;
	line-height: 1.2;
	color: var(--ink);
	text-decoration: none;
}

.lang-menu a:hover {
	background: color-mix(in srgb, var(--ink) 5%, transparent);
	color: var(--ink);
	opacity: 1;
}

.lang-menu a[aria-current="true"] {
	background: var(--accent-tint);
	color: var(--accent-ink);
	font-weight: 600;
}

.lang-check {
	font-size: 11px;
	font-weight: 600;
	color: var(--accent);
}

/* ---------- document shell (P27, T-056) ---------- */
/* Third shell: long-read layout for /legal/{key}, <= 720px (UX-SPEC §6.1).
   The article body is renderer output built only from escaped source text,
   so typography here is scoped to .doc-content and adds no new colors. */

.doc-shell {
	max-width: 45rem;
	margin: 0 auto;
	padding: var(--space-8) var(--space-4) 3rem;
	width: 100%;
	display: flex;
	flex-direction: column;
	gap: var(--space-6);
}

.doc-title {
	font-family: var(--font-heading);
	font-size: 27px;
	line-height: 1.15;
	margin: 0 0 var(--space-2);
}

.doc-meta {
	color: var(--ink-muted);
	font-size: 13px;
	margin: 0;
}

.doc-versions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-2) var(--space-3);
	font-size: 12px;
	border-top: 1px solid var(--line);
	margin-top: var(--space-4);
	padding-top: var(--space-4);
}

.doc-versions-label {
	color: var(--ink-muted);
}

.doc-version.is-current {
	color: var(--ink);
	font-weight: 700;
}

a.doc-version {
	color: var(--ink-muted);
	text-decoration: none;
}

a.doc-version:hover {
	color: var(--ink);
	opacity: 1;
}

.doc-toc {
	border: 1px solid var(--line);
	padding: var(--space-4) var(--space-6);
}

.doc-toc-title {
	font-family: var(--font-heading);
	font-size: 15px;
	margin: 0 0 var(--space-2);
}

.doc-toc ol {
	margin: 0;
	padding-left: 1.2rem;
	font-size: 13px;
}

.doc-toc a {
	color: var(--ink-muted);
}

.doc-toc a:hover {
	color: var(--ink);
	opacity: 1;
}

.doc-content {
	font-size: 15px;
	line-height: 1.7;
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
}

.doc-content > * {
	margin: 0;
}

.doc-content h2,
.doc-content h3,
.doc-content h4 {
	font-family: var(--font-heading);
	line-height: 1.25;
	margin-top: var(--space-4);
}

.doc-content h2 {
	font-size: 20px;
}

.doc-content h3 {
	font-size: 17px;
}

.doc-content ul,
.doc-content ol {
	padding-left: 1.4rem;
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
}

.doc-content blockquote {
	border-left: 2px solid var(--accent);
	padding: var(--space-2) var(--space-4);
	color: var(--ink-muted);
	background: var(--surface);
}

.doc-content code {
	font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
	font-size: 0.9em;
	border: 1px solid var(--line);
	padding: 0 4px;
}

.doc-content table {
	border-collapse: collapse;
	width: 100%;
	font-size: 14px;
	display: table;
}

.doc-content th,
.doc-content td {
	border: 1px solid var(--line);
	padding: var(--space-2) var(--space-3);
	text-align: left;
	vertical-align: top;
}

.doc-content th {
	font-family: var(--font-heading);
	background: var(--surface);
}

.doc-content hr {
	border: none;
	border-top: 1px solid var(--line);
}

.doc-content a {
	color: var(--accent-700);
}

/* Admin shell (T-073): reuses the account nav; a placeholder entry renders
   as a disabled link until its Wave 10b–10c page exists. */
.account-nav-link.is-disabled {
	opacity: 0.45;
	cursor: default;
}

.account-nav-link.is-disabled:hover {
	color: var(--ink-muted);
	background: none;
}

.account-nav-link.is-disabled small {
	display: block;
	font-family: var(--font);
	font-size: 11px;
	line-height: 1.2;
	color: var(--ink-muted);
}

.admin-role-line {
	font-family: var(--font-heading);
	font-size: 18px;
	color: var(--ink);
}

/* Full deployment identifiers remain readable on narrow admin screens. */
.admin-deployment-facts {
	display: grid;
	gap: var(--space-2);
	margin: 0;
}

.admin-deployment-facts dt {
	color: var(--ink-muted);
	font-size: 12px;
}

.admin-deployment-facts dd {
	margin: 0 0 var(--space-2);
	min-width: 0;
	overflow-wrap: anywhere;
}

.admin-deployment-facts code {
	font-size: 12px;
}

@media (min-width: 640px) {
	.admin-deployment-facts {
		grid-template-columns: 10rem minmax(0, 1fr);
	}
}

.admin-footer-note {
	color: var(--ink-muted);
	font-size: 13px;
}

/* Two row-level actions (reveal + unlock) sitting side by side (T-078). */
.admin-row-actions {
	display: inline-flex;
	gap: var(--space-2);
	flex-wrap: wrap;
}

/* ---------- admin pages (T-077): Audit + Ops ---------- */

/* Sub-navigation between the two audit surfaces (Data API / team actions). */
.admin-subnav {
	display: flex;
	gap: var(--space-2);
	margin: 0 0 var(--space-4);
}

.admin-subnav-link {
	font-family: var(--font-heading);
	font-size: 14px;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--ink-muted);
	text-decoration: none;
	padding: var(--space-1) 0;
	border-bottom: 2px solid transparent;
}

.admin-subnav-link:hover {
	color: var(--ink);
}

.admin-subnav-link.is-active {
	color: var(--ink);
	border-bottom-color: var(--accent);
}

/* Filter form: fields side by side on wide screens, one column on narrow. */
.admin-filter-form {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
	gap: var(--space-3);
	align-items: end;
}

.admin-filter-form .form-actions {
	margin: 0;
}

/* Result tables: wireframe rules like the legal documents (D-024), with a
   horizontal scroll instead of squeezing columns on narrow screens. */
.admin-table-wrap {
	overflow-x: auto;
	border: 1px solid var(--line);
}

.admin-table {
	border-collapse: collapse;
	width: 100%;
	font-size: 13px;
	white-space: nowrap;
}

.admin-table th,
.admin-table td {
	border: 1px solid var(--line);
	padding: var(--space-2) var(--space-3);
	text-align: left;
	vertical-align: top;
}

.admin-table thead th {
	font-family: var(--font-heading);
	font-size: 12px;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--ink-muted);
}

/* Metric tables on /admin/ops: label column fixed, value wide, note muted. */
.admin-table-metrics {
	white-space: normal;
}

.admin-table-metrics tbody th {
	font-weight: 400;
	color: var(--ink-muted);
	width: 40%;
}

.admin-table-metrics td {
	font-family: var(--font-heading);
}

.admin-note {
	color: var(--ink-muted);
	font-size: 12px;
	margin: var(--space-2) 0 0;
}

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

/* Admin clients pages (T-075): monospace runs inside rows/forms, and the
   one-time secret/key value block — high contrast, selectable, and visually
   loud enough that nobody mistakes it for ordinary text. */
.admin-mono {
	font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
	font-size: 12px;
	word-break: break-all;
}

.admin-secret-value {
	display: block;
	margin: var(--space-3) 0;
	padding: var(--space-3);
	font-size: 14px;
	letter-spacing: 0.04em;
	border: 1px solid var(--line);
	border-left: 3px solid var(--accent);
	background: var(--surface);
	user-select: all;
}

/* Admin ops switches (T-110): a row whose switch is OFF must be
   unmissable — red left edge plus a solid OFF badge, so an admin coming on
   shift reads the table's state column in one glance (task §5). */
.admin-table .ops-switch-off td,
.admin-table .ops-switch-off th {
	background: color-mix(in srgb, var(--danger) 7%, transparent);
}

.ops-switch-badge-off {
	display: inline-block;
	padding: 2px 10px;
	border-radius: 999px;
	background: var(--danger);
	color: var(--danger-contrast);
	font-size: 12px;
	letter-spacing: 0.04em;
}

.ops-switch-off form {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	max-width: 320px;
}

/* Account edits and removal reviews keep their forms readable when expanded. */
.form-disclosure,
.remove-review {
	min-width: 0;
	max-width: 100%;
}
.form-disclosure > summary,
.remove-review > summary {
	list-style: none;
	cursor: pointer;
}
.form-disclosure > summary::-webkit-details-marker,
.remove-review > summary::-webkit-details-marker {
	display: none;
}
.form-disclosure > form,
.remove-review > form,
.remove-review > .field-hint {
	margin-top: var(--space-3);
}
.row:has(> .form-disclosure[open]),
.row:has(> .remove-review[open]) {
	flex-wrap: wrap;
	align-items: flex-start;
}
.row > .form-disclosure[open],
.row > .remove-review[open] {
	flex-basis: 100%;
}

/* UX-05 follow-up (#322): one consistent native <details> disclosure for
   "one task per block" action rows (recommended class: security-task, used
   by the account security UI). Works with zero JavaScript; the summary is
   the whole hit area and keeps the global focus ring; the light --line is
   decoration, the summary text itself is full ink. */
.security-task {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--surface);
}

.security-task > summary {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-2);
	padding: var(--space-3) var(--space-4);
	font-weight: 600;
	color: var(--ink);
	cursor: pointer;
	list-style: none;
}

.security-task > summary::-webkit-details-marker {
	display: none;
}

.security-task[open] > summary::after {
	content: "▴";
}

.security-task[open] > summary {
	border-bottom: 1px solid var(--line);
}

/* Long account descriptions need their own line before actions on phones. */
@media (max-width: 30rem) {
	.account-content .row:has(> .row-main > .row-sub),
	.account-content .row:has(> .row-main > .row-long-text) {
		flex-wrap: wrap;
		align-items: flex-start;
	}
	.account-content .row-main:has(> .row-sub),
	.account-content .row-main:has(> .row-long-text) {
		flex-basis: 100%;
	}
	.account-content .row > .btn {
		max-width: 100%;
		white-space: normal;
	}
}
