/* Hand-written, self-contained admin styling. jax has a built-in Tailwind
   pipeline (Config.tailwind_css) that would normally be the better fit for
   a Stitch-style design, but the `tailwindcss` CLI binary isn't installed
   in this environment (only npm/node are) — rather than add an
   unverified external-binary dependency to first boot, this small
   hand-rolled sheet stands in. Swapping to Tailwind later is just setting
   Config.tailwind_css once tailwindcss is installed. */

:root {
	--bg: #f4f5f7;
	--surface: #ffffff;
	--border: #e2e4e9;
	--text: #1c1f26;
	--muted: #6b7280;
	--primary: #3457d5;
	--primary-dark: #2841ab;
	--danger: #c23c3c;
	--radius: 8px;
}

* {
	box-sizing: border-box;
}

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

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

.page {
	max-width: 960px;
	margin-left: var(--sidebar-w, 220px);
	padding: 2.5rem 2rem;
	transition: margin-left 0.15s ease;
}

/* Pages with a right-hand scrollspy (see settings.html) get extra width so
   .page-main can keep the same ~960px reading width other pages get,
   instead of losing space to the spy column. */
.page-with-scrollspy {
	display: flex;
	align-items: flex-start;
	gap: 2.5rem;
	max-width: 1160px;
}

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

.scrollspy {
	position: sticky;
	top: 2.5rem;
	flex: 0 0 180px;
	display: flex;
	flex-direction: column;
	gap: 0.25rem;
}

.scrollspy-label {
	font-size: 0.75rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.03em;
	color: var(--muted);
	margin-bottom: 0.4rem;
}

.scrollspy a {
	padding: 0.35rem 0 0.35rem 0.75rem;
	border-left: 2px solid var(--border);
	color: var(--muted);
	font-size: 0.85rem;
	text-decoration: none;
	transition: color 0.1s ease, border-color 0.1s ease;
}

.scrollspy a:hover {
	color: var(--text);
}

.scrollspy a.active {
	border-left-color: var(--primary);
	color: var(--primary);
	font-weight: 600;
}

.center-page {
	max-width: 420px;
	margin: 3rem auto;
	padding: 0 1.5rem;
}

.card {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: 1.75rem;
}

.card + .card {
	margin-top: 1.25rem;
}

h1 {
	font-size: 1.5rem;
	margin: 0 0 0.25rem;
}

h2 {
	font-size: 1.1rem;
	margin: 0 0 1rem;
}

p.lead {
	color: var(--muted);
	margin-top: 0;
}

label {
	display: block;
	font-size: 0.85rem;
	font-weight: 600;
	margin: 1rem 0 0.35rem;
}

input[type=text], input[type=email], input[type=password], input[type=number], select {
	width: 100%;
	padding: 0.55rem 0.7rem;
	border: 1px solid var(--border);
	border-radius: 6px;
	font-size: 0.95rem;
}

/* Floating-label text inputs: the label sits inside the field as
   placeholder-like text until the field is focused or already holds a
   value, then shrinks and floats above the border instead of taking its
   own line. Two requirements at the markup level: the input needs
   `placeholder=" "` (a single space — invisible, but enough for
   :placeholder-shown to tell "empty and unfocused" apart from "has a
   value"), and the <label> must come right after its <input> in the DOM —
   it's positioned absolutely over the field, so this doesn't change
   anything visually, but it's what lets the plain CSS sibling selectors
   below react to the input's state without any JS. */
.field-float {
	position: relative;
	margin-top: 1.25rem;
}

.field-float input {
	width: 100%;
	padding: 1.15rem 0.7rem 0.35rem;
	border: 1px solid var(--border);
	border-radius: 6px;
	font-size: 0.95rem;
	background: var(--surface);
	color: var(--text);
}

.field-float input:focus {
	outline: none;
	border-color: var(--primary);
	box-shadow: 0 0 0 3px rgba(52, 87, 213, 0.12);
}

.field-float label {
	position: absolute;
	left: 0.75rem;
	top: 0.85rem;
	margin: 0;
	font-size: 0.95rem;
	font-weight: 400;
	color: var(--muted);
	pointer-events: none;
	transform-origin: left top;
	transition: transform 0.15s ease, color 0.15s ease;
}

.field-float input:focus + label,
.field-float input:not(:placeholder-shown) + label {
	transform: translateY(-0.62rem) scale(0.76);
	font-weight: 600;
}

.field-float input:focus + label {
	color: var(--primary);
}

/* A read-only field can't actually be edited, so its label shouldn't claim
   the "focused" blue either — it stays the plain floated-but-unfocused
   color even while focus/selection lands on it (e.g. tabbing through, or
   the identity page's masked key/secret fields). */
