/* ── Theme tokens ─────────────────────────────────────────────────────────
   Base = light. The OS preference supplies dark automatically; an explicit
   [data-theme] on <html> (set server-side from the cookie, toggled by JS)
   overrides the OS in either direction. */
/* Light = warm parchment + sage green; dark = moonlit castle green-black + gold. */
:root {
	--bg: #faf8f3;
	--surface: #ffffff;
	--surface-2: #efeee6;
	--text: #262a20;
	--muted: #6a7161;
	--border: #e3e1d6;
	--primary: #567a4d;
	--primary-text: #ffffff;
	--primary-hover: #46683e;
	--danger: #b42318;
	--danger-bg: #fdecea;
	--success: #3f7d44;
	--success-bg: #e7f2e4;
	--info: #175cd3;
	--info-bg: #eaf1fd;
	--warn: #7c5b00;
	--warn-bg: #fbf1d6;
	--ring: rgba(86, 122, 77, 0.3);
	--radius: 10px;
	--shadow: 0 1px 3px rgba(40, 44, 30, 0.08), 0 1px 2px rgba(40, 44, 30, 0.04);
	--sidebar-bg: #f3f1e9;
	--sidebar-text: #4b5142;
	--sidebar-active-bg: #e6ebe0;
	--sidebar-active-text: #3d5c37;
	--sidebar-border: #e3e1d6;
	--brand: #b8860b;
	/* Halo behind the copyright line, which sits straight on the background
	   photo. Per theme, because it has to contrast with the TEXT colour, not
	   with the image: a pale halo behind dark text, a dark one behind light. */
	--foot-glow: 0 0 4px #faf8f3, 0 0 9px #faf8f3, 0 0 16px #faf8f3;
}

@media (prefers-color-scheme: dark) {
	:root {
		--bg: #10140e;
		--surface: #171d15;
		--surface-2: #1f261c;
		--text: #e5e9dd;
		--muted: #98a08b;
		--border: #2a3226;
		--primary: #6a9a5f;
		--primary-text: #0f150d;
		--primary-hover: #79aa6e;
		--danger: #ff6b60;
		--danger-bg: #2a1614;
		--success: #6cbf78;
		--success-bg: #14261a;
		--info: #6ba4ff;
		--info-bg: #101c2e;
		--warn: #ffcf5a;
		--warn-bg: #3a2e0c;
		--ring: rgba(106, 154, 95, 0.4);
		--shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
		--sidebar-bg: #0c110a;
		--sidebar-text: #b1bba4;
		--sidebar-active-bg: #1a271a;
		--sidebar-active-text: #d4b96a;
		--sidebar-border: #222a1f;
		--brand: #d4af37;
		--foot-glow: 0 0 3px rgba(0,0,0,.9), 0 0 9px rgba(0,0,0,.75), 0 0 16px rgba(212,175,55,.45), 0 0 28px rgba(212,175,55,.22);
	}
}

/* Explicit overrides beat the media query (higher specificity). */
:root[data-theme='light'] {
	color-scheme: light;
	--bg: #faf8f3; --surface: #fff; --surface-2: #efeee6; --text: #262a20;
	--muted: #6a7161; --border: #e3e1d6; --primary: #567a4d; --primary-text: #fff; --primary-hover: #46683e;
	--danger: #b42318; --danger-bg: #fdecea; --success: #3f7d44; --success-bg: #e7f2e4;
	--info: #175cd3; --info-bg: #eaf1fd; --warn: #7c5b00; --warn-bg: #fbf1d6; --ring: rgba(86,122,77,.3);
	--shadow: 0 1px 3px rgba(40,44,30,.08), 0 1px 2px rgba(40,44,30,.04);
	--sidebar-bg: #f3f1e9; --sidebar-text: #4b5142; --sidebar-active-bg: #e6ebe0;
	--sidebar-active-text: #3d5c37; --sidebar-border: #e3e1d6; --brand: #b8860b;
	--foot-glow: 0 0 4px #faf8f3, 0 0 9px #faf8f3, 0 0 16px #faf8f3;
}
:root[data-theme='dark'] {
	color-scheme: dark;
	--bg: #10140e; --surface: #171d15; --surface-2: #1f261c; --text: #e5e9dd;
	--muted: #98a08b; --border: #2a3226; --primary: #6a9a5f; --primary-text: #0f150d; --primary-hover: #79aa6e;
	--danger: #ff6b60; --danger-bg: #2a1614; --success: #6cbf78; --success-bg: #14261a;
	--info: #6ba4ff; --info-bg: #101c2e; --warn: #ffcf5a; --warn-bg: #3a2e0c; --ring: rgba(106,154,95,.4);
	--shadow: 0 1px 3px rgba(0,0,0,.5);
	--sidebar-bg: #0c110a; --sidebar-text: #b1bba4; --sidebar-active-bg: #1a271a;
	--sidebar-active-text: #d4b96a; --sidebar-border: #222a1f; --brand: #d4af37;
	--foot-glow: 0 0 3px rgba(0,0,0,.9), 0 0 9px rgba(0,0,0,.75), 0 0 16px rgba(212,175,55,.45), 0 0 28px rgba(212,175,55,.22);
}

