/* The reusable widgets.
 *
 * Classes and app-shell element tags only. Every value comes from tokens.css —
 * no raw colours, no ad-hoc pixel gaps — and index.test.js enforces the colour
 * half of that, because a hex code typed inline is how a stylesheet starts
 * disagreeing with itself.
 *
 * WHY PLAIN CSS IN THE DOCUMENT, not Lit `css` templates in shadow roots. Page
 * components render into light DOM (`createRenderRoot()` returns `this`, which
 * they get from FwPage in site/ui.js) and read these rules from the document.
 * A shadow root anywhere above a component cuts it off from document
 * stylesheets, which is why <fw-router> is light DOM too — it sits above every
 * page.
 *
 * Encapsulation for page components comes from there being nothing to
 * encapsulate: the widgets below are shared on purpose, and a page that needs
 * something of its own should ask whether the something is really page-specific
 * before writing a rule for it. There are currently no per-page rules in this
 * file at all, and that is the target state rather than a coincidence.
 *
 * Genuine widgets with no relationship to the page — <fw-nav> — keep their
 * shadow roots and their own styles. They are widgets, and encapsulation is
 * what they want. They still read tokens.css, because custom properties
 * inherit through a shadow boundary.
 */

/* --- App shell -----------------------------------------------------------
 *
 * Widths are 100%, not 100vw. `100vw` includes the scrollbar gutter, so on any
 * desktop browser with a classic scrollbar every one of these was a few pixels
 * wider than the viewport — which is what the `overflow-x: hidden` that used to
 * sit on the page shell was hiding. body is a flex column, so 100% resolves
 * against its content box and is simply correct.
 */

fw-nav {
	width: 100%;
	box-sizing: border-box;
}

fw-router {
	display: flex;
	flex: 1;
	width: 100%;
	box-sizing: border-box;
	justify-content: center;
}

/* Applied by FwPage in site/ui.js, not listed by tag.
 *
 * This was a hand-maintained list of every page element — `fw-home, fw-login,
 * fw-signup, …` — with a comment admitting that a page whose tag was missing
 * would simply lay out wrong and nothing would say so. Extending a base class
 * cannot be forgotten the same way, and index.test.js checks that every routed
 * component does. */
.page {
	display: flex;
	flex-direction: column;
	align-items: center;
	width: 100%;
}

/* --- Header band --------------------------------------------------------- */

.header {
	width: 100%;
	box-sizing: border-box;
	display: flex;
	justify-content: center;
	padding: var(--space-9) var(--space-6) var(--space-8);
	background: var(--surface-1);
	border-bottom: 1px solid var(--line);
}

.header-inner {
	width: 100%;
	max-width: var(--content-width);
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
}

/* The small uppercase label above a page title. */
.eyebrow {
	font-size: var(--text-2xs);
	font-weight: var(--weight-medium);
	letter-spacing: var(--tracking-caps);
	text-transform: uppercase;
	color: var(--accent);
}

/* Pink into purple across the label.
 *
 * `width: fit-content` is load-bearing rather than tidiness: .eyebrow is a
 * block inside a flex column, so at full header width a short word like
 * "Account" would sit entirely in the first few percent of the gradient and
 * render flat pink.
 *
 * Behind @supports because the transparent fill and the clip are separate
 * features — a browser with -webkit-text-fill-color but no background-clip:text
 * would paint the text transparent over nothing and lose it completely. */
@supports (background-clip: text) or (-webkit-background-clip: text) {
	.eyebrow {
		width: fit-content;
		background-image: var(--accent-gradient);
		-webkit-background-clip: text;
		background-clip: text;
		-webkit-text-fill-color: transparent;
	}
}

/* Windows High Contrast replaces the background but keeps the transparent
   fill, which would leave the label invisible. Hand it back to the system. */
@media (forced-colors: active) {
	.eyebrow {
		background-image: none;
		-webkit-text-fill-color: currentColor;
	}
}