.field-float input[readonly]:focus + label {
	color: var(--muted);
}

button, .btn {
	display: inline-block;
	margin-top: 1.5rem;
	background: var(--primary);
	color: #fff;
	border: none;
	padding: 0.6rem 1.2rem;
	border-radius: 6px;
	font-size: 0.95rem;
	cursor: pointer;
}

button:hover, .btn:hover {
	background: var(--primary-dark);
}

button:disabled, .btn:disabled {
	opacity: 0.75;
	cursor: default;
}

/* #login-submit's loading spinner — a lighter variant of the .spinner used
   elsewhere (.save-indicator's, further down) since this one sits on a
   solid primary-blue background rather than a plain page background;
   shares the same @keyframes spin. */
#login-submit {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.5rem;
}

#login-submit .spinner {
	display: none;
	width: 0.9rem;
	height: 0.9rem;
	border: 2px solid rgba(255, 255, 255, 0.35);
	border-top-color: #fff;
	border-radius: 50%;
	animation: spin 0.7s linear infinite;
}

#login-submit.is-loading .spinner {
	display: inline-block;
}

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

/* /admin/login's identity-provider buttons — see admin_auth_controller.v's
   dash_login_options. Neutral/outlined rather than the primary-blue CTA
   style, matching the usual "Continue with X" look; disabled ones (no real
   admin-login mechanism wired up yet — see DashLoginOption.working) get a
   "Coming soon" tooltip explaining why they're there but unusable. */
.sso-options {
	display: flex;
	flex-direction: column;
	gap: 0.6rem;
	margin-top: 1rem;
}

.sso-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 0.6rem;
	width: 100%;
	margin: 0;
	padding: 0.55rem 0.9rem;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: 6px;
	color: var(--text);
	font-size: 0.9rem;
	font-weight: 600;
	font-family: inherit;
	text-decoration: none;
	cursor: pointer;
}

.sso-btn:hover:not(:disabled) {
	background: var(--bg-alt, rgba(127, 127, 127, 0.08));
}

.sso-btn:disabled {
	opacity: 0.5;
	cursor: not-allowed;
}

.sso-divider {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	margin: 1.25rem 0;
	color: var(--muted);
	font-size: 0.8rem;
}

.sso-divider::before,
.sso-divider::after {
	content: '';
	flex: 1;
	height: 1px;
	background: var(--border);
}

.error {
	background: #fdecec;
	color: var(--danger);
	border: 1px solid #f4c6c6;
	border-radius: 6px;
	padding: 0.6rem 0.9rem;
	margin-top: 1rem;
	margin-bottom: 1rem;
	font-size: 0.9rem;
}

.success {
	background: #e4f7e9;
	color: #1c7a37;
	border: 1px solid #bfe8cc;
	border-radius: 6px;
	padding: 0.6rem 0.9rem;
	margin-top: 1rem;
	margin-bottom: 1rem;
	font-size: 0.9rem;
}

/* Vertical sidebar nav — fixed to the left edge, full height. Width lives
   in a custom property so the collapse toggle (identity.html-style icon
   button, wired up in _nav.html) only has to flip one class on <html> and
   both the sidebar and the page's content offset below follow. */
:root {
	--sidebar-w: 220px;
	--sidebar-w-collapsed: 64px;
}

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

.admin-sidebar {
	position: fixed;
	top: 0;
	left: 0;
	bottom: 0;
	width: var(--sidebar-w);
	display: flex;
	flex-direction: column;
	background: var(--surface);
	border-right: 1px solid var(--border);
	transition: width 0.15s ease;
	z-index: 10;
}

.sidebar-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.5rem;
	padding: 1rem;
	border-bottom: 1px solid var(--border);
}

.admin-sidebar .brand {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	color: var(--text);
	font-weight: 700;
	font-size: 1.05rem;
	white-space: nowrap;
	/* No overflow: hidden here — this element is also its own tooltip's
	   positioned container (via the base [data-tooltip] rule), and
	   overflow:hidden on it would clip that tooltip along with any
	   overflowing label text. white-space:nowrap is enough on its own to
	   stop the short, fixed label strings here from ever wrapping. */
}

.brand-accent {
	color: var(--primary);
}

.sidebar-links {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 0.15rem;
	padding: 0.75rem;
	/* No overflow-y: auto here on purpose — CSS forces overflow-x to
	   'auto' too whenever overflow-y isn't 'visible' (they can't be split,
	   even with overflow-x set explicitly), which would clip the hover
	   tooltips that escape to the right of each item. Only two links today,
	   so vertical scrolling isn't needed yet; revisit if that list grows. */
}

