/* app.baseedgelabs.ai. Ported from the approved Phase 1 mockups. Tokens are
   the brand's (.agents/rules/brand-and-theme.md); no new accent. Fonts are
   self-hosted because the CSP allows 'self' only, and a Google Fonts link
   would hand every visitor's IP to Google. */

@font-face {
	font-family: "Roboto Flex";
	src: url("/assets/fonts/roboto-flex-latin-wght-normal.woff2") format("woff2");
	font-weight: 100 1000;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: "Roboto Mono";
	src: url("/assets/fonts/roboto-mono-latin-wght-normal.woff2") format("woff2");
	font-weight: 100 700;
	font-style: normal;
	font-display: swap;
}
/* Google Sans Medium: only for the "Continue with Google" button's label, as
   Google's sign-in branding guidelines specify (.btn-provider, .btn-google). OFL, from
   Google Fonts' latin subset. */
@font-face {
	font-family: "Google Sans";
	src: url("/assets/fonts/google-sans-latin-500-normal.woff2") format("woff2");
	font-weight: 500;
	font-style: normal;
	font-display: swap;
}

:root {
	--bg: #0c0e0f;
	--surface: #16191a;
	--raised: #1e2224;
	--border: #2a2f31;
	--text: #e9eeee;
	--muted: #9aa4a4;
	--quiet: #848d8d;
	--faint: #4f5657;
	--primary: #00f0ff;
	--primary-ink: #003a40;
	--secondary: #8a2be2;
	--caution: #fed639;
	--loss: #ff4d6d;
	--sans: "Roboto Flex", system-ui, -apple-system, "Segoe UI", sans-serif;
	--mono: "Roboto Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
	/* the signed-in top bar's fixed height: the sticky side nav sits just under it */
	--header-h: 65px;
	/* .app-body's top padding: where the side nav starts, and where it sticks */
	--body-pad: 36px;
	color-scheme: dark;
}

* {
	box-sizing: border-box;
}
html {
	-webkit-text-size-adjust: 100%;
}
body {
	margin: 0;
	background: var(--bg);
	color: var(--text);
	font-family: var(--sans);
	font-size: 15px;
	line-height: 1.5;
}
a {
	color: var(--primary);
	text-decoration: none;
}
a:hover {
	color: #7ff7ff;
	text-decoration: underline;
}
a:focus-visible,
button:focus-visible,
input:focus-visible {
	outline: 2px solid var(--primary);
	outline-offset: 2px;
}
h1 {
	margin: 0;
	font-size: 28px;
	font-weight: 650;
	letter-spacing: -0.01em;
	line-height: 1.2;
}
p {
	margin: 0;
}
/* The hidden attribute wins over any display rule (.btn is flex): passkey
   buttons are rendered hidden and shown only where WebAuthn works. */
[hidden] {
	/* biome-ignore lint/complexity/noImportantStyles: the attribute must beat every class's display */
	display: none !important;
}

.grid-bg {
	background-color: var(--bg);
	background-image: radial-gradient(var(--border) 1px, transparent 1px);
	background-size: 22px 22px;
}
.mono {
	font-family: var(--mono);
	font-variant-numeric: tabular-nums;
}
.strong {
	color: var(--text);
	font-weight: 600;
}
.muted {
	color: var(--muted);
}
.small {
	font-size: 12px;
}
.center {
	text-align: center;
}
.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
}
.eyebrow {
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.2em;
	text-transform: uppercase;
	color: var(--primary);
}
.label {
	display: block;
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.08em;
	text-transform: lowercase;
	color: var(--muted);
	margin-bottom: 6px;
}
.label-row {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
}
.small-link {
	font-size: 11px;
}
.hint {
	font-size: 12.5px;
	line-height: 1.5;
	color: var(--muted);
	margin-top: 6px;
}
.lede {
	font-size: 14px;
	color: var(--muted);
	margin-top: 6px;
}
.rule {
	height: 1px;
	background: var(--border);
	border: 0;
	margin: 12px 0;
}
.stack {
	display: flex;
	flex-direction: column;
	gap: 14px;
}
.stack-sm {
	display: flex;
	flex-direction: column;
	gap: 12px;
}

/* -- controls -- */
.input {
	width: 100%;
	height: 44px;
	padding: 0 14px;
	border-radius: 10px;
	border: 1px solid var(--border);
	background: var(--bg);
	color: var(--text);
	font: 15px var(--sans);
}
.input:focus {
	outline: 2px solid var(--primary);
	outline-offset: 1px;
}
/* Every select (all are .input): no native arrow, which each browser draws
   in its own place and size, but our own chevron in --muted, 16px in, with
   room kept for it so a long option never runs under it. The chevron is
   Lucide's chevron-down with Lucide's attributes, written out as a data URI
   because a select's arrow can only be a background; app/test/icons.test.ts
   holds it to packages/kit/server/icons.generated.ts. */