/* --- Sections ------------------------------------------------------------ */

.section {
	width: 100%;
	box-sizing: border-box;
	padding: var(--space-9) var(--space-6);
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-6);
}

.inner {
	width: 100%;
	max-width: var(--content-width);
	display: flex;
	flex-direction: column;
	gap: var(--space-5);
}

/* --- Card ----------------------------------------------------------------
 *
 * A raised panel one step above the page's near-black background, separated
 * from it by a hairline rather than a shadow. On this site a card means "there
 * is something to do in here"; read-only facts belong in the header band.
 */
.card {
	background: var(--surface-1);
	border: 1px solid var(--line);
	border-radius: var(--radius-lg);
	padding: var(--space-5) var(--space-6);
	box-sizing: border-box;
	display: flex;
	flex-direction: column;
	gap: var(--space-5);
}

/* --- Buttons -------------------------------------------------------------
 *
 * One base plus two independent axes:
 *
 *   .quiet    outlined instead of filled — a secondary action
 *   .danger   destructive, so it is carmine instead of the accent
 *
 * They compose. `.btn.quiet.danger` is an outlined destructive button, which is
 * what the account page's Remove has always asked for in its markup and never
 * got: `.btn.danger` used to win the cascade and paint a solid carmine fill
 * with a stray grey border around it.
 *
 * The border is `1px solid transparent` on the base rather than absent, so a
 * quiet button is exactly as tall as a filled one. It was `0 solid`, which made
 * every outlined button 2px taller than the filled button beside it in the same
 * .actions row.
 *
 * Any variant that replaces the fill must clear background-image too: the base
 * paints the accent gradient, and a background-color alone is painted *under*
 * an image and never shows. Every rule below does; if you add one, it must.
 */
.btn {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-1);
	padding: var(--space-3) var(--space-4);
	min-width: 60px;
	box-sizing: border-box;
	border: 1px solid transparent;
	border-radius: var(--radius-sm);
	background-color: var(--accent);
	background-image: var(--accent-gradient);
	color: var(--fg-on-accent);
	font-size: var(--text-base);
	font-family: inherit;
	cursor: pointer;
	user-select: none;
	-webkit-user-select: none;
	/* Comma-separated. This was space-separated, which is invalid and made the
	   whole declaration drop, so nothing transitioned at all. */
	transition-property: border-color, background-color, color;
	transition-duration: var(--motion);
}

.btn:hover:not([disabled]) {
	background-color: var(--accent-hi);
	background-image: var(--accent-gradient-hi);
}

/* A near-white label sits at 3.2:1 on the pink end of the gradient and 2.7:1 on
   the purple, so the ring is the focus cue and it cannot be dropped. */
.btn:focus-visible {
	outline: 2px solid var(--fg-1);
	outline-offset: var(--focus-offset);
}

/* The fill has to go, not just the label. This used to override only the
   colour, leaving muted grey text on the full-strength accent fill — 1.05:1,
   which is unreadable rather than merely inactive. */
.btn[disabled] {
	background-color: var(--surface-raised-hi);
	background-image: none;
	color: var(--fg-3);
	border-color: transparent;
	cursor: not-allowed;
}

.btn.quiet:not([disabled]) {
	background-color: transparent;
	background-image: none;
	border-color: var(--fg-3);
	color: var(--fg-1);
}

.btn.quiet:hover:not([disabled]) {
	background-color: var(--surface-raised);
	background-image: none;
	border-color: var(--fg-1);
}

/* Dark label on the fill, as on the accent — a near-white one reaches only 3:1
   against this red. */
.btn.danger:not([disabled]) {
	background-color: var(--alarm);
	background-image: none;
	border-color: transparent;
	color: var(--fg-on-accent);
}

.btn.danger:hover:not([disabled]) {
	background-color: var(--alarm);
	background-image: none;
	opacity: 0.85;
}