.sidebar-foot {
	padding: 0.75rem;
	border-top: 1px solid var(--border);
}

/* Settings takes up the row (grows to fill it); Log out is just a small
   icon button floated to its right, not another full-width item — see
   below for why it's excluded from the "collapse before showing a
   tooltip" rule other nav items follow. */
.sidebar-foot-row {
	display: flex;
	align-items: center;
	gap: 0.4rem;
}

.sidebar-foot-row .settings-link {
	flex: 1;
	min-width: 0;
}

.sidebar-foot-row .logout {
	flex: none;
	width: 2.2rem;
	justify-content: center;
	padding: 0.55rem 0;
}

/* Collapsed sidebar has no room for two icons side by side — stack them
   like every other collapsed nav item instead. */
html.sidebar-collapsed .sidebar-foot-row {
	flex-direction: column;
	align-items: stretch;
	gap: 0.15rem;
}

html.sidebar-collapsed .sidebar-foot-row .logout {
	width: auto;
}

.sidebar-links a,
.sidebar-foot a {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	padding: 0.55rem 0.7rem;
	border-radius: 6px;
	color: var(--text);
	font-size: 0.9rem;
	white-space: nowrap;
	/* No overflow: hidden — see .admin-sidebar .brand above; same reason. */
}

.sidebar-links a svg,
.sidebar-foot a svg {
	flex: none;
}

.sidebar-links a:hover,
.sidebar-foot a:hover {
	background: var(--bg-alt, rgba(127, 127, 127, 0.1));
}

.sidebar-links a.active,
.sidebar-foot a.active {
	background: var(--primary);
	color: #fff;
	font-weight: 600;
}

/* Collapsed: hide every text label, center the now icon-only rows. Scoped
   to .admin-sidebar specifically — .label is also used elsewhere (the
   dashboard's stat-grid captions), which must stay visible regardless. */
html.sidebar-collapsed .admin-sidebar .label {
	display: none;
}

html.sidebar-collapsed .sidebar-head {
	flex-direction: column;
	gap: 0.6rem;
}

html.sidebar-collapsed .sidebar-links a,
html.sidebar-collapsed .sidebar-foot a {
	justify-content: center;
	padding: 0.55rem;
}

/* Sidebar items stack vertically, so a tooltip above/below would overlap
   the next row — anchor it to the right of the item instead, vertically
   centered. */
.admin-sidebar [data-tooltip]::after {
	top: 50%;
	left: 100%;
	bottom: auto;
	transform: translateY(-50%);
	margin-left: 8px;
}

/* Nav-item tooltips (brand/Users/Identity/Settings) only make sense once
   collapsed — expanded, the label sitting right next to the icon already
   says the same thing, so showing a tooltip on top of it is redundant.
   Two exceptions, both always showing their tooltip regardless of collapse
   state: the collapse toggle button (no adjacent label in either state),
   and Log out (icon-only now even when expanded — see .sidebar-foot-row
   above — so it never has a label to make the tooltip redundant). */
html:not(.sidebar-collapsed) .sidebar-links a:hover::after,
html:not(.sidebar-collapsed) .sidebar-links a:focus-visible::after,
html:not(.sidebar-collapsed) .sidebar-foot a:not(.logout):hover::after,
html:not(.sidebar-collapsed) .sidebar-foot a:not(.logout):focus-visible::after,
html:not(.sidebar-collapsed) .admin-sidebar .brand:hover::after,
html:not(.sidebar-collapsed) .admin-sidebar .brand:focus-visible::after {
	opacity: 0;
}

table {
	width: 100%;
	border-collapse: collapse;
	font-size: 0.9rem;
}

th, td {
	text-align: left;
	padding: 0.6rem 0.5rem;
	border-bottom: 1px solid var(--border);
}

th {
	color: var(--muted);
	font-weight: 600;
	font-size: 0.8rem;
	text-transform: uppercase;
	letter-spacing: 0.03em;
}

.stat-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
	gap: 1rem;
	margin-bottom: 1.5rem;
}

.stat {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: 1.1rem;
}

.stat .num {
	font-size: 1.6rem;
	font-weight: 700;
}

.stat .label {
	color: var(--muted);
	font-size: 0.85rem;
}

.badge {
	display: inline-block;
	padding: 0.15rem 0.55rem;
	border-radius: 999px;
	font-size: 0.75rem;
	font-weight: 600;
}

.badge-on {
	background: #e4f7e9;
	color: #1c7a37;
}