select.input {
	appearance: none;
	padding-right: 44px;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239aa4a4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: right 16px center;
	background-size: 12px 12px;
	cursor: pointer;
}
select.input-sm {
	padding-right: 34px;
	background-position: right 12px center;
}
.input-sm {
	height: 36px;
	width: 140px;
	font-size: 14px;
}
.code-input {
	font-size: 22px;
	letter-spacing: 0.3em;
	text-align: center;
}
.btn {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	width: 100%;
	min-height: 44px;
	padding: 0 16px;
	border-radius: 10px;
	border: 1px solid var(--border);
	background: var(--surface);
	color: var(--text);
	font: 500 14px var(--mono);
	cursor: pointer;
	text-decoration: none;
}
.btn:hover {
	border-color: var(--muted);
	color: var(--text);
	text-decoration: none;
}
.btn-primary {
	background: var(--primary);
	border-color: var(--primary);
	color: var(--primary-ink);
	font-weight: 600;
}
.btn-primary:hover {
	color: var(--primary-ink);
	border-color: #7ff7ff;
	background: #7ff7ff;
}
.btn[aria-busy="true"],
.btn-primary[aria-busy="true"] {
	opacity: 0.6;
	cursor: progress;
}
/* A busy button also shows a small ring beside its label, so a wait of a few
   seconds (checkout's Pay, while the card is authorised) reads as work, not a
   frozen page. Pure CSS in the label's own colour; with reduced motion it
   stands still and the label and dimming carry the state. */
.btn[aria-busy="true"]::after {
	content: "";
	flex-shrink: 0;
	width: 14px;
	height: 14px;
	box-sizing: border-box;
	border: 2px solid currentColor;
	border-right-color: transparent;
	border-radius: 50%;
	animation: btn-busy-spin 0.8s linear infinite;
}
@keyframes btn-busy-spin {
	to {
		transform: rotate(360deg);
	}
}
@media (prefers-reduced-motion: reduce) {
	.btn[aria-busy="true"]::after {
		animation: none;
	}
}
.btn-quiet {
	background: transparent;
}
/* The sign-in buttons stand as far apart as the card's own blocks (.auth-card
   gap), so passkey -> Google -> Discord read as one evenly spaced column
   (operator, 2026-10-14). */
.provider-list {
	gap: 20px;
}
/* The light button: the sign-in card's passkey (operator, 2026-10-14, "S5"),
   so Sign in stays the card's only cyan button. Near-white fill, dark text
   and icon. */
.btn-light {
	background: #e9eeee;
	border-color: #e9eeee;
	color: #0c0e0f;
	font-weight: 600;
}
.btn-light:hover {
	background: #ffffff;
	border-color: #ffffff;
	color: #0c0e0f;
}
.btn-sm {
	width: auto;
	min-height: 36px;
	font-size: 12.5px;
}
.btn-inline {
	width: auto;
	min-height: 36px;
	font-size: 12.5px;
	flex-shrink: 0;
}
.btn-danger {
	background: transparent;
	border-color: #5a2230;
	color: #ff8da1;
}
.btn-danger:hover {
	border-color: var(--loss);
	color: #ff8da1;
}
/* "Continue with Google / Discord": the provider's mark, then the label,
   centred like every other button on the card. The marks are the owners'
   own files (brand/third-party/README.md has the rules), drawn by
   app/src/modules/accounts/brand-marks.ts. Both buttons share Google's
   type and spacing (Google Sans Medium 14/20; 12px before the mark, 10px
   after it, 12px after the text) and the app's 10px corners: Google allows
   a rectangular or pill shape and gives no radius (operator, 2026-10-12).
   Same size, Google first (equal prominence, as Google asks). */
.btn-provider {
	gap: 10px;
	padding: 0 12px;
	font-family: "Google Sans", var(--sans);
	font-size: 14px;
	font-weight: 500;
	line-height: 20px;
}
/* Google, dark theme, to Google's spec: #131314 fill, 1px #8E918F stroke,
   #E3E3E3 text. */
.btn-google,
.btn-google:hover {
	border-color: #8e918f;
	background: #131314;
	color: #e3e3e3;
}
.btn-google:hover {
	background: #232324;
}
/* Discord in its own colour (operator, 2026-10-12): Blurple #5865F2 with
   the white symbol and white text, as Discord's branding pairs them. */
.btn-discord,
.btn-discord:hover {
	border-color: #5865f2;
	background: #5865f2;
	color: #fff;
}
.btn-discord:hover {
	border-color: #4752c4;
	background: #4752c4;
}
/* A mark's box; .brand-mark-none is an empty one, so a sign-in button's
   label lines up where its provider has no mark (Cloudflare: text only). */
.brand-mark {
	display: inline-block;
	flex-shrink: 0;
	vertical-align: middle;
}
.brand-mark-none {
	width: 20px;
	height: 15px;
}
.label .brand-mark-discord {
	width: 14px;
	height: auto;
	margin-right: 6px;
	vertical-align: -1px;
}
/* The letter badges of the first mockup. Nothing in the app draws them now;
   they go once every page uses brandMark(). */