* { box-sizing: border-box; }
/* An author `display` beats the UA stylesheet's [hidden] rule, so anything with
   a display of its own (.divider is a flex row) would ignore the attribute. */
[hidden] { display: none !important; }
html, body { margin: 0; padding: 0; }
body {
	background: var(--bg);
	color: var(--text);
	font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
	min-height: 100vh;
}

.container { width: 100%; max-width: 60rem; margin: 0 auto; padding: 0 1rem; }
main.container { flex: 1 0 auto; padding-top: 2rem; padding-bottom: 3rem; }

a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── Header / footer ─────────────────────────────────────────────────────── */
.site-header { background: var(--surface); border-bottom: 1px solid var(--border); }
.header-inner { display: flex; align-items: center; justify-content: space-between; height: 3.5rem; }
.brand { font-weight: 700; color: var(--text); }
.brand:hover { text-decoration: none; }
.site-nav { display: flex; align-items: center; gap: 1rem; }
.site-nav a { color: var(--muted); font-size: .95rem; }
.site-nav a:hover { color: var(--text); text-decoration: none; }
.site-footer { flex-shrink: 0; border-top: 1px solid var(--border); color: var(--muted); font-size: .85rem; padding: 1.25rem 0; }
.site-footer a { color: var(--muted); }

.inline { display: inline; margin: 0; }
.linkish { background: none; border: 0; color: var(--muted); cursor: pointer; font: inherit; font-size: .95rem; padding: 0; }
.linkish:hover { color: var(--text); text-decoration: underline; }

.theme-toggle {
	width: 2rem; height: 2rem; border-radius: 999px; border: 1px solid var(--border);
	background: var(--surface-2); cursor: pointer; display: inline-grid; place-items: center;
}
.theme-toggle:hover { border-color: var(--primary); }
.theme-icon::before { content: '🌓'; font-size: 1rem; line-height: 1; }
:root[data-theme='light'] .theme-icon::before { content: '☀️'; }
:root[data-theme='dark'] .theme-icon::before { content: '🌙'; }

/* ── Cards / auth layout ─────────────────────────────────────────────────── */
.card {
	background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--radius); box-shadow: var(--shadow); padding: 1.75rem;
}
.auth-card { max-width: 26rem; margin: 1rem auto; }
.auth-card h1 { margin: 0 0 .25rem; font-size: 1.5rem; }
.auth-sub { color: var(--muted); margin: 0 0 1.25rem; }
.stack > * + * { margin-top: 1rem; }

h1 { font-size: 1.75rem; }
.page-title { margin-top: 0; }

/* ── Forms ───────────────────────────────────────────────────────────────── */

/* The box every text-like control wears, applied by TYPE rather than by being
   inside .field. Scoping it to .field is what left the search boxes, the merge
   and passkey-name fields and the 2FA code inputs looking like raw browser
   widgets — `select` had a global rule and these did not. Types are listed
   explicitly rather than excluded with :not(), so a control only ever picks this
   up on purpose: checkboxes, radios, files and buttons are left alone. */