.badge-off {
	background: #f0f1f3;
	color: var(--muted);
}

.badge-admin {
	background: #e5e9fc;
	color: var(--primary-dark);
	margin-left: 0.4rem;
}

/* /dash/users' search box + role filter, above the table. */
.users-toolbar {
	display: flex;
	align-items: flex-end;
	gap: 1rem;
	margin-bottom: 1.25rem;
}

.users-search {
	flex: 1 1 260px;
	margin-top: 0;
}

.users-toolbar select {
	flex: 0 0 auto;
	width: auto;
	min-width: 140px;
}

.users-empty,
.logs-empty {
	margin: 1rem 0 0;
}

.step-indicator {
	color: var(--muted);
	font-size: 0.85rem;
	margin-bottom: 1.5rem;
}

/* Identity providers page — compact cards, auto-save, no submit buttons. */

.identity-list {
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
}

.identity-card {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: 1.1rem;
}

.identity-card-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem;
	margin-bottom: 0.5rem;
}

/* On /dash/identity this margin-bottom is always followed by a field-row,
   so it just reads as normal spacing before that. On /dash/settings'
   Linked Accounts cards, the head is the *only* thing in the card — with
   nothing to make room for, that margin becomes naked extra space at the
   card's bottom edge, unbalanced by the top (identity-card's padding is
   symmetric). Drop it whenever there's nothing after the head to space out
   from. */
.identity-card-head:last-child {
	margin-bottom: 0;
}

.identity-name {
	display: inline-flex;
	align-items: center;
	gap: 0.45rem;
	margin: 0;
	font-size: 0.95rem;
	font-weight: 600;
	line-height: 1;
}

/* Small colored badge identifying each provider — a plain monogram/generic
   glyph rather than each company's actual brand mark, since this is an
   admin config screen, not a "Sign in with ..." button subject to brand
   guidelines, and hand-drawn brand logos risk both inaccuracy and looking
   like a knockoff. */
.provider-icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.5rem;
	height: 1.5rem;
	border-radius: 6px;
	flex-shrink: 0;
	font-size: 0.7rem;
	font-weight: 700;
	color: #fff;
}

.provider-icon-email {
	background: var(--primary);
}

.provider-icon-passkey {
	background: #111827;
}

/* Steam/EOS/Apple/Google show their real favicon (see provider_icon_html in
   admin_dashboard_service.v) rather than a drawn glyph on a color swatch —
   plain white behind them since favicons generally assume a light
   background. */
.provider-icon-steam,
.provider-icon-eos,
.provider-icon-apple,
.provider-icon-google {
	background: #fff;
}

.provider-icon img {
	width: 100%;
	height: 100%;
	object-fit: contain;
	border-radius: inherit;
}

.identity-card-head-right {
	display: flex;
	align-items: center;
	gap: 0.75rem;
}

/* /dash/settings' "Link" button (an unlinked, working identity provider's
   identity-card-head) would otherwise inherit the global button/.btn rule's
   form-spacing margin-top and larger padding — sized for a submit button
   sitting under a stack of fields, not a small pill next to a provider
   name. That inflates the row's height past a linked card's (identity-note
   text + a compact icon-btn), so the two states end up with visibly
   different card padding even though both are just identity-card-head.
   Scoped to identity-card-head rather than .btn generally, since Save/
   Unban/etc. elsewhere still need the normal form-button sizing. */
.identity-card-head .btn {
	margin-top: 0;
	padding: 0.35rem 0.9rem;
	font-size: 0.85rem;
}

.identity-note {
	margin: 0;
	font-size: 0.85rem;
}

/* One provider's four independent login-surface switches (Web/Dash/Game/
   Forum — see ProviderConfig's own comment in database/entities.v for what
   each one means/gates), as a segmented button group — each press toggles
   that surface and auto-saves, same as the toggle switches this replaced.
   data-tooltip (kept in sync with the pressed state by setToggleActive() in
   identity.html) reads "Enabled"/"Disabled" on hover, via the shared
   [data-tooltip] bubble style below. */
.button-group {
	display: inline-flex;
	border: 1px solid var(--border);
	border-radius: 6px;
	overflow: hidden;
}

.button-group button {
	appearance: none;
	background: var(--surface);
	border: none;
	border-left: 1px solid var(--border);
	margin: 0;
	padding: 0.3rem 0.7rem;
	font-size: 0.78rem;
	font-weight: 600;
	color: var(--muted);
	cursor: pointer;
}

.button-group button:first-child {
	border-left: none;
}