.provider-mark {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	width: 22px;
	height: 22px;
	border-radius: 6px;
	color: var(--bg);
	font: 700 11px var(--mono);
}
.provider-google {
	background: var(--text);
}
.provider-discord {
	background: #b9a4ff;
}
.provider-cloudflare {
	background: #ffb46b;
}
.sec-key .provider-mark {
	margin-right: 8px;
	vertical-align: middle;
}
.divider {
	display: flex;
	align-items: center;
	gap: 12px;
	font-size: 11px;
	letter-spacing: 0.08em;
	color: var(--quiet);
}
.divider::before,
.divider::after {
	content: "";
	flex-grow: 1;
	height: 1px;
	background: var(--border);
}
.check {
	display: flex;
	gap: 10px;
	align-items: flex-start;
	font-size: 13px;
	color: var(--muted);
	line-height: 1.45;
}
.check input {
	margin: 2px 0 0;
	width: 16px;
	height: 16px;
	accent-color: var(--primary);
	flex-shrink: 0;
}

/* -- password meter (advice, never a rule) -- */
.meter {
	display: flex;
	flex-direction: column;
	gap: 8px;
	margin-top: 8px;
}
.meter[hidden] {
	display: none;
}
.meter-bars {
	display: flex;
	gap: 4px;
}
.meter-bars span {
	flex-grow: 1;
	height: 4px;
	border-radius: 2px;
	background: var(--border);
}
.meter-bars span.on {
	background: var(--primary);
}
.meter-text {
	display: flex;
	justify-content: space-between;
	font-size: 11px;
	color: var(--muted);
}
.meter-text .short {
	color: var(--caution);
}
.meter-text .ok {
	color: var(--primary);
}

/* -- pills, alerts -- */
.pill {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	border-radius: 999px;
	padding: 3px 10px;
	font: 500 11px var(--mono);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	white-space: nowrap;
}
.pill-on {
	background: rgba(0, 240, 255, 0.12);
	color: var(--primary);
	box-shadow: inset 0 0 0 1px rgba(0, 240, 255, 0.35);
}
.pill-off {
	background: rgba(255, 255, 255, 0.04);
	color: var(--muted);
	box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.pill-warn {
	background: rgba(254, 214, 57, 0.12);
	color: var(--caution);
	box-shadow: inset 0 0 0 1px rgba(254, 214, 57, 0.35);
}
.pill-bad {
	background: rgba(255, 77, 109, 0.08);
	color: var(--loss);
	box-shadow: inset 0 0 0 1px rgba(255, 77, 109, 0.4);
}
.dot {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: currentColor;
	flex-shrink: 0;
}
.alert {
	display: flex;
	gap: 12px;
	padding: 14px;
	border-radius: 12px;
	background: var(--bg);
	border: 1px solid var(--border);
	font-size: 13.5px;
	line-height: 1.5;
}
.alert .dot {
	margin-top: 7px;
}
.alert-info .dot {
	color: var(--primary);
}
.alert-error {
	border-color: rgba(255, 77, 109, 0.45);
}
.alert-error .dot {
	color: var(--loss);
}

/* -- signed-out pages -- */
.auth-shell {
	min-height: 100vh;
	display: flex;
	flex-direction: column;
	gap: 24px;
	padding: 28px 48px;
}
.auth-header,
.app-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
}
.brand {
	display: flex;
	align-items: center;
	gap: 8px;
	color: var(--text);
	font-size: 14px;
	font-weight: 500;
	letter-spacing: 0.03em;
}
.brand-symbol {
	display: block;
	width: 28px;
	height: 28px;
}
.brand:hover {
	color: var(--text);
	text-decoration: none;
}
.host {
	font-size: 12px;
	color: var(--quiet);
}
.host span {
	color: var(--muted);
}
/* the app's header breadcrumb, "app.baseedgelabs.ai / app / login"
   (2026-10-16): the domain plain, "app" a link to the app's home, the page
   plain and ellipsed when long */
.host.crumb {
	display: inline-flex;
	align-items: baseline;
	gap: 6px;
	min-width: 0;
	overflow: hidden;
	white-space: nowrap;
}
.host.crumb a {
	color: var(--muted);
}
.host.crumb .host-app {
	color: var(--primary);
}
.host.crumb a:hover {
	color: var(--primary);
	text-decoration: none;
}
.host.crumb .host-sep {
	color: var(--faint);
}
.host.crumb .host-domain {
	color: var(--quiet);
}
.host.crumb .host-path {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
}
.auth-main {
	flex-grow: 1;
	display: flex;
	align-items: center;
	justify-content: center;
}
.card {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: 16px;
}
.auth-card {
	width: 440px;
	max-width: 100%;
	padding: 36px 36px 30px;
	display: flex;
	flex-direction: column;
	gap: 20px;
	box-shadow: 0 30px 80px rgba(0, 0, 0, 0.45);
}
.auth-title {
	display: flex;
	flex-direction: column;
	gap: 8px;
}
.bot-note {
	font-size: 10.5px;
	color: var(--quiet);
	text-align: center;
	letter-spacing: 0.04em;
}
.site-footer {
	display: flex;
	justify-content: space-between;
	gap: 16px;
	flex-wrap: wrap;
	font-size: 11.5px;
	color: var(--quiet);
}
.footer-links {
	display: flex;
	flex-wrap: wrap;
	gap: 6px 18px;
}
.footer-links a {
	color: var(--muted);
}
.cf-turnstile:empty {
	display: none;
}
/* What the bot check is doing, whenever it is doing something visible. */
.bot-status {
	font-size: 12px;
	line-height: 1.5;
	color: var(--caution);
}
.bot-status[hidden] {
	display: none;
}