/* (0,4,0) against .btn.danger's (0,3,0), so the composition wins wherever the
   two rules sit in the file. */
.btn.quiet.danger:not([disabled]) {
	background-color: transparent;
	background-image: none;
	border-color: var(--alarm);
	color: var(--alarm);
	opacity: 1;
}

.btn.quiet.danger:hover:not([disabled]) {
	background-color: var(--alarm-tint);
	background-image: none;
	opacity: 1;
}

/* --- Badges --------------------------------------------------------------
 *
 * A short state marker attached to something else: "synced" beside a passkey,
 * "EXPERIMENTAL" beside a figure off the pre-gate viscous path.
 *
 * One badge with four tones, replacing two half-overlapping systems. There was
 * a marram `.pill` and an amber `.experimental`, and because `.experimental`
 * was the only one with an attention colour it had been pressed into service
 * for "Used", "Only one passkey", "None set" and "Shown once" — none of which
 * are experimental anything. The tone now says the weight and the word says the
 * meaning, which is the only arrangement that stays true as pages are added.
 *
 * Built exactly like a message box — tint fill, 1px border in the colour, text
 * in the colour — so a state reads the same whichever shape carries it.
 */
.badge {
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
	padding: var(--space-hair) var(--space-2);
	border: 1px solid var(--line);
	border-radius: var(--radius-pill);
	background: var(--surface-raised);
	color: var(--fg-2);
	/* em, not rem, and the one deliberate exception to the type scale: a badge
	   qualifies the text it sits in, so it has to stay in proportion to it —
	   inside an <h2> as readily as inside a <p>. See tokens.css. */
	font-size: 0.72em;
	font-weight: var(--weight-medium);
	letter-spacing: var(--tracking-wide);
	text-transform: uppercase;
	white-space: nowrap;
	vertical-align: middle;
}

.badge.positive {
	color: var(--positive);
	background: var(--positive-tint);
	border-color: var(--positive);
}

.badge.attention {
	color: var(--attention);
	background: var(--attention-tint);
	border-color: var(--attention);
}

.badge.alarm {
	color: var(--alarm);
	background: var(--alarm-tint);
	border-color: var(--alarm);
}

/* --- Messages ------------------------------------------------------------
 *
 * Something the interface says has happened. Two axes, as with buttons:
 *
 *   tone     .error   it failed
 *            .notice  it worked
 *   scope    block    (default) it is about the page or the card
 *            .inline  it is about one field, so it is sized and placed to
 *                     belong to that field rather than to the card
 *
 * This was three unrelated treatments. A `.error` was a bordered box; a second
 * `form .error` rule painted the same colours flat and borderless, so the same
 * failure looked like two different things depending on whether it happened
 * inside a <form>; a field error was bare red text with nothing around it; and
 * "Passkey added." came out as `.hint`, identical to the paragraph of standing
 * prose beside it.
 *
 * `.hint` below is deliberately not part of this. It is standing explanation
 * rather than an event, so it has no box and no colour and does not compete
 * with these for attention.
 *
 * `overflow-wrap` is load-bearing: the account page puts raw server text into a
 * message, and one unbroken token would otherwise push a card wider than the
 * viewport. `margin: 0` because every container these land in is a flex column
 * with its own gap — the old `margin: 0 0 16px` added to that gap and left the
 * box with 20px above it and 36px below.
 */
.msg {
	display: block;
	padding: var(--space-3) var(--space-4);
	border: 1px solid;
	border-radius: var(--radius-md);
	font-size: var(--text-sm);
	line-height: var(--leading-snug);
	overflow-wrap: anywhere;
	margin: 0;
}

.msg.error {
	color: var(--alarm);
	background: var(--alarm-tint);
	border-color: var(--alarm);
}

.msg.notice {
	color: var(--positive);
	background: var(--positive-tint);
	border-color: var(--positive);
}