input[type=text], input[type=email], input[type=password], input[type=search],
input[type=url], input[type=tel], input[type=number], input[type=date],
input[type=datetime-local], input[type=time], input[type=month], input[type=week],
input:not([type]), textarea {
	padding: .6rem .7rem; border: 1px solid var(--border); border-radius: 8px;
	/* background-COLOR, not the shorthand: the shorthand would wipe out the
	   chevron background-image the `select` rule below draws. */
	background-color: var(--surface); color: var(--text); font: inherit;
	/* Some browsers ship a narrower default font for form controls, which made
	   inputs sit a pixel or two shorter than the buttons beside them. */
	line-height: 1.4; max-width: 100%;
}
textarea { min-height: 5rem; resize: vertical; }
input::placeholder, textarea::placeholder { color: var(--muted); opacity: .8; }

input:focus, textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--ring); }

/* Read-only and disabled read as "not for typing in" rather than as a field
   that happens to ignore you. */
input:read-only:not([type=checkbox]):not([type=radio]), textarea:read-only { background-color: var(--surface-2); }
input:disabled, textarea:disabled, select:disabled { background-color: var(--surface-2); color: var(--muted); cursor: not-allowed; }

.field { display: flex; flex-direction: column; gap: .35rem; }
.field label { font-weight: 600; font-size: .9rem; }
.field .req { color: var(--danger); }
/* Inside a labelled field the control fills the column; the box itself comes
   from the rules above. */
.field input, .field select, .field textarea { width: 100%; }
.field select { padding-right: 2rem; }
.field-invalid input { border-color: var(--danger); }
.field .hint { color: var(--muted); font-size: .8rem; }
.field .error { color: var(--danger); font-size: .82rem; }

/* Selects wear the same box as text inputs and buttons — same height, border,
   radius and focus ring. appearance:none drops the native widget, so the
   chevron is drawn in; its grey reads on both light and dark surfaces. */
select {
	appearance: none; -webkit-appearance: none;
	padding: .6rem 2rem .6rem .7rem;
	border: 1px solid var(--border); border-radius: 8px;
	background-color: var(--surface); color: var(--text); font: inherit;
	background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%23888f98' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
	background-repeat: no-repeat; background-position: right .65rem center; background-size: 11px 7px;
	cursor: pointer;
}
select:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--ring); }

.btn {
	display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
	padding: .6rem 1rem; border-radius: 8px; border: 1px solid transparent;
	font: inherit; font-weight: 600; cursor: pointer;
}
.btn-primary { background: var(--primary); color: var(--primary-text); }
.btn-primary:hover { background: var(--primary-hover); text-decoration: none; }
.btn-block { width: 100%; }
.btn-secondary { background: var(--surface-2); color: var(--text); border-color: var(--border); }
.btn-secondary:hover { border-color: var(--primary); text-decoration: none; }
.btn-danger { background: var(--danger-bg); color: var(--danger); border-color: transparent; }

.form-aux { display: flex; justify-content: space-between; font-size: .88rem; margin-top: .25rem; }

/* ── Alerts ──────────────────────────────────────────────────────────────── */
.alert { padding: .7rem .9rem; border-radius: 8px; margin-bottom: 1rem; font-size: .92rem; border: 1px solid transparent; }
.alert-error { background: var(--danger-bg); color: var(--danger); }
.alert-success { background: var(--success-bg); color: var(--success); }
.alert-info { background: var(--info-bg); color: var(--info); }