/* -- signed-in pages -- */
.app-shell {
	min-height: 100vh;
	display: flex;
	flex-direction: column;
}
/* Sticky top bar, a fixed height, so the side nav can stick at exactly the
   place it starts (header + body padding) and never slides. */
.app-header {
	position: sticky;
	top: 0;
	z-index: 10;
	height: var(--header-h);
	padding: 0 40px;
	border-bottom: 1px solid var(--border);
	background: rgba(12, 14, 15, 0.92);
	backdrop-filter: blur(8px);
}
.app-header-right {
	display: flex;
	align-items: center;
	gap: 18px;
}
.who {
	display: flex;
	align-items: center;
	gap: 10px;
	font-size: 14px;
}
.avatar {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	border-radius: 50%;
	background: var(--raised);
	box-shadow: inset 0 0 0 1px var(--border);
	font: 600 11px var(--mono);
	flex-shrink: 0;
}
.avatar-lg {
	width: 44px;
	height: 44px;
	font-size: 15px;
}
.app-body {
	flex-grow: 1;
	display: flex;
	gap: 40px;
	padding: var(--body-pad) 40px;
}
/* Sticky (operator 2026-10-07): the links and "sign out" stay in view however
   long the page. It sticks exactly where it starts, under the sticky top bar,
   so nothing moves when the page scrolls; its height is the rest of the
   window, so "sign out" (pushed down by .signout) stays put at the bottom. A
   short window scrolls the nav itself rather than hiding anything. */
.side-nav {
	width: 200px;
	flex-shrink: 0;
	display: flex;
	flex-direction: column;
	gap: 4px;
	position: sticky;
	top: calc(var(--header-h) + var(--body-pad));
	align-self: flex-start;
	/* Exactly the window below the top bar, less the body's padding above AND
	   below: any taller and a short page (the overview) scrolls by the
	   difference, and the nav, which cannot outrun its own container, rides
	   up with it. */
	height: calc(100vh - var(--header-h) - 2 * var(--body-pad));
	min-height: 0;
	overflow-y: auto;
}
.nav-heading {
	font-size: 10.5px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--quiet);
	padding: 0 12px 8px;
}
.side-nav a {
	display: block;
	padding: 9px 12px;
	border-radius: 6px;
	font-size: 13px;
	color: var(--muted);
}
/* option A from the approved mockups: a faint grey wash, no cyan edge */
.side-nav a.current {
	background: rgba(255, 255, 255, 0.06);
	color: var(--text);
}
.side-nav .later {
	display: flex;
	justify-content: space-between;
	padding: 9px 12px;
	font-size: 13px;
	color: var(--faint);
}
.side-nav .later span {
	font-size: 10px;
	letter-spacing: 0.08em;
}
.signout {
	margin-top: auto;
	padding-top: 16px;
}
.signout .btn {
	justify-content: flex-start;
	font-size: 13px;
	color: var(--muted);
}
/* Terms, privacy, refunds under "sign out": the legal pages are one click
   from every signed-in page (and in the footer too). */