.button-group button:hover:not(.is-active) {
	background: var(--bg-alt, rgba(127, 127, 127, 0.14));
	color: var(--text);
}

.button-group button.is-active {
	background: var(--primary);
	color: #fff;
}

.button-group button:disabled {
	cursor: not-allowed;
	opacity: 0.5;
}

.button-group button:disabled:hover {
	background: var(--surface);
	color: var(--muted);
}

.field-row {
	display: flex;
	flex-wrap: wrap;
	gap: 0 1rem;
}

.field-row .field {
	flex: 1 1 220px;
}

/* .field-float's default margin-top (1.25rem) is sized for a tall, standalone
   stacked form (install/login pages). On an identity card the field sits
   directly under the compact name/toggle header, so that much space reads
   as a gap under the provider name rather than breathing room between
   fields — tighten it here without touching the taller pages. */
.field-row .field-float {
	margin-top: 0;
}

.input-group {
	position: relative;
	display: flex;
}

.input-group input {
	flex: 1;
	min-width: 0;
	padding-right: 2.1rem; /* room for the single overlaid delete button */
}

.input-group input[readonly] {
	background: var(--bg-alt, rgba(127, 127, 127, 0.08));
	color: var(--muted);
	cursor: default;
	/* cursor:default above only changes the mouse pointer's icon — the
	   browser still happily places a text caret and lets you drag-select on
	   click unless told not to. user-select stops the selection itself;
	   caret-color hides the blinking insertion-point caret a click or Tab
	   still puts there even with nothing selectable. */
	user-select: none;
	caret-color: transparent;
}

/* A locked field can't be typed into, so neither its focus ring nor a text
   selection inside it should ever be visible — including the floating-label
   input's own blue focus border (.field-float input:focus), which readonly
   fields would otherwise still pick up when clicked or tabbed to. */
.input-group input[readonly]:focus {
	outline: none;
	box-shadow: none;
	border-color: var(--border);
}

.input-group input[readonly]::selection {
	background: transparent;
}

/* Two stacked .field-row blocks (e.g. the Endpoint field below Google's
   Client ID/secret row) otherwise sit flush against each other — gap:
   0 1rem on .field-row only spaces fields *within* one row. 0.75rem
   matches .identity-list's own gap between whole cards, so rows within a
   card and cards within the list share the same rhythm. */
.field-row + .field-row {
	margin-top: 0.75rem;
}

.input-group .icon-btn {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	right: 0.2rem;
}

.icon-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.7rem;
	height: 1.7rem;
	margin: 0;
	padding: 0;
	background: transparent;
	color: var(--muted);
	border: none;
	border-radius: 5px;
	cursor: pointer;
}

.icon-btn[hidden] {
	display: none;
}

.icon-btn:hover {
	background: var(--bg-alt, rgba(127, 127, 127, 0.14));
	color: var(--text);
}

/* Custom hover tooltip for icon-only buttons/links (nav's Log out, and the
   identity page's delete button) — the icons alone don't label
   themselves, so `data-tooltip` (kept alongside `aria-label` for
   screen readers) drives a small styled bubble instead of relying on the
   browser's native, unstyled `title` tooltip. `position: relative` is a
   fallback base only — anything that already needs its own positioning
   (e.g. the identity page's overlaid icon buttons) sets that itself with a
   more specific selector, which wins over this one. */
[data-tooltip] {
	position: relative;
}

[data-tooltip]::after {
	content: attr(data-tooltip);
	position: absolute;
	bottom: calc(100% + 7px);
	left: 50%;
	transform: translateX(-50%);
	background: var(--text);
	color: var(--surface);
	font-size: 0.72rem;
	font-weight: 600;
	line-height: 1;
	white-space: nowrap;
	padding: 0.35rem 0.55rem;
	border-radius: 5px;
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.1s ease;
	z-index: 30;
}

[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after {
	opacity: 1;
}

.save-indicator {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	font-size: 0.8rem;
	font-weight: 600;
	color: var(--muted);
	opacity: 0;
	transition: opacity 0.15s ease;
	white-space: nowrap;
}

.save-indicator.shown {
	opacity: 1;
}

.save-indicator.save-ok {
	color: #1c7a37;
}

.save-indicator.save-err {
	color: var(--danger);
}

.save-indicator .spinner {
	display: none;
	width: 0.8rem;
	height: 0.8rem;
	border: 2px solid var(--border);
	border-top-color: var(--primary);
	border-radius: 50%;
	animation: spin 0.7s linear infinite;
}

.save-indicator.save-saving .spinner {
	display: inline-block;
}

@keyframes spin {
	to {
		transform: rotate(360deg);
	}
}