/* Scoped to a field: one step smaller, and it stops at its own text. A
   full-width bar under a field reads as a page-level failure, which is the
   opposite of what a field message is saying.
   It renders inside a `.field` label, which is a flex column, hence align-self
   rather than a float. The side margins are the input's own text padding, so
   the message lines up with the value it is about rather than with the edge of
   the field — and because those margins are outside the box, a plain
   `max-width: 100%` would let an unbreakable token sit two pads past it. */
.msg.inline {
	display: inline-flex;
	align-self: flex-start;
	box-sizing: border-box;
	max-width: calc(100% - var(--space-4) - var(--space-4));
	margin: var(--space-2) var(--space-4) var(--space-3);
	padding: var(--space-1) var(--space-3);
	font-size: var(--text-xs);
}

/* Standing explanation. Body copy that has opted out of being a <p> in a
   layout, or that carries a badge. */
.hint {
	color: var(--fg-3);
	font-size: var(--text-sm);
	line-height: var(--leading-normal);
	margin: 0;
}

/* --- Fields --------------------------------------------------------------
 *
 * A labelled control. The <label> wraps its input rather than pointing at it by
 * id, so there is no id to keep unique and the whole block is a click target.
 * Rendered by field() in site/ui.js — the placement of an inline message
 * depends on this structure, so hand-rolling one is how the two drift apart.
 */
.field {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	background-color: var(--surface-raised);
	border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}

.field > span {
	display: inline-flex;
	align-items: baseline;
	gap: var(--space-1);
	padding: var(--space-4);
	user-select: none;
	-webkit-user-select: none;
}

/* An inline qualifier inside a label — "(only if …)". Quieter and smaller than
   the label, so the field still reads as one thing. `em` for the same reason a
   badge is in em: it is proportional to the text it qualifies. */
.field > span em {
	font-style: normal;
	font-weight: var(--weight-normal);
	font-size: 0.85em;
	color: var(--fg-3);
}

/* --- Rows ----------------------------------------------------------------
 *
 * A list of facts with actions on the right: the account page's passkeys, and
 * anything else shaped like them.
 */
.row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
	flex-wrap: wrap;
}

.row + .row {
	border-top: 1px solid var(--line);
	padding-top: var(--space-4);
}

.row-main {
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
	min-width: 0;
}

.row-title {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	flex-wrap: wrap;
	color: var(--fg-1);
	font-weight: var(--weight-medium);
	overflow-wrap: anywhere;
}

.row-sub {
	font-size: var(--text-xs);
	color: var(--fg-3);
}

/* A cluster of buttons. Used inside a row and on its own. */
.actions {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	flex-wrap: wrap;
}

/* --- Numeric output ------------------------------------------------------
 *
 * Coefficients are read by comparing them down a column, so they are tabular
 * and monospaced. Without tabular-nums a changing digit shifts the ones beside
 * it, which makes a live-updating Cl look like it is twitching.
 */
.num {
	font-family: var(--font-mono);
	font-variant-numeric: tabular-nums;
}

/* --- Recovery key --------------------------------------------------------
 *
 * Shown exactly once and meant to be transcribed onto paper, so it is set
 * large, monospaced and wrapping — a key that overflows its box on a phone is
 * one someone copies wrong. `user-select: all` makes a single click take the
 * whole thing, since selecting a hyphenated 39-character string by dragging is
 * a way to lose the last group.
 */
.recovery-key {
	padding: var(--space-4);
	background: var(--surface-inset);
	border: 1px solid var(--line);
	border-radius: var(--radius-md);
	font-size: var(--text-lg);
	line-height: var(--leading-loose);
	letter-spacing: var(--tracking-wide);
	word-break: break-all;
	white-space: pre-wrap;
	user-select: all;
	-webkit-user-select: all;
}

/* --- Utilities ----------------------------------------------------------- */

/* Available to a screen reader, absent from the page. */
.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}