.side-nav .nav-legal {
	display: flex;
	flex-wrap: wrap;
	gap: 4px 14px;
	padding: 10px 12px 0;
	font-size: 11.5px;
}
.side-nav .nav-legal a {
	display: inline;
	padding: 0;
	border-radius: 0;
	font-size: 11.5px;
	color: var(--quiet);
}
.side-nav .nav-legal a:hover {
	color: var(--muted);
}
/* "We've updated our terms" (after a sign-in): the documents that changed. */
.legal-docs {
	list-style: none;
	margin: 0;
	padding: 0;
	font-size: 14px;
}
/* The signed-in shell's footer, under the side nav and the main column. */
.app-foot {
	padding: 0 40px 28px;
}
.app-main {
	flex-grow: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 24px;
}
.page-title {
	margin-top: 6px;
	font-size: 30px;
}
.banner-warn {
	display: flex;
	gap: 12px;
	align-items: flex-start;
	padding: 14px 18px;
	border-radius: 12px;
	border: 1px solid rgba(254, 214, 57, 0.35);
	background: rgba(254, 214, 57, 0.06);
	font-size: 14px;
}
.banner-info {
	display: flex;
	gap: 12px;
	align-items: flex-start;
	padding: 14px 18px;
	border-radius: 12px;
	border: 1px solid rgba(0, 240, 255, 0.3);
	background: rgba(0, 240, 255, 0.05);
	font-size: 14px;
}
.banner-body {
	display: flex;
	flex-direction: column;
	gap: 10px;
}
.banner-forms {
	display: flex;
	gap: 12px;
	flex-wrap: wrap;
	align-items: center;
}
.inline-form {
	display: flex;
	gap: 8px;
	align-items: center;
}
.cards {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 16px;
}
.card-pad {
	padding: 20px;
}
.push-down {
	margin-top: auto;
}
.who-card {
	display: flex;
	align-items: center;
	gap: 12px;
}
.kv {
	display: flex;
	justify-content: space-between;
	align-items: center;
	font-size: 14px;
}
.table-card {
	overflow: hidden;
}
.table-head {
	padding: 16px 20px 4px;
}
.table-scroll {
	overflow-x: auto;
}
table {
	border-collapse: collapse;
	width: 100%;
}
th {
	font: 500 10.5px var(--mono);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
	text-align: left;
	padding: 10px 14px;
	border-bottom: 1px solid var(--border);
	white-space: nowrap;
}
td {
	font-size: 13px;
	padding: 12px 14px;
	border-bottom: 1px solid #1e2224;
	vertical-align: middle;
}
td.m {
	font-family: var(--mono);
	font-size: 12px;
	color: var(--muted);
	white-space: nowrap;
}

/* -- the house data table (packages/kit/server/table.ts, README "Tables") --
   A real <table>; under 640px a "cards" table stacks each row into a card of
   label: value lines (each cell's data-label), and a "scroll" table scrolls
   sideways in its own box. Either way the page itself never scrolls sideways. */
.dt {
	min-width: 0;
	/* a toolbar card and the rows' card, spaced as the page's own cards */
	display: flex;
	flex-direction: column;
	gap: 24px;
	/* table.js scrolls a new page's top into view: clear of the sticky header */
	scroll-margin-top: calc(var(--header-h) + 16px);
}
.dt-body {
	transition: opacity 0.15s ease;
}
/* the containing block of the visually hidden caption and sort hints, so a
   wide table's hidden text scrolls with it instead of widening the page */
.dt-wrap {
	position: relative;
}
.dt[aria-busy="true"] .dt-body {
	opacity: 0.55;
}
.dt table:focus {
	outline: none;
}
.dt table:focus-visible {
	outline: 2px solid var(--primary);
	outline-offset: -2px;
}
.dt-caption {
	caption-side: top;
	text-align: left;
	padding: 12px 14px 4px;
	font: 500 11px var(--mono);
	color: var(--muted);
}
.dt-sort {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	color: inherit;
	text-decoration: none;
}
.dt-sort:hover,
th[aria-sort] .dt-sort {
	color: var(--text);
}
.dt-sort-off {
	opacity: 0.5;
}
.dt-pager {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: 12px;
	padding: 12px 20px;
}
.dt-count {
	padding: 12px 20px;
	margin: 0;
}
.dt-note {
	padding: 0 20px 16px;
}
@media (max-width: 640px) {
	.dt-hide-phone {
		display: none;
	}
	.dt-cards table,
	.dt-cards tbody,
	.dt-cards tr,
	.dt-cards td {
		display: block;
		width: 100%;
	}
	.dt-cards thead {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip: rect(0 0 0 0);
		white-space: nowrap;
	}
	.dt-cards tr {
		padding: 12px 16px;
		border-bottom: 1px solid var(--raised);
	}
	.dt-cards td {
		display: grid;
		grid-template-columns: 84px minmax(0, 1fr);
		gap: 12px;
		align-items: baseline;
		padding: 4px 0;
		border-bottom: 0;
		white-space: normal;
		overflow-wrap: anywhere;
	}
	.dt-cards td::before {
		content: attr(data-label);
		font: 500 10.5px var(--mono);
		letter-spacing: 0.08em;
		text-transform: uppercase;
		color: var(--muted);
	}
	.dt-cards td.m {
		white-space: normal;
	}
	.dt-cards td[data-label=""] {
		display: block;
	}
	.dt-cards td[data-label=""]::before {
		content: none;
	}
	.dt-cards td.right {
		text-align: left;
	}
	/* the card cells' display: grid above outranks .dt-hide-phone alone, so a
	   hidePhone column in a "cards" table needs the same specificity to go */
	.dt-cards td.dt-hide-phone {
		display: none;
	}
	.dt-cards .dt-empty td {
		display: block;
	}
	.dt-cards .dt-empty td::before {
		content: none;
	}
	.dt-pager {
		padding: 12px 16px;
	}
}

.row-between {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: 12px;
}
.right {
	text-align: right;
}
.text-bad {
	color: var(--loss);
}
.narrow {
	max-width: 520px;
}