/* ── Social buttons ──────────────────────────────────────────────────────── */
.divider { display: flex; align-items: center; gap: .75rem; color: var(--muted); font-size: .82rem; margin: 1.25rem 0; }
.divider::before, .divider::after { content: ''; flex: 1; height: 1px; background: var(--border); }
.social { display: grid; gap: .6rem; }
.social .btn { width: 100%; }
.provider-google { background: #fff; color: #1b1d22; border-color: var(--border); }
.provider-discord { background: #5865f2; color: #fff; }
.provider-twitter { background: #000; color: #fff; }
.provider-github { background: #1f2328; color: #fff; }
.provider-facebook { background: #1877f2; color: #fff; }
.provider-twitch { background: #9146ff; color: #fff; }

/* ── Account ─────────────────────────────────────────────────────────────── */
.account-grid { display: grid; gap: 1.25rem; grid-template-columns: 1fr; }
@media (min-width: 44rem) { .account-grid { grid-template-columns: 1fr 1fr; } .account-grid .span-2 { grid-column: 1 / -1; } }
.avatar { width: 96px; height: 96px; border-radius: 50%; object-fit: cover; border: 1px solid var(--border); background: var(--surface-2); }
.avatar-row { display: flex; align-items: center; gap: 1rem; }
.card h2 { margin-top: 0; font-size: 1.15rem; }
.muted { color: var(--muted); }
.list-reset { list-style: none; padding: 0; margin: 0; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .6rem 0; border-bottom: 1px solid var(--border); }
.row-between:last-child { border-bottom: 0; }
.badge { font-size: .72rem; padding: .1rem .5rem; border-radius: 999px; background: var(--surface-2); color: var(--muted); border: 1px solid var(--border); }

.consent-scopes { margin: 1rem 0; }
.consent-scopes li { padding: .3rem 0; }
.actions { display: flex; gap: .75rem; margin-top: 1.25rem; }

/* ── Admin console ─────────────────────────────────────────────────────────── */
/* A tab bar on its own surface. The old version was transparent with just an
   underline, which left the labels sitting straight on the background scene and
   barely readable. Selected state matches the sidebar's. */
.admin-bar {
	display: flex; align-items: center; flex-wrap: wrap; gap: .5rem;
	background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--radius); padding: .3rem;
	margin-bottom: 1.25rem;
}
.admin-bar-title { font-size: 1.1rem; margin: 0; padding: 0 .3rem 0 .55rem; }
/* Links sit hard right of the title. */
.admin-nav { display: flex; flex-wrap: wrap; gap: .2rem; margin-left: auto; }
.admin-nav a { padding: .45rem .8rem; border-radius: 8px; color: var(--muted); font-size: .92rem; font-weight: 500; }
.admin-nav a:hover { color: var(--text); background: var(--surface-2); text-decoration: none; }
.admin-nav a.active { color: var(--sidebar-active-text); background: var(--sidebar-active-bg); font-weight: 600; }
.admin-search { display: flex; gap: .5rem; align-items: center; margin-bottom: 1rem; }
.admin-search input[type=search] { flex: 1 1 16rem; max-width: 22rem; }
.admin-search .count { margin-left: auto; }

/* The table sits on a flush panel rather than inside a .card: it still needs an
   opaque backdrop to stay readable over the background scene, but the card's
   1.75rem of padding was stealing the width the columns needed — enough that
   "Password" and "Login as" wrapped onto two lines. */
/* No overflow-x at desktop widths: the table has to fit. Dropping the separate
   email column (it moved under the name) and swapping the sign-in text badges
   for icons freed the width to make that hold. Narrow screens drop columns and
   get a scrollbar on the panel — see the responsive block at the end. */
.table-panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.admin-table { width: 100%; border-collapse: collapse; }
.admin-table th, .admin-table td { text-align: left; padding: .7rem .85rem; border-bottom: 1px solid var(--border); vertical-align: middle; }
.admin-table thead th {
	background: var(--surface-2); color: var(--muted);
	font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em;
	white-space: nowrap;
}
.admin-table thead th a { color: inherit; }
.admin-table tbody tr:hover { background: var(--surface-2); }
.admin-table tr:last-child td { border-bottom: 0; }
.admin-table .nowrap { white-space: nowrap; }
.admin-table td .badge { white-space: nowrap; }
.admin-table thead .th-sep { opacity: .5; margin: 0 .1rem; }

/* User cell: name + badges on one line, email beneath. The email is the only
   thing here long enough to force a scrollbar, so it is allowed to wrap. */
.admin-table .user-cell { width: 40%; }
.u-line { display: flex; align-items: center; flex-wrap: wrap; gap: .35rem; }
.u-email { color: var(--muted); font-size: .8rem; margin-top: .15rem; overflow-wrap: anywhere; }
.u-unverified { color: var(--danger); }

/* Stacked endpoint lines (dashboard / webhook), label then value. URLs wrap
   rather than widen the table. */
.ep { display: flex; gap: .4rem; overflow-wrap: anywhere; }
.ep + .ep { margin-top: .15rem; }
/* ── Overview charts ──────────────────────────────────────────────────────
   Server-rendered inline SVG: no chart library, nothing for the CSP to block,
   and no client JS. The bars stretch to the card width, so every dimension in
   the markup is in viewBox units rather than pixels. */
.chart-wrap { margin-top: .35rem; }
.chart { display: block; width: 100%; height: 72px; overflow: visible; }
.chart-bar { fill: var(--primary); }
.chart-bar.is-danger { fill: var(--danger); }
/* A day with nothing on it still draws its 1-unit stub, as a baseline rather
   than as a bar that happens to be tiny. */
.chart-bar.is-zero { fill: var(--border); }
.chart-bar:hover { fill: var(--primary-hover); }
.chart-bar.is-danger:hover { opacity: .8; }
.chart-area { fill: var(--primary); opacity: .14; }
.chart-line { fill: none; stroke: var(--primary); stroke-width: 1.5px; stroke-linejoin: round; }
.chart-axis {
	display: flex; justify-content: space-between; align-items: baseline;
	margin-top: .35rem; color: var(--muted); font-size: .75rem;
}
.chart-peak { font-variant-numeric: tabular-nums; }

/* Audit/activity details: key-value pairs, the widest cell on the row after the
   two email columns. Claiming a share up front stops the table handing it the
   leftovers and wrapping it one character per line. */
.admin-table .meta-cell { width: 22%; overflow-wrap: anywhere; }

/* Email addresses are a single unbreakable token, so their min-content width
   wins every fight for table space and squeezes the rest to nothing. Same
   treatment as .u-email in the users table: let them break anywhere. */
.admin-table .who-cell { overflow-wrap: anywhere; }

/* Narrow leading order column — as wide as its digits and no wider. */
.admin-table .col-order { width: 1%; white-space: nowrap; text-align: right; }

/* Breadcrumb heading: Admin » Section » This thing. Carries the same bar as the
   admin nav — detail pages have no nav, and bare muted links were unreadable
   sitting straight on the background scene. */
.crumbs {
	display: flex; align-items: center; flex-wrap: wrap; gap: .4rem;
	font-size: 1.1rem; margin: 0 0 1.25rem;
	background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--radius); padding: .5rem .8rem;
}
/* inline-flex so a crumb carrying a service icon aligns with its text. */
.crumbs a { display: inline-flex; align-items: center; gap: .35rem; color: var(--muted); font-weight: 500; }
.crumbs a:hover { color: var(--text); }
.crumb-sep { color: var(--muted); opacity: .6; }
/* Flex containers collapse the whitespace between child nodes, so spacing
   inside a crumb (icon, name, muted key) comes from the gap instead. */
.crumb-current { display: inline-flex; align-items: center; gap: .35rem; }
.crumbs .service-icon { margin-right: 0; }

/* ── Admin overview ──────────────────────────────────────────────────────────
   Headline numbers, so stat tiles rather than charts. The bars rank magnitude in
   a single hue — they are not distinct series, so there is no categorical
   palette here and no legend to go with one. */
.stat-hero { display: flex; align-items: center; flex-wrap: wrap; gap: 1.5rem 2.5rem; }
.hero-figure { flex: none; }
/* Proportional figures: tabular-nums would make a big number look loose. */
.hero-value { font-size: 3rem; font-weight: 600; line-height: 1.05; }
.kpi-row { display: flex; flex-wrap: wrap; gap: 1.5rem 2.25rem; margin-left: auto; }
.stat-tile { min-width: 7rem; }
.stat-value { font-size: 1.6rem; font-weight: 600; line-height: 1.15; }
.stat-label { color: var(--muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .05em; }
.stat-sub { color: var(--muted); font-size: .8rem; margin-top: .1rem; }
.stat-list li { padding: .3rem 0; }
.stat-list li + li { border-top: 1px solid var(--border); }

.bar-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.bar-list li { display: grid; grid-template-columns: 9rem 1fr auto; align-items: center; gap: .6rem; }
.bar-key { display: inline-flex; align-items: center; gap: .4rem; min-width: 0; }
.bar-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Wider key column for longer labels (e.g. "Service · Tier"). */
.bar-list-wide li { grid-template-columns: minmax(0, 11rem) 1fr auto; }
/* Track is a lighter step of the fill's own hue, so the bar reads across its
   whole length. Mark caps at 10px with a 4px rounded data-end. */
.bar-track { background: var(--surface-2); border-radius: 5px; height: 10px; overflow: hidden; }
.bar-fill { display: block; height: 100%; background: var(--primary); border-radius: 0 4px 4px 0; }
.bar-value { font-variant-numeric: tabular-nums; color: var(--muted); font-size: .85rem; }

/* Linked social accounts, with the admin repair actions under each. */
.identity-list li { padding: .5rem 0; }
.identity-list li + li { border-top: 1px solid var(--border); }
.identity-actions { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; margin-top: .35rem; }
.identity-actions input { max-width: 15rem; }

/* Action under a list (e.g. "+ New service"), centred. */
.list-actions { display: flex; justify-content: center; margin-top: 1.25rem; }

/* One redirect URI per line, wrapping rather than widening the table. */
.uri { overflow-wrap: anywhere; }
.uri + .uri { margin-top: .15rem; }
.ep-label { flex: none; width: 4.75rem; color: var(--muted); }
.icon-fallback {
	display: inline-grid; place-items: center; flex: none;
	width: 22px; height: 22px; border-radius: 6px; margin-right: .4rem;
	background: var(--surface-2); color: var(--muted);
	font-size: .72rem; font-weight: 700;
}

/* Brand glyphs for sign-in methods, in place of text badges. */
.provider-icons { display: inline-flex; align-items: center; gap: .45rem; flex-wrap: wrap; }
.provider-icon { display: block; flex: none; }
.pi-password { color: var(--muted); }
.pi-google { color: #4285f4; }
.pi-discord { color: #5865f2; }
.pi-facebook { color: #1877f2; }
.pi-twitch { color: #9146ff; }
/* X and GitHub are black on white / white on black — follow the text colour. */
.pi-twitter, .pi-github { color: var(--text); }
.pager { display: flex; gap: 1rem; align-items: center; justify-content: center; margin-top: 1rem; }
/* Both banners sit in the normal flow inside .content, and .content-bg is a
   positioned element — without a stacking position of their own the scene's top
   half paints straight over them. */
.impersonation-banner { position: relative; z-index: 1; background: var(--info-bg); color: var(--info); border-bottom: 1px solid var(--border); }
.impersonation-banner .container { display: flex; align-items: center; justify-content: space-between; padding: .5rem 1rem; }
.confirm-banner { position: relative; z-index: 1; background: var(--warn-bg, #7c5b00); color: var(--warn, #ffcf5a); border-bottom: 1px solid var(--border); }
.confirm-banner .container { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; padding: .5rem 1rem; }
.confirm-banner-actions { display: flex; align-items: center; gap: 1rem; }
.confirm-banner a, .confirm-banner .linkish { color: inherit; font-weight: 600; }
.confirm-banner .confirm-resend-btn[disabled] { opacity: .7; cursor: default; text-decoration: none; }
.check { display: block; font-weight: normal; padding: .15rem 0; }
.small { font-size: .8rem; word-break: break-all; }
.grid-2 { display: grid; gap: .75rem; grid-template-columns: 1fr; }
@media (min-width: 32rem) { .grid-2 { grid-template-columns: 1fr 1fr; } }
.inline-form { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.inline-form input, .inline-form select { width: auto; }
.service-icon { border-radius: 6px; object-fit: cover; vertical-align: middle; margin-right: .4rem; }
/* Sits outside .account-grid, so it needs the grid's own gap as bottom margin
   to avoid butting up against the first card below it. */
.dash-hero { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.dash-hero .avatar { border-radius: 12px; }
.dash-hero-text { flex: 1; min-width: 12rem; }
.dash-hero-actions { margin-left: auto; }
.product-grid { display: grid; gap: 1rem; grid-template-columns: 1fr; }
@media (min-width: 34rem) { .product-grid { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); } }
.product { border: 1px solid var(--border); border-radius: 10px; padding: .9rem; display: flex; flex-direction: column; gap: .5rem; }
.product-head { display: flex; align-items: center; gap: .6rem; }
.product-desc { margin: 0; }
.product .actions { margin-top: auto; }
.btn-sm { padding: .3rem .6rem; font-size: .85rem; }
.session-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .6rem 0; border-bottom: 1px solid var(--border); }
.session-row:last-child { border-bottom: 0; }
.session-dev { min-width: 0; }
.session-dev .small { word-break: break-word; }
.badge-warn { background: var(--warn-bg, #7c5b00); color: var(--warn, #ffcf5a); }
.badge-success { background: var(--success-bg, #14532d); color: var(--success, #86efac); border-color: transparent; }
.sub-status { border: 1px solid var(--border); border-radius: 8px; padding: .6rem .8rem; margin-bottom: 1rem; }
.tier-grid { display: grid; gap: 1rem; grid-template-columns: 1fr; }
@media (min-width: 40rem) { .tier-grid { grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); } }
.tier-card { border: 1px solid var(--border); border-radius: 10px; padding: 1rem; display: flex; flex-direction: column; gap: .55rem; }
.tier-card.is-current { border-color: var(--accent, #4c8bf5); box-shadow: 0 0 0 1px var(--accent, #4c8bf5) inset; }
.tier-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.tier-head h3 { margin: 0; }
.tier-price { font-size: 1.1rem; }
.tier-features { white-space: pre-line; font-size: .9rem; }
.tier-actions { margin-top: auto; display: flex; flex-direction: column; gap: .4rem; }
.tier-actions .btn { width: 100%; }
.avatar-placeholder { display: inline-flex; align-items: center; justify-content: center; width: 96px; height: 96px; border-radius: 12px; background: var(--border); color: var(--muted); font-size: 2.5rem; font-weight: 600; }

/* ── App shell: sidebar + content ─────────────────────────────────────────── */
.app-shell { min-height: 100vh; }

.sidebar {
	position: fixed; top: 0; left: 0; bottom: 0; width: 264px; z-index: 20;
	background: var(--sidebar-bg); border-right: 1px solid var(--sidebar-border);
	display: flex; flex-direction: column; overflow-y: auto;
}
.sidebar-brand { display: flex; flex-direction: column; align-items: center; gap: .4rem; padding: 1.5rem 1.25rem 1.25rem; }
.sidebar-brand .crest { font-size: 2.1rem; line-height: 1; filter: drop-shadow(0 1px 1px rgba(0,0,0,.15)); }
.sidebar-brand .name { font-weight: 700; font-size: 1.05rem; color: var(--text); letter-spacing: .01em; text-align: center; }
.sidebar-nav { display: flex; flex-direction: column; gap: .1rem; padding: .5rem .75rem; border-top: 1px solid var(--sidebar-border); }
.sidebar-nav a { display: flex; align-items: center; gap: .7rem; padding: .55rem .7rem; border-radius: 9px; color: var(--sidebar-text); font-weight: 500; font-size: .95rem; }
.sidebar-nav a:hover { background: var(--sidebar-active-bg); text-decoration: none; color: var(--text); }
.sidebar-nav a.active { background: var(--sidebar-active-bg); color: var(--sidebar-active-text); font-weight: 600; }
.sidebar-nav a svg { width: 19px; height: 19px; flex-shrink: 0; stroke: currentColor; }

.sidebar-mid { flex: 1 1 auto; min-height: 0; overflow: hidden; padding: .75rem; display: flex; flex-direction: column; gap: .6rem; }
.sidebar-mid-title { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); padding: .25rem .35rem 0; }
.promo-card { display: block; padding: .7rem .8rem; border-radius: 11px; border: 1px solid var(--sidebar-border); background: var(--surface); }
.promo-card:hover { text-decoration: none; border-color: var(--primary); }
.promo-card .promo-head { display: flex; align-items: center; gap: .5rem; }
.promo-card img, .promo-card .promo-ic { width: 24px; height: 24px; border-radius: 6px; object-fit: cover; }
.promo-card .promo-name { font-weight: 600; color: var(--text); font-size: .9rem; }
.promo-card .promo-desc { color: var(--muted); font-size: .8rem; margin: .35rem 0 0; }
.promo-card .promo-cta { color: var(--primary); font-size: .82rem; font-weight: 600; margin-top: .4rem; display: inline-block; }

.sidebar-user { display: flex; align-items: center; gap: .6rem; padding: .8rem 1rem; border-top: 1px solid var(--sidebar-border); }
.sidebar-user .avatar-sm { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; border: 1px solid var(--sidebar-border); flex-shrink: 0; }
.sidebar-user .su-info { min-width: 0; flex: 1; }
.sidebar-user .su-name { font-weight: 600; font-size: .92rem; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar-user .su-role { color: var(--muted); font-size: .78rem; }
.sidebar-user .su-return { font-size: .78rem; color: var(--info); font-weight: 600; }
.sidebar-user .su-return:hover { text-decoration: underline; }
.sidebar-user .su-actions { display: flex; gap: .25rem; }
.icon-btn { width: 2rem; height: 2rem; border-radius: 8px; border: 1px solid transparent; background: none; color: var(--muted); cursor: pointer; display: inline-grid; place-items: center; padding: 0; }
.icon-btn:hover { background: var(--sidebar-active-bg); color: var(--text); }

/* Content column + castle background bleed */
/* Full viewport height keeps the footer down the bottom of a short page. */
.content { margin-left: 264px; min-width: 0; position: relative; min-height: 100vh; min-height: 100dvh; }
.content-bg {
	/* The whole scene, fixed to the viewport: `cover` crops it to fill, and
	   fixed positioning means it stays put while the page scrolls over it.
	   Spans the full viewport — the sidebar is fixed and opaque, so it simply
	   covers the left edge. */
	position: fixed; inset: 0; z-index: 0;
	background-image: var(--scene);
	background-size: cover;
	background-position: center;
	background-repeat: no-repeat;
	pointer-events: none;
	--scene: url('/static/img/lightbg.png');
}
/* Gentle scrim to keep text readable over the scene without hiding it. */
.content-bg::after {
	content: ''; position: absolute; inset: 0;
	background: color-mix(in srgb, var(--bg) 20%, transparent);
}
@media (prefers-color-scheme: dark) { :root:not([data-theme='light']) .content-bg { --scene: url('/static/img/darkbg.png'); } }
:root[data-theme='dark'] .content-bg { --scene: url('/static/img/darkbg.png'); }
:root[data-theme='light'] .content-bg { --scene: url('/static/img/lightbg.png'); }
.content-inner { position: relative; z-index: 1; max-width: 66rem; margin: 0 auto; padding: 2.25rem 2rem 9rem; }
/* Positioned like .content-inner: .content-bg is a positioned element, so it
   paints above in-flow block content. Without this the scene's bottom half
   covered the copyright — visibly or not, depending on where the fade landed. */
/* Sits directly on the background photo, so neither theme's muted grey is
   reliably readable against it — light mode especially, where the text is dark
   and the image often is too. A halo in the page background colour separates the
   glyphs from whatever is behind them without boxing the line in. */
.content-foot {
	position: relative; z-index: 1; max-width: 66rem; margin: 0 auto;
	padding: 1rem 2rem 2rem; color: var(--text); font-size: .82rem;
	text-align: center; text-shadow: var(--foot-glow);
}
.content-foot a { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }

/* Auth (logged-out) centered layout */
.auth-shell { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 2rem 1rem; position: relative; }

/* Responsive: sidebar becomes a top bar on narrow screens */
@media (max-width: 820px) {
	/* `relative`, not `static`: the bar has to stay in the flow here, but it also
	   has to keep a stacking position, or the fixed .content-bg (z-index 0)
	   paints straight over it and the whole nav disappears. */
	.sidebar { position: relative; width: 100%; bottom: auto; flex-direction: column; border-right: 0; border-bottom: 1px solid var(--sidebar-border); overflow: visible; }
	.content { margin-left: 0; }
	.sidebar-brand { flex-direction: row; justify-content: flex-start; padding: .9rem 1rem; }
	.sidebar-brand .name { font-size: 1rem; }
	.sidebar-nav { flex-direction: row; flex-wrap: wrap; }
	.sidebar-nav a span { display: none; }
	.sidebar-mid { display: none; }
	.content-inner, .content-foot { padding-left: 1rem; padding-right: 1rem; }

	/* A five-column table cannot fit a phone. Drop the columns an admin is least
	   likely to need on one (they stay on the user's own page), and let the panel
	   scroll if what remains still doesn't fit — never the whole page, which
	   would drag the layout out from under the fixed background. */
	.col-hide-sm { display: none; }
	.table-panel { overflow-x: auto; }
	.admin-table th, .admin-table td { padding: .6rem .55rem; }
	.admin-table .user-cell { width: auto; }
}

/* Passkey ceremony feedback — inline, next to the button that started it. */
/* The reserved line keeps the layout from jumping when a message lands, but an
   empty one would read as a stray gap above the sign-in page's next divider. */
.passkey-status { color: var(--muted); font-size: .85rem; margin: .5rem 0 0; min-height: 1.2em; }
.passkey-status:empty { min-height: 0; margin: 0; }
.passkey-status.error { color: var(--danger); }