/* -- security page: one card per section, label / value / action rows -- */
.sec-card {
	overflow: hidden;
	/* #passkeys, #devices, #activity land below the sticky top bar */
	scroll-margin-top: calc(var(--header-h) + 16px);
}
.sec-head {
	display: flex;
	align-items: flex-start;
	gap: 16px;
	padding: 20px;
}
.sec-title {
	flex-grow: 1;
	min-width: 0;
}
.sec-title h2 {
	margin: 0;
	font-size: 17px;
	font-weight: 600;
}
.sec-title .hint {
	margin-top: 4px;
}
.sec-aside {
	flex-shrink: 0;
}
.sec-aside .btn {
	width: auto;
	min-height: 36px;
	font-size: 12.5px;
}
.sec-status {
	padding: 0 20px;
}
.sec-status:has(> [hidden]) {
	display: none;
}
.sec-row {
	display: flex;
	align-items: center;
	gap: 16px;
	padding: 16px 20px;
	border-top: 1px solid var(--raised);
}
.sec-key {
	width: 170px;
	flex-shrink: 0;
	font-size: 12px;
	color: var(--muted);
	overflow-wrap: anywhere;
}
.sec-val {
	flex-grow: 1;
	min-width: 0;
	font-size: 14px;
}
.sec-action {
	flex-shrink: 0;
	margin: 0;
}
.sec-card table {
	border-top: 1px solid var(--raised);
}

/* -- profile (Phase 5): avatar card, the two-column form, delete -- */
.avatar-img {
	object-fit: cover;
	background: var(--raised);
}
.avatar-xl {
	width: 72px;
	height: 72px;
	font-size: 25px;
}
/* The avatar card (touch-ups mockup A1, "Two rows"): the picture, its hint
   and Remove / Upload on one row; the linked accounts' photos on a second,
   under a divider. */
.avatar-card {
	display: grid;
	gap: 18px;
	padding: 20px;
	scroll-margin-top: calc(var(--header-h) + 16px);
}
.avatar-main {
	display: flex;
	align-items: center;
	gap: 20px;
}
.avatar-text {
	flex-grow: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
}
.avatar-actions {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-shrink: 0;
}
.avatar-upload {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0;
}
/* Remove is the quiet one: no box until pointed at */
.avatar-actions .btn-quiet {
	border-color: transparent;
	color: var(--muted);
}
.avatar-actions .btn-quiet:hover {
	border-color: var(--border);
	color: var(--text);
}
.file-input {
	max-width: 220px;
	font: 12px var(--mono);
	color: var(--muted);
}
.file-input::file-selector-button {
	margin-right: 10px;
	padding: 8px 12px;
	border-radius: 8px;
	border: 1px solid var(--border);
	background: var(--surface);
	color: var(--text);
	font: 500 12px var(--mono);
	cursor: pointer;
}
/* Without /assets/app.js: the plain file input and its Upload button. With
   it (data-enhanced): only the "Upload a photo" label, which opens the
   picker; picking a file sends the form. The input stays focusable, so the
   label shows its focus ring. */
.avatar-pick {
	display: none;
}
.avatar-upload[data-enhanced] .avatar-pick {
	display: flex;
}
.avatar-upload[data-enhanced] .avatar-send {
	display: none;
}
.avatar-upload[data-enhanced] .file-input {
	position: absolute;
	width: 1px;
	height: 1px;
	opacity: 0;
	overflow: hidden;
}
.avatar-upload[data-enhanced] .file-input:focus-visible + .avatar-pick {
	outline: 2px solid var(--primary);
	outline-offset: 2px;
}
.avatar-status {
	margin: -8px 0 0;
	text-align: right;
}
.avatar-sources {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 12px 14px;
	padding-top: 16px;
	border-top: 1px solid var(--border);
}
.avatar-sources .label {
	margin: 0;
}
.avatar-source-buttons {
	display: flex;
	gap: 8px;
	margin-left: auto;
}
.avatar-source-buttons .brand-mark-discord {
	width: 16px;
	height: auto;
}
@media (max-width: 640px) {
	.avatar-main {
		flex-wrap: wrap;
	}
	/* the hint stays beside the picture; the buttons wrap under both */
	.avatar-text {
		flex-basis: 0;
	}
	/* Upload first, full width; Remove under it */
	.avatar-actions {
		width: 100%;
		flex-direction: column-reverse;
		align-items: stretch;
	}
	.avatar-actions form,
	.avatar-actions .btn {
		width: 100%;
	}
	.avatar-upload {
		flex-wrap: wrap;
	}
	.avatar-actions .file-input {
		flex: 1 1 0;
		min-width: 0;
		max-width: none;
	}
	.avatar-status {
		text-align: left;
	}
	/* the providers' photos side by side, in equal columns */
	.avatar-sources {
		display: grid;
	}
	.avatar-source-buttons {
		display: grid;
		grid-auto-flow: column;
		grid-auto-columns: minmax(0, 1fr);
		margin-left: 0;
	}
	.avatar-source-buttons .btn {
		width: 100%;
		padding: 0 10px;
	}
}
.profile-form {
	padding: 24px;
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 20px 24px;
	margin: 0;
}
/* The profile's email (touch-ups mockup, option 3): an input's shape, but
   dashed, muted and locked, so it never looks typeable. Not a field: it is
   changed on its own page (the hint's link). */
.input-locked {
	display: flex;
	align-items: center;
	gap: 10px;
	border-style: dashed;
	background: #101314;
	color: var(--muted);
	cursor: default;
}
.input-locked-icon {
	flex-shrink: 0;
}
.input-locked-value {
	flex-grow: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font: 14px var(--mono);
}
.hint-link {
	white-space: nowrap;
}
.input-warn {
	border-color: rgba(254, 214, 57, 0.5);
}
.form-actions {
	grid-column: 1 / -1;
	display: flex;
	justify-content: flex-end;
	gap: 10px;
	padding-top: 4px;
}
/* The profile's detail cards: a .sec-card per card, each holding a form of its
   own, its Save in the footer. A label above a pill row is given the pill
   row's height, so side-by-side fields line up. */
.card-form {
	margin: 0;
}
.card-fields {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 20px 24px;
	padding: 0 20px 20px;
}
.card-fields-2 {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}
.card-fields .label-row {
	align-items: center;
	min-height: 22px;
	margin-bottom: 6px;
}
.card-fields > div > .label {
	line-height: 22px;
}
.card-fields .label-row .label {
	margin-bottom: 0;
}
/* (.sec-foot, below, spaces its two ends apart) */
.sec-foot.sec-foot-end {
	justify-content: flex-end;
}
.card-unsaved {
	margin: 0 auto 0 0;
	color: var(--caution);
}
/* /assets/app.js turns a card's buttons off until something in it changes */
.btn:disabled {
	opacity: 0.45;
	cursor: default;
	pointer-events: none;
}
.danger-card {
	border-color: #3a1e26;
}
.plain-list {
	margin: 0;
	padding-left: 18px;
	font-size: 14px;
	line-height: 1.7;
}
@media (max-width: 900px) {
	.profile-form,
	.card-fields-2 {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* -- authenticator app setup and backup codes -- */
.totp-setup {
	display: flex;
	gap: 28px;
	align-items: flex-start;
	max-width: 720px;
}
.qr {
	flex-shrink: 0;
	padding: 10px;
	border-radius: 12px;
	background: #fff;
	line-height: 0;
}
.qr svg {
	width: 200px;
	height: 200px;
}
.secret {
	font-size: 15px;
	letter-spacing: 0.06em;
	overflow-wrap: anywhere;
}
.codes {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, max-content));
	gap: 10px 36px;
	margin: 0;
	padding: 18px 22px 18px 44px;
	border-radius: 12px;
	background: var(--bg);
	border: 1px solid var(--border);
	font-size: 17px;
	letter-spacing: 0.05em;
	user-select: all;
}
.code-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 10px;
}
.code-actions .bot-status {
	flex-basis: 100%;
}
.codes li::marker {
	color: var(--quiet);
	font-size: 12px;
}

/* -- phones -- */
/* -- the narrow-screen menu (button in the top bar, nav as a panel) -- */
.menu-btn {
	display: none;
	align-items: center;
	justify-content: center;
	width: 40px;
	height: 40px;
	border-radius: 10px;
	border: 1px solid var(--border);
	background: var(--surface);
	color: var(--text);
	cursor: pointer;
}
.menu-btn:hover {
	border-color: var(--muted);
}
.menu-btn .icon-close,
.menu-btn[aria-expanded="true"] .icon-open {
	display: none;
}
.menu-btn[aria-expanded="true"] .icon-close {
	display: block;
}

@media (max-width: 900px) {
	.cards {
		grid-template-columns: minmax(0, 1fr);
	}
	.app-body {
		flex-direction: column;
		gap: 24px;
		padding: 24px 16px;
	}
	.side-nav {
		width: auto;
		flex-direction: row;
		flex-wrap: wrap;
		align-items: center;
		position: static;
		height: auto;
		overflow: visible;
		align-self: stretch;
	}
	.side-nav .nav-heading,
	.side-nav .later,
	.side-nav .rule {
		display: none;
	}
	.signout {
		margin: 0 0 0 auto;
		padding: 0;
	}
	/* the row form has the footer below it for these */
	.side-nav .nav-legal {
		display: none;
	}
	.app-foot {
		padding: 0 16px 24px;
	}
	.app-header {
		padding: 0 16px;
	}
	.app-header .host {
		display: none;
	}
	.sec-head,
	.sec-row {
		flex-wrap: wrap;
	}
	/* an aside wider than the card (Passkeys: name field + Add a passkey)
	   shrinks to it and wraps, instead of running off the edge */
	.sec-aside {
		flex-shrink: 1;
		min-width: 0;
	}
	.sec-key {
		width: 100%;
	}
	.totp-setup {
		flex-direction: column;
	}
}
/* After the plain narrow-screen rules it overrides. */
@media (max-width: 900px) {
	/* html.has-menu is set by /assets/app.js; without it the nav stays a row */
	.has-menu .menu-btn {
		display: flex;
	}
	.has-menu .side-nav {
		display: none;
	}
	.has-menu .side-nav.open {
		display: flex;
		flex-direction: column;
		flex-wrap: nowrap;
		align-items: stretch;
		position: fixed;
		top: var(--header-h);
		left: 0;
		right: 0;
		z-index: 9;
		height: auto;
		max-height: calc(100vh - var(--header-h));
		overflow-y: auto;
		padding: 12px 16px 16px;
		background: var(--surface);
		border-bottom: 1px solid var(--border);
		box-shadow: 0 24px 48px rgba(0, 0, 0, 0.5);
	}
	.has-menu .side-nav.open .nav-heading,
	.has-menu .side-nav.open .later {
		display: flex;
	}
	.has-menu .side-nav.open .nav-heading {
		display: block;
		padding-top: 4px;
	}
	.has-menu .side-nav.open .rule {
		display: block;
		margin: 8px 0;
	}
	.has-menu .side-nav.open .signout {
		margin: 4px 0 0;
		padding: 8px 0 0;
		border-top: 1px solid var(--border);
	}
	.has-menu .side-nav.open a {
		padding: 12px;
	}
	.has-menu .side-nav.open .nav-legal {
		display: flex;
		padding: 12px 12px 0;
	}
	.has-menu .side-nav.open .nav-legal a {
		padding: 0;
	}
}
@media (max-width: 420px) {
	.who-name {
		display: none;
	}
}
@media (max-width: 560px) {
	.auth-shell {
		padding: 20px 16px;
	}
	.auth-header .host:not(.crumb) {
		display: none;
	}
	/* phones keep only "app", the way back */
	.auth-header .host.crumb .host-domain,
	.auth-header .host.crumb .host-sep,
	.auth-header .host.crumb .host-path {
		display: none;
	}
	.auth-card {
		padding: 28px 20px 24px;
		box-shadow: none;
	}
}

/* -- naming authenticator apps and passkeys -- */
.passkey-add {
	display: flex;
	gap: 8px;
	align-items: center;
	flex-wrap: wrap;
	justify-content: flex-end;
}
.passkey-add .input-sm {
	width: 180px;
}
/* Rename opens inline, on its own line under the row: a popover would be
   clipped by the card (overflow: hidden) on the last row. */
.sec-row {
	flex-wrap: wrap;
}
.rename summary {
	list-style: none;
	cursor: pointer;
}
.rename summary::-webkit-details-marker {
	display: none;
}
.rename[open] {
	order: 10;
	flex-basis: 100%;
	display: flex;
	justify-content: flex-end;
	gap: 8px;
}
.rename[open] summary {
	border-color: var(--muted);
}
.rename-form {
	display: flex;
	gap: 8px;
	align-items: center;
}
.rename-form .input-sm {
	width: 220px;
}
.sec-row .sec-action + .sec-action {
	margin-left: -8px;
}

/* -- the staging bar (the approved Staging mockup): every page, whenever the
   Worker is not production; added to the HTML by the Worker, never closable -- */
.env-banner {
	background: var(--caution);
	color: #2a2300;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	text-align: center;
	padding: 5px 12px;
}

/* -- Discord (app/docs/PLAN.md "Discord role sync"; the approved mockups):
   the account page's card, and the role chips the admin's card shares -- */
.sec-foot {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: 12px;
	flex-wrap: wrap;
	padding: 12px 20px 16px;
	border-top: 1px solid var(--raised);
}
.sec-foot-start {
	justify-content: flex-start;
}
.sec-buttons {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px;
}
.sec-note {
	padding: 0 20px 18px;
	margin: 0;
}
.sec-status .alert {
	margin-bottom: 16px;
}
.quiet {
	color: var(--quiet);
}
.roles-row {
	display: flex;
	gap: 8px;
	flex-wrap: wrap;
	align-items: center;
}
.roles-row .hint {
	margin: 0;
}
.role-chip {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	border-radius: 999px;
	padding: 3px 11px 3px 9px;
	border: 1px solid var(--border);
	background: var(--bg);
	font: 500 12px var(--mono);
	color: var(--text);
	white-space: nowrap;
}
.role-chip i {
	width: 8px;
	height: 8px;
	border-radius: 50%;
	flex-shrink: 0;
}
.r-lic i {
	background: var(--primary);
}
.r-hn i {
	background: #b57bff;
}
.r-gx i {
	background: var(--caution);
}
.sec-val .provider-mark {
	margin-right: 8px;
	vertical-align: middle;
}
@media (max-width: 560px) {
	.sec-row:has(.roles-row) {
		flex-wrap: wrap;
	}
}
