/* Self-hosted Courier Prime (vendored OFL) for the web SSH terminal. */
@font-face {
	font-family: "Courier Prime";
	font-style:  normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/courier-prime/regular.woff2") format("woff2");
}
@font-face {
	font-family: "Courier Prime";
	font-style:  italic;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/courier-prime/italic.woff2") format("woff2");
}
@font-face {
	font-family: "Courier Prime";
	font-style:  normal;
	font-weight: 700;
	font-display: swap;
	src: url("../fonts/courier-prime/bold.woff2") format("woff2");
}
@font-face {
	font-family: "Courier Prime";
	font-style:  italic;
	font-weight: 700;
	font-display: swap;
	src: url("../fonts/courier-prime/bold-italic.woff2") format("woff2");
}

/* Structural CSS only. Colors come from themes/<name>.css via CSS variables. */

*,
*::before,
*::after {
	box-sizing: border-box;
}

html, body {
	margin: 0;
	padding: 0;
}

html {
	height: 100%;
	/* Reserve scrollbar gutter so centred containers don't shift
	   horizontally between short and long pages. */
	scrollbar-gutter: stable;
}

body {
	min-height: 100%;
	font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	font-size: 16px;
	line-height: 1.45;
	display: flex;
	flex-direction: column;
}

/* Top bar
   ------------------------------------------------------------ */
.topbar {
	display: flex;
	align-items: center;
	gap: 1.5rem;
	padding: 0.5rem 1.25rem;
	border-bottom: 1px solid transparent;
	/* Lock height so the bar reads the same on every page regardless
	   of whether the right cluster carries buttons (index, post-login)
	   or just the brand (login). Sized to fit a .topbar__signin row
	   (~2.2rem content + 1rem padding). */
	min-height: 3.25rem;
}

.topbar__brand {
	display: inline-flex;
	align-items: center;
	gap: 0.55rem;
	font-weight: 600;
	letter-spacing: 0.02em;
	text-decoration: none;
}

.topbar__mark {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.6rem;
	height: 1.6rem;
	border-radius: 0.4rem;
	font-size: 1rem;
	font-weight: 700;
}

.topbar__nav {
	display: flex;
	gap: 0.25rem;
}

.topbar__nav a {
	text-decoration: none;
	font-size: 0.95rem;
	padding: 0.35rem 0.75rem;
	border-radius: 0.35rem;
	position: relative;
	/* inline-flex column + ::after reserves the bold-variant width without
	   adding height, so the nav doesn't reflow on active-item changes. */
	display: inline-flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
}

.topbar__nav a::after {
	content: attr(data-label);
	font-weight: 600;
	height: 0;
	visibility: hidden;
	overflow: hidden;
	pointer-events: none;
	user-select: none;
}

.topbar__nav a:hover {
	text-decoration: none;
}

.topbar__nav a.is-active {
	font-weight: 600;
}

.topbar__actions {
	margin-left: auto;
	display: flex;
	align-items: center;
	gap: 0.75rem;
}

.topbar__user {
	font-size: 0.9rem;
	opacity: 0.85;
}

.topbar__user-link {
	color: inherit;
	text-decoration: none;
}

.topbar__user-link:hover .topbar__user {
	text-decoration: underline;
	opacity: 1;
}

.topbar__signin {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	text-decoration: none;
	font-size: 0.95rem;
	padding: 0.35rem 0.85rem;
	border-radius: 0.4rem;
	/* Reset so the class also works on <button> (logout form). */
	background: none;
	cursor: pointer;
	font-family: inherit;
}

.topbar__signin:hover {
	text-decoration: underline;
}

.topbar__logout-form {
	display: inline;
	margin: 0;
}

/* Page surface (between topbar and footer)
   ------------------------------------------------------------ */
.page {
	flex: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	padding: 4rem 1.25rem 2rem;
	gap: 3.5rem;
}

.page--landing {
	position: relative;
	overflow: hidden;
	padding: 5rem 1.25rem 4rem;
	gap: 4rem;
	justify-content: flex-start;
}

/* Absolute-positioned radial-gradient glow filling the flex-grown page.
   Children sit above via z-index. */
.page__bg {
	position: absolute;
	inset: 0;
	z-index: 0;
	pointer-events: none;
	background:
		radial-gradient(
			ellipse at 50% 50%,
			var(--hero-grad-1, #cba6f7) 0%,
			var(--hero-grad-2, #7c3aed) 40%,
			var(--hero-grad-3, #4c1d95) 90%
		);
	filter: blur(60px) saturate(115%);
	opacity: 0.32;
}

.page--landing > .hero,
.page--landing > .features {
	position: relative;
	z-index: 1;
}

.page--login {
	position: relative;
	overflow: hidden;
	justify-content: center;
	padding: 2rem 1rem;
	gap: 2rem;
}

/* Lift card content above the .page__bg glow. */
.page--login > .card {
	position: relative;
	z-index: 1;
}

/* Profile + Meta share canvas chrome (glow behind a card). */
.page--profile,
.page--meta {
	position: relative;
	overflow: hidden;
	padding: 2rem 1.25rem 3rem;
	align-items: center;
}

.profile {
	position: relative;
	z-index: 1;
	width: 100%;
	max-width: 72rem;
	margin: 0 auto;
	padding: 1.5rem 2rem 2rem;
	border-radius: 0.75rem;
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
	background: var(--card-bg);
	border: 1px solid var(--card-border);
}

.profile__head {
	padding: 0 0 1rem;
}

.profile__title {
	margin: 0;
	font-size: 1.75rem;
}

.profile__banner {
	margin: 0 0 0.5rem;
}

.profile__row {
	display: grid;
	grid-template-columns: 14rem 1fr;
	gap: 1.5rem;
	align-items: start;
	padding: 1.5rem 0;
	border-top: 1px solid var(--card-border);
}

.profile__row:first-of-type {
	border-top: 0;
}

.profile__row-label {
	font-weight: 600;
	font-size: 0.95rem;
	letter-spacing: 0.01em;
	/* Match the right column's label line-height for baseline alignment. */
	line-height: 1.5rem;
}

/* Right-column content cap. MFA enrollment uses --wide to opt out. */
.profile__row-content {
	display: flex;
	flex-direction: column;
	gap: 0.85rem;
	min-width: 0;
	max-width: 36rem;
}

.profile__row-content--wide {
	max-width: none;
}

.profile__value {
	font-weight: 600;
	font-size: 1rem;
	line-height: 1.5rem;
}

[data-profile-email-display] {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 0.75rem;
}

.profile__form {
	display: flex;
	flex-direction: column;
	gap: 0.85rem;
	margin: 0;
}

.profile__form-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0.75rem;
}

@media (max-width: 640px) {
	.profile__form-grid {
		grid-template-columns: minmax(0, 1fr);
	}
	.profile__row {
		grid-template-columns: minmax(0, 1fr);
		gap: 0.5rem;
	}
}

.profile__actions {
	display: flex;
	gap: 0.5rem;
	flex-wrap: wrap;
}

/* Uniform button width across the actions row. */
.profile__actions .btn {
	min-width: 12rem;
}

/* Servers modal "Auth credentials" section: center the Request / Delete
   CTAs inside the section since each is a standalone action (Request
   alone on Edit pre-fetch; Delete alone after a successful fetch). The
   default left-alignment looked off in the centered modal column. */
[data-server-auth-section] .profile__actions {
	justify-content: center;
}
/* Uniform vertical rhythm between the body elements (hint, Request
   button, form fields, create_key checkbox, key-rotation explainer,
   Delete button). Without this every adjacent pair sits flush — the
   parent .settings__section > :not(summary) rule only handles the
   horizontal indent, not vertical spacing. 0.85rem matches the
   .modal__form gap so the auth section breathes with the same
   cadence as the inventory fields above it. */
[data-server-auth-section] > :not(summary) + :not(summary) {
	margin-top: 0.85rem;
}
/* Re-center the section hint paragraph: .card__hint defaults to
   center-aligned, but the global .settings__section .card__hint rule
   (text-align: left, used everywhere else .settings__section appears)
   would otherwise left-align it in the modal context. The hint here
   sits above the centered Request / form-fields stack, so left-align
   reads as off-axis. Chain [data-server-auth-section] with
   .settings__section on the same element to push specificity past
   the (0,2,0) conflicting rule lower in the file — without this
   tie, source order would let .settings__section .card__hint win. */
.settings__section[data-server-auth-section] .card__hint {
	text-align: center;
}

/* Left-align hints inside profile/form contexts (card hint default is centre). */
.profile__row-content .card__hint,
.profile__form .card__hint {
	margin: 0;
	text-align: left;
}

/* iOS-style toggle switch. Whole control is one <button type="submit">. */
.toggle {
	display: inline-flex;
	align-items: center;
	gap: 0.65rem;
	cursor: pointer;
	user-select: none;
	background: none;
	border: 0;
	padding: 0;
	margin: 0;
	font: inherit;
	color: inherit;
}

.toggle:focus {
	outline: none;
}

.toggle__track {
	position: relative;
	display: inline-block;
	width: 2.75rem;
	height: 1.5rem;
	border-radius: 999px;
	background: rgba(127, 127, 127, 0.3);
	border: 1px solid var(--card-border);
	transition: background-color 120ms ease;
}

.toggle__thumb {
	position: absolute;
	top: 0.15rem;
	left: 0.15rem;
	width: 1.1rem;
	height: 1.1rem;
	border-radius: 50%;
	background: var(--text, #fff);
	box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
	transition: transform 140ms ease, background-color 120ms ease;
}

.toggle.is-on .toggle__track {
	background: var(--accent, #a78bfa);
}

.toggle.is-on .toggle__thumb {
	transform: translateX(1.25rem);
	background: var(--accent-text, #1f1f2c);
}

.toggle:focus-visible .toggle__track {
	outline: 2px solid var(--accent, #a78bfa);
	outline-offset: 2px;
}

.toggle__caption {
	font-size: 0.95rem;
	min-width: 2.5rem;
}

/* MFA enrollment splits into QR on the left and verify form on the
   right when there's room. Stacks on narrow viewports. */
.profile__mfa-enroll {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 1.5rem;
	align-items: start;
}

.profile__mfa-text {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
	min-width: 0;
}

@media (max-width: 640px) {
	.profile__mfa-enroll {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* Hardcoded white so QR modules stay legible in both themes. */
.profile__qr {
	display: inline-flex;
	padding: 0.75rem;
	background: #fff;
	border-radius: 0.5rem;
}

.profile__qr svg {
	width: 12rem;
	height: 12rem;
	display: block;
}

.profile__secret {
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 0.85rem;
	user-select: all;
	word-break: break-all;
	opacity: 0.9;
}

/* Hero (landing splash)
   ------------------------------------------------------------ */
.hero {
	text-align: center;
	max-width: 40rem;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.5rem;
}

.hero__mark {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 5.5rem;
	height: 5.5rem;
	border-radius: 1rem;
	font-size: 2.5rem;
	font-weight: 700;
	margin-bottom: 0.5rem;
}

.hero__title {
	font-size: 3rem;
	letter-spacing: 0.03em;
	margin: 0;
}

.hero__subtitle {
	margin: 0;
	font-size: 1.05rem;
	opacity: 0.85;
}

/* Feature grid (2x2 below hero)
   ------------------------------------------------------------ */
.features {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 2rem 3rem;
	width: 100%;
	max-width: 56rem;
}

@media (max-width: 640px) {
	.features {
		grid-template-columns: 1fr;
		gap: 1.5rem;
	}
}

.feature {
	text-align: center;
	padding: 1.75rem 1.5rem;
	border-radius: 0.75rem;
	border: 1px solid transparent;
	transition: transform 120ms ease, border-color 120ms ease;
}

.feature:hover {
	transform: translateY(-2px);
}

.feature__icon {
	font-size: 2rem;
	line-height: 1;
	margin-bottom: 0.75rem;
}

.feature__title {
	margin: 0 0 0.5rem;
	font-size: 1.15rem;
	font-weight: 600;
}

.feature__body {
	margin: 0;
	font-size: 0.95rem;
	opacity: 0.85;
}

/* Login card
   ------------------------------------------------------------ */
.card {
	width: 100%;
	max-width: 22rem;
	padding: 1.75rem;
	border-radius: 0.75rem;
	display: flex;
	flex-direction: column;
	gap: 1rem;
}

.card__title {
	font-size: 1.75rem;
	margin: 0;
	text-align: center;
}

.card__subtitle {
	margin: -0.25rem 0 0.5rem;
	text-align: center;
	opacity: 0.85;
}

.card__hint {
	margin: 0.5rem 0 0;
	font-size: 0.85rem;
	text-align: center;
	opacity: 0.7;
}

/* Defeat .card__hint's 0.7 opacity for anchors inside it (link colour
   would otherwise wash out to slate-blue). */
.card__hint a {
	opacity: 1;
}

/* Force-reset action block in the edit-user modal's password row. */
.modal__passwd-action {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.5rem;
}
.modal__passwd-action .card__hint {
	margin: 0;
}

/* [hidden] must win over same-specificity display: rules (e.g. .field).
   !important is the standard fix; Normalize/Bootstrap ship the same. */
[hidden] {
	display: none !important;
}

/* Fields
   ------------------------------------------------------------ */
.field {
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
}

/* Input + trailing button (e.g. password + Generate). */
.field__input-group {
	display: flex;
	gap: 0.5rem;
	align-items: stretch;
}
.field__input-group > .field__input {
	flex: 1 1 auto;
	min-width: 0;
}

/* Right-anchored field for opt-in toggles inside modal forms. */
.field--right {
	align-items: flex-end;
}
.field--right .card__hint {
	text-align: right;
	margin-top: 0;
}

.field__label {
	font-size: 0.85rem;
	letter-spacing: 0.02em;
}

.field__input {
	font: inherit;
	padding: 0.6rem 0.75rem;
	border: 1px solid transparent;
	border-radius: 0.4rem;
	width: 100%;
	outline: none;
	transition: background-color 80ms ease, color 80ms ease, border-color 80ms ease;
}

/* Buttons
   ------------------------------------------------------------ */
.btn {
	display: inline-block;
	font: inherit;
	padding: 0.6rem 1.25rem;
	border: 1px solid transparent;
	border-radius: 0.4rem;
	cursor: pointer;
	text-decoration: none;
	transition: background-color 80ms ease, color 80ms ease, border-color 80ms ease;
}

.btn--block {
	display: block;
	width: 100%;
	text-align: center;
}

/* Site footer (skin switcher) */
.sitefoot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.75rem 1.25rem;
	font-size: 0.85rem;
	border-top: 1px solid transparent;
}

.sitefoot__left,
.sitefoot__right {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	flex-wrap: wrap;
}

.sitefoot__right {
	justify-content: flex-end;
}

.sitefoot a {
	text-decoration: none;
}

.sitefoot a:hover {
	text-decoration: underline;
}

/* Build identifier "v: <sha>" in the footer-left segment. Mono so the
   hex reads as an identifier; smaller + lower opacity so it sits as a
   subtle build tag rather than competing with the wireframe label. */
.sitefoot__version {
	font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
	font-size: 0.75rem;
	opacity: 0.7;
	margin-left: 0.4rem;
}

/* Legacy .footer class kept for login.php's existing markup. */
.footer {
	margin-top: 1rem;
	font-size: 0.85rem;
	opacity: 0.75;
	display: flex;
	gap: 0.5rem;
	align-items: center;
	flex-wrap: wrap;
	justify-content: center;
}

.footer a {
	text-decoration: none;
}

.footer a:hover {
	text-decoration: underline;
}

/* List page chrome (servers / users / incidents / sessions / settings). */
.page--list {
	position: relative;
	overflow: hidden;
	padding: 2rem 1.25rem 3rem;
	align-items: center;
}

.list {
	position: relative;
	z-index: 1;
	width: 100%;
	max-width: 72rem;
	margin: 0 auto;
	padding: 1.5rem 2rem 2rem;
	border-radius: 0.75rem;
	display: flex;
	flex-direction: column;
	gap: 1rem;
	background: var(--card-bg);
	border: 1px solid var(--card-border);
}

.list__head {
	display: flex;
	align-items: baseline;
	gap: 1rem;
}

.list__title {
	margin: 0;
	font-size: 1.5rem;
	letter-spacing: 0.01em;
}

.list__meta {
	font-size: 0.9rem;
	opacity: 0.7;
}

.list__actions {
	margin-left: auto;
}

.list__search {
	display: flex;
	gap: 0.5rem;
	align-items: stretch;
}

.list__search-input {
	flex: 1;
	min-width: 0;
}

/* Compact icon-shaped buttons (help trigger, combobox clear, etc.).
   Square hit-target sized to the input baseline; overrides .btn's
   default padding so the glyph centres cleanly. */
.list__search-help,
.server-transfer__clear {
	flex: 0 0 auto;
	width: 2.25rem;
	padding: 0;
	font-weight: 600;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
/* Hide the clear button while the combobox input shows its
   placeholder (i.e. is empty). :placeholder-shown only matches when
   the value is empty — no JS-driven visibility tracking needed. */
.field__input-group > .field__input:placeholder-shown ~ .server-transfer__clear {
	display: none;
}

textarea.field__input {
	font-family: inherit;
	resize: vertical;
}

/* Replace native select chrome so the dropdown follows the theme. */
select.field__input {
	appearance: none;
	background-image:
		linear-gradient(45deg, transparent 50%, currentColor 50%),
		linear-gradient(135deg, currentColor 50%, transparent 50%);
	background-position:
		calc(100% - 1.1rem) 50%,
		calc(100% - 0.65rem) 50%;
	background-size: 0.4rem 0.4rem;
	background-repeat: no-repeat;
	padding-right: 2rem;
}

/* Pagination strip */
.list__pager {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.5rem 0;
}

.list__pager-btn {
	min-width: 6rem;
	text-align: center;
}

.list__pager-btn.is-disabled {
	opacity: 0.4;
	cursor: not-allowed;
	pointer-events: none;
}

.list__pager-pos {
	font-size: 0.95rem;
	letter-spacing: 0.03em;
	opacity: 0.85;
}

/* Two-column detail panel — collapses to one column under 720px. */
.detail-split {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: 2rem;
	/* stretch so the right column's left-border runs full height. */
	align-items: stretch;
}

.detail-split__right {
	display: flex;
	flex-direction: column;
	gap: 0.6rem;
	padding-left: 1.5rem;
	border-left: 1px solid var(--card-border);
}

.detail-split__title {
	margin: 0;
	font-size: 0.95rem;
	letter-spacing: 0.02em;
	opacity: 0.85;
}

/* Zero margin so the flex gap alone controls vertical rhythm. */
.detail-split__right .card__hint {
	margin: 0;
}

/* Centre actions under the centred hint. */
.detail-split__right .profile__actions {
	justify-content: center;
}

/* Stack multiple dls in the left column with uniform row-gap. */
.detail-split__left {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

@media (max-width: 720px) {
	.detail-split {
		grid-template-columns: minmax(0, 1fr);
	}
	.detail-split__right {
		padding-left: 0;
		border-left: 0;
		border-top: 1px solid var(--card-border);
		padding-top: 1rem;
	}
}

.detail-grid {
	display: grid;
	/* 6rem fits "Description"/"Hostname"; keeps values left-anchored. */
	grid-template-columns: 6rem 1fr;
	gap: 0.5rem 1rem;
	margin: 0;
}

/* 4-column dt-dd-dt-dd variant; same first-column width as the base. */
.detail-grid--paired {
	grid-template-columns: 6rem minmax(0, 1fr) 6rem minmax(0, 1fr);
}

/* Meta page (server metadata viewer). Mirrors .profile chrome. */
.meta {
	position: relative;
	z-index: 1;
	width: 100%;
	max-width: 72rem;
	margin: 0 auto;
	padding: 1.5rem 2rem 2rem;
	border-radius: 0.75rem;
	display: flex;
	flex-direction: column;
	gap: 1.25rem;
	background: var(--card-bg);
	border: 1px solid var(--card-border);
}

/* Two-row header: row 1 is title + back, row 2 is the policy toggle. */
.meta__head {
	display: flex;
	flex-direction: column;
	gap: 0.85rem;
}

.meta__head-top {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
}

.meta__policy-row {
	display: inline-flex;
	align-items: center;
	gap: 0.6rem;
}

/* Reusable busy overlay; fired by busy.js or window.icarusBusy.show(). */
.busy-overlay {
	position: fixed;
	inset: 0;
	z-index: 9999;
	display: flex;
	align-items: center;
	justify-content: center;
	flex-direction: column;
	gap: 1rem;
	background: rgba(10, 10, 15, 0.55);
	backdrop-filter: blur(2px);
	-webkit-backdrop-filter: blur(2px);
	color: var(--text, #e6e6f0);
}

.busy-overlay__spinner {
	width: 3rem;
	height: 3rem;
	border-radius: 50%;
	border: 4px solid rgba(255, 255, 255, 0.15);
	border-top-color: var(--accent, #a78bfa);
	animation: busy-overlay-spin 0.8s linear infinite;
}

.busy-overlay__message {
	font-size: 1rem;
	max-width: 24rem;
	text-align: center;
	opacity: 0.9;
}

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

@media (prefers-reduced-motion: reduce) {
	.busy-overlay__spinner {
		animation-duration: 2.5s;
	}
}

/* Bottom-right action row (back + collect-now). */
.meta__foot {
	display: flex;
	justify-content: flex-end;
	gap: 0.5rem;
	padding-top: 0.5rem;
	align-items: center;
}

.meta__collect-form,
.meta__force-form {
	margin: 0;
}

.meta__policy-label {
	font-size: 0.85rem;
	opacity: 0.75;
	white-space: nowrap;
}

.meta__policy-form {
	margin: 0;
}

/* Segmented tri-state control (Inherit / Off / On). Each segment is a
   real <button type="submit"> so the form works without JS. */
.seg-toggle {
	display: inline-flex;
	border-radius: 999px;
	border: 1px solid var(--card-border);
	background: rgba(127, 127, 127, 0.12);
	overflow: hidden;
}

.seg-toggle__btn {
	background: transparent;
	border: 0;
	padding: 0.35rem 0.85rem;
	font: inherit;
	font-size: 0.85rem;
	color: inherit;
	cursor: pointer;
	transition: background-color 80ms ease, color 80ms ease;
}

.seg-toggle__btn + .seg-toggle__btn {
	border-left: 1px solid var(--card-border);
}

.seg-toggle__btn:hover {
	background: rgba(127, 127, 127, 0.18);
}

.seg-toggle__btn.is-active {
	background: var(--accent, #a78bfa);
	color: var(--accent-text, #1f1f2c);
	font-weight: 600;
}

.seg-toggle__btn:focus-visible {
	outline: 2px solid var(--accent, #a78bfa);
	outline-offset: 2px;
}

.meta__title {
	margin: 0;
	font-size: 1.75rem;
}

.meta__sid {
	margin: 0.25rem 0 0;
	font-size: 0.85rem;
	opacity: 0.65;
}

.meta__timestamp {
	margin: 0;
}

/* Tight stack of timestamp lines under the policy pill (right-aligned). */
.meta__timestamps {
	display: flex;
	flex-direction: column;
	gap: 0.05rem;
	align-items: flex-end;
	text-align: right;
}

/* Header right-cluster: pill on top, timestamps under. */
.meta__head-side {
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 0.5rem;
}

/* 2-column meta sections; full-width opt-in via --full; single column <720px. */
.meta-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 1rem;
}

@media (max-width: 720px) {
	.meta-grid {
		grid-template-columns: minmax(0, 1fr);
	}
}

.meta-section {
	border-top: 1px solid var(--card-border);
	padding-top: 1rem;
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
}

.meta-section--full {
	grid-column: 1 / -1;
}

/* Inline-SVG usage bar (viewBox=100 so rect.width attr IS the percent). */
.meta-bar {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	width: 100%;
}

.meta-bar__svg {
	flex: 1;
	height: 0.6rem;
	display: block;
	border-radius: 0.2rem;
	overflow: hidden;
}

.meta-bar__track {
	fill: rgba(127, 127, 127, 0.18);
}

.meta-bar__fill--ok   { fill: #16a34a; }  /* green - under 75% */
.meta-bar__fill--mid  { fill: #d97706; }  /* amber - 75-90%    */
.meta-bar__fill--high { fill: #dc2626; }  /* red   - 90%+      */

.meta-bar__label {
	font-size: 0.85rem;
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	white-space: nowrap;
	min-width: 6rem;
	text-align: right;
	opacity: 0.85;
}

/* Tighter dt column for load-bar rows (1m/5m/15m labels). */
.meta-grid__loadbars {
	grid-template-columns: 3rem 1fr;
	gap: 0.4rem 0.75rem;
}

.meta-section h2 {
	margin: 0;
	font-size: 1.05rem;
	font-weight: 600;
	opacity: 0.85;
}

.meta-section h3 {
	margin: 0;
	font-size: 0.95rem;
	font-weight: 600;
}

/* Nested per-panel / per-interface cards. */
.meta__panel-list,
.meta__iface-list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
}

.meta__panel,
.meta__iface {
	padding: 0.75rem 1rem;
	border-radius: 0.5rem;
	background: rgba(127, 127, 127, 0.06);
	border: 1px solid var(--card-border);
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

.meta__panel-label {
	margin: 0;
	font-size: 0.8rem;
	opacity: 0.65;
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.detail-grid dt {
	font-size: 0.85rem;
	opacity: 0.65;
	margin: 0;
}

.detail-grid dd {
	margin: 0;
	word-break: break-word;
}

/* Tag chips (server aliases etc.). Neutral palette so it reads in both themes. */
.tag-list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: 0.35rem;
}

.tag {
	display: inline-block;
	padding: 0.1rem 0.55rem;
	border-radius: 999px;
	background-color: rgba(127, 127, 127, 0.14);
	border: 1px solid var(--card-border);
	font-size: 0.85rem;
	line-height: 1.45;
	white-space: nowrap;
}

/* Anchor variant of .tag (alias chips in the servers detail panel). */
a.tag {
	color: inherit;
	text-decoration: none;
	transition: background-color 80ms ease, border-color 80ms ease;
}

a.tag:hover,
a.tag:focus-visible {
	background-color: rgba(127, 127, 127, 0.28);
	outline: none;
}

/* Force-delete escalation prompt — overrides .card__error's pre-line
   and centred text for the multi-paragraph form content. */
.users__force-delete-prompt {
	white-space: normal;
	text-align: left;
	padding: 0.75rem 1rem;
	font-size: 1rem;
}
.users__force-delete-prompt p {
	margin: 0 0 0.5rem;
}
.users__force-delete-prompt p:last-of-type {
	margin-bottom: 0.75rem;
}

/* Low-contrast 'resolved' badge on incidents.php. */
.tag--state-disabled {
	margin-right: 0.4rem;
	background-color: rgba(127, 127, 127, 0.20);
	border-color: rgba(127, 127, 127, 0.45);
	color: var(--text-muted);
}

/* Severity / state tag variants. Direct rgba/hex (not theme tokens)
   because the existing tag family doesn't go through theme abstraction. */
.tag--severity-critical {
	margin-left: 0.4rem;
	background-color: rgba(220, 38, 38, 0.18);
	border-color: rgba(220, 38, 38, 0.45);
	color: #dc2626;
}
/* Stale-unresolved (incidents) — warmer amber than severity-critical. */
.tag--stale {
	margin-left: 0.4rem;
	background-color: rgba(245, 158, 11, 0.18);
	border-color: rgba(245, 158, 11, 0.45);
	color: #d97706;
}
/* "Unknown type" footnote chip (servers, when type_meta.known=false). */
.tag--unknown-type {
	margin-left: 0.4rem;
	background-color: rgba(127, 127, 127, 0.12);
	border-color: rgba(127, 127, 127, 0.30);
	color: var(--text-muted);
	font-style: italic;
}

/* Server-transfer state chips. Colour palette intentionally tracks
   server_transfer.php's pending/running/succeeded/failed/cancelled
   enum 1:1, so a future addition to the enum needs a matching rule
   added here (CSS unmatch is benign — falls back to the .tag base). */
.tag--state-pending {
	background-color: rgba(127, 127, 127, 0.18);
	border-color: rgba(127, 127, 127, 0.45);
	color: var(--text-muted);
}
.tag--state-running {
	background-color: rgba(37, 99, 235, 0.18);
	border-color: rgba(37, 99, 235, 0.45);
	color: #2563eb;
}
.tag--state-succeeded {
	background-color: rgba(22, 163, 74, 0.18);
	border-color: rgba(22, 163, 74, 0.45);
	color: #16a34a;
}
.tag--state-failed {
	background-color: rgba(220, 38, 38, 0.18);
	border-color: rgba(220, 38, 38, 0.45);
	color: #dc2626;
}
.tag--state-cancelled {
	background-color: rgba(127, 127, 127, 0.12);
	border-color: rgba(127, 127, 127, 0.30);
	color: var(--text-muted);
	font-style: italic;
}

/* Bulk-resolve disclosure on incidents list. */
.incidents__bulk-resolve {
	width: 100%;
	margin: 0;
	padding: 1rem 1.25rem;
	border: 1px solid rgba(127, 127, 127, 0.25);
	border-radius: 0.6rem;
}
.incidents__bulk-resolve > summary {
	cursor: pointer;
	font-weight: 600;
}
.incidents__bulk-resolve[open] > summary {
	margin-bottom: 0.75rem;
}

.incidents__bulk-intro {
	margin-bottom: 1.25rem;
	text-align: left;
}
.incidents__bulk-section {
	padding: 0.25rem 0;
}
.incidents__bulk-title {
	cursor: pointer;
	font-size: 1rem;
	font-weight: 600;
	padding: 0.4rem 0.25rem;
	border-radius: 0.4rem;
	user-select: none;
	list-style: none;
	display: flex;
	align-items: center;
	gap: 0.5rem;
}
.incidents__bulk-title::-webkit-details-marker { display: none; }
.incidents__bulk-title::before {
	content: "\25B6";
	display: inline-block;
	font-size: 0.7em;
	transition: transform 80ms ease;
}
.incidents__bulk-section[open] > .incidents__bulk-title::before {
	transform: rotate(90deg);
}
.incidents__bulk-title:hover {
	background-color: rgba(127, 127, 127, 0.10);
}
.incidents__bulk-body {
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
	padding: 0.5rem 0.25rem 0 1.5rem;
}
.incidents__bulk-help {
	margin: 0;
	font-size: 0.9rem;
	opacity: 0.75;
}
.incidents__bulk-help code {
	font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
	font-size: 0.85em;
	padding: 0.05rem 0.3rem;
	border-radius: 3px;
	background-color: rgba(127, 127, 127, 0.18);
}
.incidents__bulk-sep {
	margin: 0.5rem 0;
	border: 0;
	border-top: 1px solid rgba(127, 127, 127, 0.25);
}
.incidents__bulk-actions {
	justify-content: center;
}

/* ============================================================
   Grid-list family. Per-row grid container; detail panel is a
   sibling block so it can't shift other rows' columns. Shared
   chrome here; per-page grid-template-columns below.
   ============================================================ */
.incidents__list,
.users__list,
.servers__list,
.sessions__admin-list,
.sftp-audit__list,
.workerbee__list,
.workerbee-deployments__list,
.server-transfer__list {
	display: flex;
	flex-direction: column;
	border-radius: 0.6rem;
	overflow: hidden;
	font-size: 0.95rem;
}
.incidents__head, .incidents__row,
.users__head, .users__row,
.servers__head, .servers__row,
.sessions__admin-head, .sessions__admin-row,
.sftp-audit__head, .sftp-audit__row,
.workerbee__head, .workerbee__row,
.workerbee-deployments__head, .workerbee-deployments__row,
.server-transfer__head, .server-transfer__row {
	display: grid;
	gap: 0.85rem;
	align-items: center;
	padding: 0.6rem 0.85rem;
	border-top: 1px solid transparent;
}
.incidents__head,
.users__head,
.servers__head,
.sessions__admin-head,
.sftp-audit__head,
.workerbee__head,
.workerbee-deployments__head,
.server-transfer__head {
	font-size: 0.85rem;
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	padding-top: 0.65rem;
	padding-bottom: 0.65rem;
}
.incidents__row > *,
.users__row > *,
.servers__row > *,
.sessions__admin-row > *,
.sftp-audit__row > *,
.workerbee__row > *,
.workerbee-deployments__row > *,
.server-transfer__row > * {
	min-width: 0;
	overflow-wrap: anywhere;
}
/* Admin sessions rows aren't whole-row click targets (the in-cell
   name-link handles navigation), so it's the only row family
   without cursor:pointer + transition. */
.incidents__row,
.users__row,
.servers__row,
.sftp-audit__row,
.workerbee__row,
.workerbee-deployments__row,
.server-transfer__row {
	cursor: pointer;
	transition: background-color 80ms ease;
}

/* Per-page grid column templates — head + row stay in sync. */
.incidents__head,
.incidents__row {
	grid-template-columns: 3rem 7rem minmax(0, 1fr) 11rem 5rem;
}

.incidents__cell-primary {
	font-weight: 500;
}
.incidents__cell-severity {
	text-align: center;
	white-space: nowrap;
}
.incidents__cell-severity .tag {
	margin-left: 0;        /* defeat the +0.4rem the tag variants ship */
	margin-right: 0.2rem;
}
.incidents__cell-actions,
.sftp-audit__cell-actions {
	text-align: right;
	white-space: nowrap;
}

/* Report-page variant: drop the Actions column, right-align When. */
.incidents__list--report .incidents__head,
.incidents__list--report .incidents__row {
	grid-template-columns: 3rem 7rem minmax(0, 1fr) 11rem;
}
.incidents__list--report .incidents__cell-actions {
	display: none;
}
.incidents__list--report .incidents__row > :nth-child(4),
.incidents__list--report .incidents__head > :nth-child(4) {
	text-align: right;
}

/* Detail panel chrome shared by users / servers / incidents. */
.users__detail,
.servers__detail,
.incidents__detail,
.sftp-audit__detail,
.server-transfer__detail,
.audit-log__detail,
.session-activity__detail {
	padding: 1rem 1.25rem 1.5rem;
	border-top: 1px solid transparent;
}
.users__detail > *,
.servers__detail > *,
.incidents__detail > *,
.sftp-audit__detail > *,
.server-transfer__detail > *,
.audit-log__detail > *,
.session-activity__detail > * {
	min-width: 0;
}
.users__detail .profile__actions .btn,
.servers__detail .profile__actions .btn,
.incidents__detail .profile__actions .btn {
	min-width: 0;
}
.users__detail textarea,
.servers__detail textarea,
.incidents__detail textarea {
	width: 100%;
	min-width: 0;
	box-sizing: border-box;
}
/* Vertically centre the Resolution-note panel. */
.incidents__detail .detail-split__right {
	justify-content: center;
}

/* Brief panel: tighter detail-grid for the short incident summary. */
.incidents__detail .detail-split__left .detail-grid {
	gap: 0.2rem 1rem;
	line-height: 1.35;
}
.incidents__detail .detail-split__left .detail-grid dt,
.incidents__detail .detail-split__left .detail-grid dd {
	margin: 0;
}
/* Normalise UUID/SID values across BOTH detail-split sides — left has
   SID/UUID rows in the brief, right has the resolved-By row. Defeats
   .link-action's font-size + margin and forces mono so ids align. */
.incidents__detail .detail-grid dd .link-action,
.incidents__detail .detail-grid dd .data-table__mono,
.server-transfer__detail .detail-grid dd .link-action,
.audit-log__detail .detail-grid dd .link-action {
	font-size: inherit;
	line-height: inherit;
	margin-left: 0;
	font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}

/* Identity section: 4-column grid (label + value × 2 pairs). */
.incidents__identity-grid {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr) auto minmax(0, 1fr);
	grid-auto-rows: auto;
	align-items: baseline;
	column-gap: 0.75rem;
	row-gap: 0.25rem;
	font-size: 0.9rem;
	line-height: 1.3;
}
.incidents__identity-label {
	color: var(--text-muted);
	font-size: 0.9rem;
	line-height: 1.3;
	white-space: nowrap;
}
/* Breathing room between the two label/value pairs. */
.incidents__identity-label:nth-of-type(2n) {
	padding-left: 2rem;
}
.incidents__identity-value {
	font-weight: 500;
	font-size: 0.9rem;
	line-height: 1.3;
	min-width: 0;
}
/* Force every value rendering to the same baseline metrics so the
   column reads even regardless of inner element type. */
.incidents__identity-value .link-action,
.incidents__identity-value .data-table__mono,
.incidents__identity-value a,
.incidents__identity-value span {
	font-size: 0.9rem;
	line-height: 1.3;
	margin: 0;
	padding: 0;
	vertical-align: baseline;
}
/* Compact tag chrome to fit the identity row height. */
.incidents__identity-value .tag {
	line-height: 1.2;
	padding: 0.05rem 0.5rem;
}

/* Full-report sections on incidents.php?view=<id>: span container width. */
.incidents__full-report {
	margin-top: 1.5rem;
	padding-top: 1.25rem;
	border-top: 1px solid rgba(127, 127, 127, 0.25);
	display: flex;
	flex-direction: column;
	gap: 1rem;
}
.incidents__section-title {
	margin: 0.5rem 0 0;
	font-size: 1rem;
	font-weight: 600;
}

/* Collapsible payload sections (stderr / stdout / details). */
.incidents__section {
	margin: 0;
}
.incidents__section > .incidents__section-title {
	cursor: pointer;
	user-select: none;
	list-style: none;
	display: flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.25rem 0.75rem;
	border-radius: 0.3rem;
}
.incidents__section > .incidents__section-title::-webkit-details-marker { display: none; }
.incidents__section > .incidents__section-title::before {
	content: "\25B6";
	display: inline-block;
	font-size: 0.7em;
	transition: transform 80ms ease;
}
.incidents__section[open] > .incidents__section-title::before {
	transform: rotate(90deg);
}
.incidents__section > .incidents__section-title:hover {
	background-color: rgba(127, 127, 127, 0.10);
}
/* Indent expanded payload to visually nest under its summary. */
.incidents__section > .incidents__pre {
	margin-top: 0.5rem;
	margin-left: 1.5rem;
}
.incidents__section > .incidents__identity-grid {
	margin-top: 0.5rem;
	padding-left: 1.5rem;
}
/* Wrap + cap stderr/stdout so a 60KB payload doesn't blow out the page. */
.incidents__pre {
	margin: 0;
	padding: 0.75rem 0.9rem;
	background-color: rgba(127, 127, 127, 0.08);
	border: 1px solid rgba(127, 127, 127, 0.25);
	border-radius: 4px;
	max-height: 24rem;
	overflow-y: auto;
	white-space: pre-wrap;
	word-break: break-word;
}

/* Removable variant: chip + zero-chrome × button on one baseline. */
.tag--removable {
	display: inline-flex;
	align-items: center;
	gap: 0.25rem;
}

.tag__remove {
	background: none;
	border: 0;
	font: inherit;
	font-size: 1rem;
	line-height: 1;
	cursor: pointer;
	opacity: 0.65;
	color: inherit;
	/* Larger hit zone so a near-miss with the cursor doesn't strip a
	   tag the user didn't mean to remove. Negative margin pulls the
	   chip back to roughly its original visual size; the padding is
	   pure click target. */
	padding: 0.3rem 0.4rem;
	margin: -0.2rem -0.3rem -0.2rem 0.1rem;
}
.tag__remove:hover,
.tag__remove:focus {
	opacity: 1;
}

/* Users grid-list. Columns: Level / User / Name / Email / Actions. */
.users__head,
.users__row {
	grid-template-columns: 6rem 11rem minmax(0, 1fr) minmax(0, 1fr) 8rem;
}

/* Uniform-width pill, horizontally centred in the Level cell. Fixed
   width (not min-width) so 'Anonymous' and 'User' render at exactly
   the same visual size. 5.5rem comfortably fits 'Anonymous' (the
   widest label, rendered ~5.3rem at the 0.78rem font in the system
   sans stack); the cell at 6rem leaves 0.5rem of dead space which
   .users__cell-level's text-align: center distributes 0.25rem on
   each side of the pill, so the gap to the USER column reads the
   same regardless of label length. Rule applies to both the static
   <span class="tag"> and the <button class="tag tag--actionable">
   impersonate trigger. */
.users__cell-level {
	text-align: center;
}
.users__cell-level .tag {
	display: inline-block;
	box-sizing: border-box;
	width: 5.5rem;
	padding: 0.1rem 0.4rem;
	font-size: 0.78rem;
	text-align: center;
}
/* The LEVEL column header centres to sit over the centred pills. */
.users__head > :first-child {
	text-align: center;
}

/* Right-align Actions header so its text right-edge matches the data
   row's Delete right-edge (anchoring both ends sidesteps the cell-vs-
   content-width mismatch a fixed left-shift can only approximate). */
.users__head > :last-child,
.servers__head > :last-child,
.server-transfer__head > :last-child {
	text-align: right;
}
.users__row .users__cell-user {
	font-weight: 500;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.users__cell-actions {
	text-align: right;
	white-space: nowrap;
	/* font-size: 0 collapses the inline whitespace between </form> and
	   </span> in PHP source that would otherwise push right-aligned
	   content ~4px left. Action links restore size via .link-action. */
	font-size: 0;
}

/* Row tint by account_state. Carries .users__list as an ancestor to
   beat the theme's hover/selected/focus-visible specificity, so the
   lifecycle signal wins over row-state highlighting. */
.users__list .users__row[data-account-state="disabled"] {
	background-color: rgba(220, 38, 38, 0.12);
}
.users__list .users__row[data-account-state="disabled"]:hover {
	background-color: rgba(220, 38, 38, 0.18);
}
.users__list .users__row[data-account-state="disabled"].is-selected,
.users__list .users__row[data-account-state="disabled"]:focus-visible {
	background-color: rgba(220, 38, 38, 0.24);
}
.users__list .users__row[data-account-state="pending_verification"] {
	background-color: rgba(202, 138, 4, 0.12);
}
.users__list .users__row[data-account-state="pending_verification"]:hover {
	background-color: rgba(202, 138, 4, 0.18);
}
.users__list .users__row[data-account-state="pending_verification"].is-selected,
.users__list .users__row[data-account-state="pending_verification"]:focus-visible {
	background-color: rgba(202, 138, 4, 0.24);
}

/* Servers grid-list. Columns: Hostname / IPv4 / IPv6 / Port / Type / Actions.
   Actions column carries up to 6 links (Connect, SFTP, View, Meta, Edit,
   Del); 17rem keeps Del visible without forcing hostname to wrap. Bump
   if a 7th action lands. */
.servers__head,
.servers__row {
	grid-template-columns: minmax(0, 1fr) 9rem 4rem 5rem 7rem 17rem;
}

/* SFTP audit grid-list. Columns: When / Server / Op / Result / Actions.
   Detail panel below the row carries Path, Size, Duration, Mode,
   Error detail. The --admin modifier adds a User column between When
   and Server for the cross-user variant (sftp_audit_admin.php). */
.sftp-audit__head,
.sftp-audit__row {
	grid-template-columns: 12rem minmax(0, 1fr) 7rem 5rem 5rem;
}
.sftp-audit__list--admin .sftp-audit__head,
.sftp-audit__list--admin .sftp-audit__row {
	grid-template-columns: 12rem minmax(0, 1fr) minmax(0, 1fr) 7rem 5rem 5rem;
}

/* Workerbee queue list. Columns: When (scheduled) / Task / Target / Retries / Actions.
   Detail panel carries parsed options blob (sealed fields stubbed). */
.workerbee__head,
.workerbee__row {
	grid-template-columns: 12rem minmax(0, 1fr) minmax(0, 1fr) 5rem 6rem;
}

/* Workerbee deployments list. Columns: When / User / Host / Op / State / Actions. */
.workerbee-deployments__head,
.workerbee-deployments__row {
	grid-template-columns: 12rem minmax(0, 1fr) minmax(0, 1fr) 9rem 6rem 6rem;
}

/* Server-transfer list (in-flight + recent share the same template).
   Columns: When / Source→Destination / State / Progress|Bytes / Actions.
   The progress/bytes column is wider than a typical numeric cell
   because it carries the bar + the "47% · 1,234 B" caption. */
.server-transfer__head,
.server-transfer__row {
	grid-template-columns: 7rem minmax(0, 1fr) 7rem 12rem 8rem;
}
/* Admin variant inserts an Operator column between When and the
   Source→Destination cell. server_transfer_admin.php opts in via
   `.server-transfer__list--admin`. */
.server-transfer__list--admin .server-transfer__head,
.server-transfer__list--admin .server-transfer__row {
	grid-template-columns: 7rem 9rem minmax(0, 1fr) 7rem 12rem 8rem;
}
.server-transfer__cell-hostname {
	font-weight: 500;
}

/* Access-groups admin tables (5-col label / identifier / timestamp /
   actor / actions). Reuses the .server-transfer__list shell but with
   a column template tuned for the access_groups.php Name|Description,
   Members User|UUID, and Servers Hostname|SID layouts. */
.access-groups__list--admin .server-transfer__head,
.access-groups__list--admin .server-transfer__row {
	grid-template-columns: minmax(10rem, 1fr) minmax(15rem, 2fr) 10rem 10rem 8rem;
}
/* Selected-row marker for the access-groups list when ?view=<uuid> is
   set. Subtle highlight, no border-shift (avoid layout jitter). The
   themes file owns the actual color via a CSS variable token. */
.access-groups__list--admin .server-transfer__row.is-selected {
	background-color: rgba(99, 102, 241, 0.12);
}
/* profile.php's "My access" row table (Name|Description). 2-col grid
   with balanced columns when rendered full-width, so neither column
   dominates. Originally introduced for me_access.php; that page was
   later folded into profile.php. */
.access-groups__list--simple .server-transfer__head,
.access-groups__list--simple .server-transfer__row {
	grid-template-columns: minmax(10rem, 1fr) minmax(15rem, 1fr);
	cursor: default;
}
/* Override the global .server-transfer__head > :last-child right-align
   for --simple lists: their last column is data (Description / SID),
   not an Actions cell, so the header should stay left-aligned to match
   the cells below. */
.access-groups__list--simple .server-transfer__head > :last-child {
	text-align: left;
}

/* Inline detail panel content rhythm for access_groups.php — keep
   vertical gaps consistent between the Edit form, metadata <dl>, and
   collapsible subsections; left-indent each subsection's body so the
   members/servers tables visibly belong to their <summary>. Scoped via
   .access-groups__detail so other .server-transfer__detail consumers
   (server_transfer_admin) aren't affected. */
.access-groups__detail > * + * {
	margin-top: 1rem;
}
.access-groups__detail .settings__section > :not(summary) {
	margin-left: 0.85rem;
}
/* "Excluded servers" is wider than the default detail-grid dt column
   (6rem). Widen here so the label stays on one line; values still
   share the remaining width. */
.access-groups__detail > .detail-grid {
	grid-template-columns: 10rem 1fr;
}
/* Horizontal divider between the per-group metadata (Group UUID +
   counts) and the Members/Excluded servers row. Matches the
   .settings__section + .settings__section visual treatment used
   elsewhere on the page. */
.access-groups__detail > .detail-split {
	margin-top: 1.5rem;
	padding-top: 1.25rem;
	border-top: 1px solid var(--card-border);
}

/* Tighter gap between the top-level Create-form section and the
   Groups section — the global .settings__section + .settings__section
   rule paints a heavy 2.75rem-plus-divider gap that's overkill for
   two distinct top-level controls (form vs list). Trim to a modest
   margin with no divider line. */
main[data-page="access_groups"] > .list > .settings__section + .settings__section {
	margin-top: 0.75rem;
	padding-top: 0;
	border-top: 0;
}

/* Members + Excluded servers sit side-by-side via .detail-split.
   When they're siblings inside a grid (not stacked vertically) the
   global .settings__section + .settings__section margin/padding/border
   rule paints a spurious horizontal HORIZONTAL separator on the right
   column — undo it. The vertical divider between the two columns is
   added separately below. */
.access-groups__detail .detail-split > .settings__section + .settings__section {
	margin-top: 0;
	padding-top: 0;
	border-top: 0;
	/* Vertical divider between the two side-by-side sections. The
	   .detail-split gap (2rem) gives breathing room; the border sits
	   inside that gap on the right column's left edge. */
	padding-left: 1.5rem;
	border-left: 1px solid var(--card-border);
}
/* Compact 2-col table variant used inside the side-by-side Members
   and Excluded servers sections (primary identifier | actions).
   Narrower than --admin so the table fits within a half-column. */
.access-groups__list--compact .server-transfer__head,
.access-groups__list--compact .server-transfer__row {
	grid-template-columns: minmax(10rem, 1fr) 6rem;
}
/* .link-action's default margin-left: 0.6rem is intended for spacing
   between sibling actions in an actions cell. When a .link-action is
   the first/only child of its cell (e.g. the Name column of the
   access-groups list row), that margin shows up as spurious leading
   whitespace. Drop it for first-child instances within row cells. */
.access-groups__list--admin .server-transfer__row > [role="cell"] > .link-action:first-child,
.access-groups__list--compact .server-transfer__row > [role="cell"] > .link-action:first-child,
.access-groups__list--simple .server-transfer__row > [role="cell"] > .link-action:first-child,
.audit-log__list .server-transfer__row > [role="cell"] > .link-action:first-child {
	margin-left: 0;
}

/* audit_log.php: 5-column grid (When | Actor | Route | Result | Actions).
   Target moved into the inline detail panel (per design feedback) so the
   row stays glanceable. Actions cell right-aligns its single "View" link
   per the Actions-column convention shared with other admin lists. */
.audit-log__list .server-transfer__head,
.audit-log__list .server-transfer__row {
	grid-template-columns: minmax(11rem, 1fr)    /* When */
	                       minmax(7rem,  1.2fr)  /* Actor */
	                       minmax(10rem, 1fr)    /* Route */
	                       minmax(5rem,  0.5fr)  /* Result */
	                       6rem;                 /* Actions */
}
/* The View link in the Actions cell right-aligns like every other
   admin-list Actions column (sftp_audit, users, servers). */
.audit-log__list .audit-log__cell-actions {
	text-align: right;
	white-space: nowrap;
}
/* Route value (e.g. `deployment.retry`): render as bold mono text so
   the dotted identifier is scannable without the badge-pill padding
   (the base .badge class has no background variant for "route",
   leaving padding as invisible whitespace that looks like indent). */
.audit-log__cell-route,
.audit-log__detail-route {
	font-weight: 600;
}
/* Audit-log row is non-interactive (no row-click target — only the
   "View" link in the actions cell toggles state). Drop the
   pointer-cursor + hover transition inherited from the shared
   .server-transfer__row rule above. */
.audit-log__list .server-transfer__row {
	cursor: default;
}
/* Filter form: responsive grid that wraps at ~13rem per cell. 7 filter
   fields + the actions cell = 8 total cells; at the common 4-col
   layout that produces 2 rows of 4, with Apply / Clear sitting in the
   last cell (row 2, col 4) inline with Until. Narrower viewports
   collapse to 3 / 2 / 1 columns as needed. */
.audit-log__filters {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
	gap: 0.6rem 0.85rem;
	align-items: end;
}
.audit-log__filters .audit-log__filter-field {
	margin: 0;
}
.audit-log__filter-actions {
	display: flex;
	gap: 0.6rem;
	align-self: end;
}
/* Stretch the buttons to fill the cell width so Apply (alone) or
   Apply+Clear (split evenly) line up with the input above them
   (Target id) instead of sitting as a small button hugging the
   right edge with empty space to its left. text-align: center
   needed because flex: 1 widens the button beyond its natural
   inline-block width — without it the label sits left-aligned
   inside a wide button (the Apply <button> auto-centres via the
   element default; <a class="btn"> Clear doesn't). */
.audit-log__filter-actions .btn {
	flex: 1;
	min-width: 0;
	text-align: center;
}
/* The details JSON dropdown on the right side of the detail panel.
   The <details> + <summary> handles the toggle natively. Spacing
   matches .detail-split__right's gap so it sits flush with the
   panel title above. */
.audit-log__detail-json-toggle {
	display: block;
	width: 100%;
}
.audit-log__detail-json-summary {
	cursor: pointer;
	padding: 0.25rem 0;
	font-size: 0.9rem;
	font-weight: 500;
}
.audit-log__detail-json {
	margin: 0.35rem 0 0 0;
	padding: 0.5rem 0.65rem;
	font-size: 0.85rem;
	border-radius: 0.4rem;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
}

/* Re-use the audit-cell font weight for hostname/user cells in workerbee. */
.workerbee__row .workerbee__cell-target,
.workerbee-deployments__row .workerbee-deployments__cell-target {
	font-weight: 500;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* session_activity.php — derived view over audit_log scoped to
   session-credential issuance. Layered on top of the .users__list
   family (matches the audit-log __list pattern: reuse an existing
   list family + override grid-template-columns under a scoping
   class). The page applies BOTH `.users__list` AND
   `.session-activity__list` to the wrapper; .users__head/.users__row
   on the children pick up the shared chrome (padding, font size,
   uppercase head, theme background, hover/selected states). */

/* Aggregate view: 8 columns (User, Root, Ephemeral, Fail, By admin,
   First, Last, Actions). Actions sits last so the global
   .users__head > :last-child { text-align: right } rule applies to
   it — the page's drill-down "View" link right-aligns like every
   other admin list's Actions column. */
.session-activity__list--aggregate .users__head,
.session-activity__list--aggregate .users__row {
	/* User column gets the flexible track (minmax(0, 1fr) — same
	   shape users.php uses for its name/email column) so it
	   absorbs all remaining row width after the fixed-width
	   numeric + timestamp + actions columns. The 36-char UUID
	   below the username renders at 0.78rem mono (~17rem wide)
	   which fits inside the remaining ~18rem when the .list
	   container is at its 72rem max-width.

	   First / Last are mono ISO timestamps (19 chars × ~0.55rem).
	   11rem fits without truncation. Actions is sized to
	   "View all" plus the link-action margin reset. */
	grid-template-columns: minmax(0, 1fr)          /* User */
	                       2.5rem                  /* Root */
	                       5.5rem                  /* Ephemeral */
	                       2.5rem                  /* Fail */
	                       5rem                    /* By admin */
	                       10.25rem                /* First */
	                       10.25rem                /* Last */
	                       5rem;                   /* Actions */
}
/* The User column header inherits the global
   `.users__head > :first-child { text-align: center }` rule which is
   intentional on users.php (the LEVEL pill column centres) but wrong
   here — our first column is text data, not a centred pill. Override
   with matching-then-higher specificity (3 selectors) so the override
   wins. */
.session-activity__list .users__head > :first-child {
	text-align: left;
}
/* Chronological view: 5 columns (When, Server, Kind, Result, Actions).
   Actor + Beneficiary + Source IP + Row id + admin-issued marker
   moved into the inline detail panel — matches users.php / servers.php
   / audit_log.php pattern. Keeps the row glanceable; full record is
   one click away. */
.session-activity__list--chronological .users__head,
.session-activity__list--chronological .users__row {
	/* Kind column at 7rem fits the "EPHEMERAL" badge (9 caps + badge
	   padding + tracking ≈ 5.7rem; the buffer absorbs different mono
	   character widths across themes). */
	grid-template-columns: 11rem                   /* When */
	                       minmax(0, 1fr)          /* Server (hostname + sid) */
	                       7rem                    /* Kind */
	                       5rem                    /* Result */
	                       5rem;                   /* Actions */
}
/* Zebra striping. The default .users__row:nth-child(4n) rule from
   the theme stripes 1-in-4 rows because users.php interleaves row
   and detail-panel siblings. Session-activity has no detail panels,
   so an n-th rule based on natural row positions zebras correctly.
   :nth-child(odd) starts the alternate background on positions
   1,3,5,... — but the .users__head is at child position 1, so the
   actual alternation lands on rows 2,4,6 which is what we want.
   Override the inherited 4n rule first to clear its stripe. */
/* Row-click selection (handled by session_activity.js + list-page.js)
   feels right with a pointer cursor — matches the users / servers
   row family. Zebra / hover / selected backgrounds live in the theme
   files (default.css + light.css) so colour values stay there;
   session-activity-scoped rules in themes also have to match the
   specificity of the zebra rule (3 selectors) so hover beats the
   stripe. */
.session-activity__list .users__row {
	cursor: pointer;
}
/* Reset the inline margin that .link-action carries by default; in
   admin lists that margin spaces successive action links from each
   other, but when a link-action is the first child of a non-Actions
   cell (e.g. the username in the User column) it shows up as
   spurious leading whitespace. Same fix the audit-log list applies. */
.session-activity__list .users__row > [role="cell"] > .link-action:first-child {
	margin-left: 0;
}
/* Stacked-identifier cell: human label on top (link, font-weight bump),
   pasteable UUID/sid on bottom (muted mono, smaller font). Used by
   the aggregate User column AND the chronological Server cell; same
   layout idiom, single class. */
.session-activity__cell-stack {
	display: flex;
	flex-direction: column;
	gap: 0.1rem;
	overflow: hidden;
	min-width: 0;
}
.session-activity__cell-stack .link-action {
	font-size: 0.95rem;
	font-weight: 500;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	/* The .link-action class ships with margin-left: 0.6rem to space
	   successive action links inside an Actions cell. Inside a stack
	   cell the link is primary content, not an action — drop the
	   margin so it aligns flush with the column-start. The first-
	   child override rule scoped to [role="cell"] further up doesn't
	   reach this link because it's nested under .cell-stack. */
	margin-left: 0;
}
.session-activity__uuid {
	font-size: 0.78rem;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
/* Chronological-row Kind cell — plain bold uppercase mono text (no
   badge). The badge's internal padding made the value visually offset
   from the column-start; plain text aligns flush with the header.
   Root vs ephemeral discrimination via font weight + colour
   (handled in the theme — themes set the --root colour). */
.session-activity__cell-kind {
	font-weight: 600;
	font-size: 0.8rem;
	letter-spacing: 0.04em;
	text-transform: uppercase;
}
/* Detail panel: 5-column grid (title | value | divider | title | value).
   dt-dd pairs sit naturally in cols 1-2 and 4-5; an explicit .__sep
   element fills col 3 with a thin vertical divider that spans the
   row's height via align-self: stretch. */
.session-activity__detail-grid {
	display: grid;
	/* Title columns at 7rem so the longest current label ("Beneficiary"
	   at ~5.5rem) has comfortable breathing room and shorter labels
	   ("Actor", "Row id") aren't crammed against their values. Value
	   columns flex to fill the remaining width. */
	grid-template-columns: 7rem minmax(0, 1fr)
	                       1px
	                       7rem minmax(0, 1fr);
	column-gap: 0.85rem;
	row-gap: 0.55rem;
	align-items: baseline;
	margin: 0;
}
.session-activity__detail-grid > dt {
	font-size: 0.9rem;
	color: var(--text-muted);
	white-space: nowrap;
}
.session-activity__detail-grid > dd {
	margin: 0;
	font-size: 0.9rem;
	font-weight: 500;
	min-width: 0;
	overflow-wrap: anywhere;
}
.session-activity__detail-grid__sep {
	grid-row: span 1;
	align-self: stretch;
	background-color: var(--card-border);
}
/* The link-action / data-table__mono inside the detail-grid dd cells
   needs the same normalisation as .incidents__detail does — drop the
   action-column margin-left, inherit row font sizing, and force mono
   so identifiers line up vertically. */
.session-activity__detail-grid dd .link-action,
.session-activity__detail-grid dd .data-table__mono {
	font-size: inherit;
	line-height: inherit;
	margin-left: 0;
	font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}
/* Time cells: nowrap so the ISO timestamp doesn't break mid-string.
   Default left-align is fine for the First / Last columns now that
   they are no longer the last grid child (Actions took that slot). */
.session-activity__cell-time {
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
/* Actions cell mirrors the users / servers convention. text-align:
   right is already applied by the global .users__head > :last-child
   rule; this just adds the data-row counterpart. */
.session-activity__list .session-activity__cell-actions {
	text-align: right;
	white-space: nowrap;
}
/* Inline admin-issued chip in the chronological beneficiary cell.
   Reuses the .tag base + .tag--state-pending colour. */
.session-activity__admin-flag {
	font-size: 0.72rem;
	margin-left: 0.4rem;
	padding: 0.05rem 0.4rem;
}
/* The view-mode switcher (aggregate vs chronological). Pill-shaped
   anchors styled to mirror the .btn--ghost / .btn--primary pairing
   for a segmented-control look. */
.session-activity__view-tabs {
	display: flex;
	gap: 0.4rem;
	margin-bottom: 0.5rem;
}
.session-activity__view-tab {
	padding: 0.4rem 0.85rem;
	border-radius: 0.5rem;
	font-size: 0.9rem;
	font-weight: 500;
	text-decoration: none;
	border: 1px solid var(--card-border);
	background: transparent;
	color: inherit;
	transition: background-color 80ms ease, border-color 80ms ease;
}
.session-activity__view-tab:hover,
.session-activity__view-tab:focus-visible {
	border-color: var(--accent);
}
.session-activity__view-tab.is-active {
	background: var(--accent);
	color: var(--accent-text);
	border-color: var(--accent);
}

/* Workerbee action cells (Run now / Retry) right-align like other rows. */
.workerbee__cell-actions,
.workerbee-deployments__cell-actions {
	text-align: right;
	white-space: nowrap;
}

/* Deployment-state badge variants. The base .badge--online/--offline
   pair doesn't map cleanly onto the four lifecycle states, so dedicated
   variants. Colors come from the theme files; this rule just declares
   the namespace. */
.badge--pending,
.badge--in_progress,
.badge--succeeded,
.badge--failed {
	/* Inherits .badge base styling (padding / radius / font-size). The
	   color/background is theme-specific - see themes/default.css and
	   themes/light.css. */
}

/* Operator-typed scratch area for the parsed options blob. */
.workerbee__options {
	font-family: var(--font-mono, monospace);
	font-size: 0.85rem;
	white-space: pre-wrap;
	word-break: break-word;
	padding: 0.5rem;
	border-radius: 0.3rem;
	max-height: 16rem;
	overflow: auto;
}

/* Sealed-payload stub renderer — visually distinct from raw values
   so an operator can't confuse a sealed entry with cleartext. */
.workerbee__sealed {
	font-style: italic;
	opacity: 0.7;
}

/* Layout helpers for workerbee.php — extracted to scoped classes
   because strict CSP blocks inline style="..." attributes. Pure
   layout / typography, no color (themes own color). */
.workerbee__count-suffix {
	font-weight: 400;
	font-size: 0.9rem;
	margin-left: 0.35rem;
}
.workerbee__inline-form {
	display: inline-block;
}
.workerbee__state-label {
	display: inline-block;
	margin-right: 0.75rem;
}
.workerbee__inline-spacer {
	margin-left: 0.4rem;
}
/* The form-row inside a collapsible section. Compact horizontal layout
   with checkboxes + filter inputs aligned. */
.workerbee__filters {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
	align-items: flex-end;
	margin-bottom: 0.5rem;
}
.workerbee__filters fieldset {
	border: 1px solid var(--card-border);
	border-radius: 0.4rem;
	padding: 0.25rem 0.6rem;
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
	align-items: center;
	margin: 0;
}
.workerbee__filters fieldset legend {
	font-size: 0.75rem;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	opacity: 0.7;
	padding: 0 0.25rem;
}
/* Workerbee row-of-tags wrapper (used by detail-grid--paired metric
   strip and the by-task-type .tag-list). */
.workerbee__row-of-tags {
	margin: 0 0 0.4rem;
}
/* Tighten the outer .list flex-gap on the workerbee page so the
   per-section margin/padding/divider does the visual work without
   the global 1rem gap stacking on top. */
main[data-page="workerbee"] .list {
	gap: 0.5rem;
}
/* Tighten the detail-grid metric strip a notch — the four-cell paired
   layout had row-gap inherited from the global 0.5rem 1rem. */
.workerbee__row-of-tags.detail-grid--paired {
	row-gap: 0.3rem;
}
/* Tighten the next-due grid-list rows slightly — they sit inside a
   denser collapsible section so the global 0.6rem 0.85rem padding
   reads a little roomy. */
main[data-page="workerbee"] .workerbee__head,
main[data-page="workerbee"] .workerbee__row,
main[data-page="workerbee"] .workerbee-deployments__head,
main[data-page="workerbee"] .workerbee-deployments__row {
	padding-top: 0.4rem;
	padding-bottom: 0.4rem;
}

.servers__row .servers__cell-hostname,
.sftp-audit__row .sftp-audit__cell-server {
	font-weight: 500;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.servers__row .servers__cell-actions {
	text-align: right;
	white-space: nowrap;
	/* See .users__cell-actions for the font-size:0 rationale. */
	font-size: 0;
}

/* ============================================================
   Collapsible <details open> section family — originally introduced
   for settings.php, now also used by workerbee.php. Union-selector
   form so adding a new page-scoped section class only requires
   joining the union (per the codebase's "extend existing CSS"
   convention).
   ============================================================ */
.settings__section,
.workerbee__section {
	margin-top: 0.5rem;
}
.settings__section + .settings__section {
	margin-top: 1.5rem;
	padding-top: 1.25rem;
	border-top: 1px solid var(--card-border);
}
.workerbee__section + .workerbee__section {
	margin-top: 0.5rem;
	padding-top: 0.5rem;
	border-top: 1px solid var(--card-border);
}

.settings__section-title,
.workerbee__section-title {
	margin: 0 0 0.5rem;
	font-size: 1.1rem;
	font-weight: 600;
	cursor: pointer;
	list-style: none;
	display: flex;
	align-items: center;
	gap: 0.5rem;
	user-select: none;
}
.workerbee__section-title {
	margin-bottom: 0.35rem;
}
.settings__section-title::-webkit-details-marker,
.workerbee__section-title::-webkit-details-marker { display: none; }
.settings__section-title::before,
.workerbee__section-title::before {
	content: '\25B6';
	font-size: 0.75rem;
	opacity: 0.6;
	transition: transform 120ms ease;
	display: inline-block;
}
.settings__section[open] > .settings__section-title::before,
.workerbee__section[open] > .workerbee__section-title::before {
	transform: rotate(90deg);
}

/* Left-align hints in the settings layout (default is centred for login). */
.settings__section .card__hint,
.workerbee__section .card__hint {
	text-align: left;
	line-height: 1.5;
	margin-bottom: 1rem;
}
/* Workerbee hints are denser — they sit just above a list/table, so
   the extra breathing room of the settings layout becomes wasted
   vertical real estate. */
.workerbee__section .card__hint {
	margin-top: 0.5rem;
	margin-bottom: 0.35rem;
	line-height: 1.35;
}

/* Indent section body so it visually nests under the flush-left summary. */
.settings__section > :not(summary) {
	margin-left: 1.5rem;
	margin-right: 1.5rem;
}
/* Workerbee body uses a smaller indent so the dense grids inside don't
   feel cramped by the side padding. */
.workerbee__section > :not(summary) {
	margin-left: 0.75rem;
	margin-right: 0.75rem;
}

/* Self-service SSH session cards (sessions.php). Two per row, one <720px. */
.sessions__list {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0.6rem;
}
@media (max-width: 720px) {
	.sessions__list {
		grid-template-columns: minmax(0, 1fr);
	}
}
.sessions__card {
	padding: 0.6rem 1rem 0.7rem;
	border: 1px solid var(--card-border);
	border-radius: 0.5rem;
	display: flex;
	flex-direction: column;
	gap: 0.05rem;
}
.sessions__card-head {
	display: flex;
	align-items: baseline;
	gap: 0.6rem;
	row-gap: 0.2rem;
	flex-wrap: wrap;
}
/* Push Expired chip to the right edge of the card head. */
.sessions__card-status {
	margin-left: auto;
}
/* Defeat the global .link-action margin so the sid aligns flush-left. */
.sessions__card-sid.link-action {
	margin-left: 0;
}
.sessions__card-meta {
	font-size: 0.9rem;
	line-height: 1.2;
	color: var(--text-muted);
}
.sessions__card-cleanup-note {
	margin-top: 0;
}
/* <button class="link-action"> reset so it renders like a.link-action.
   Inherit font-family/weight/style/line-height (NOT the `font:` shorthand
   - that would also overwrite font-size, which .link-action sets to
   0.85rem and we want to keep). */
button.link-action {
	background: transparent;
	border: 0;
	padding: 0;
	font-family: inherit;
	font-weight: inherit;
	font-style: inherit;
	line-height: inherit;
	cursor: pointer;
}

/* Force-password-reset success row (users.php inline detail panel).
   The mono password value sits beside the Show/Hide + Copy buttons -
   flex+gap so they read as one row rather than wrapping per-element.
   The buttons get .link-action chrome but we zero its built-in
   margin-left here (the row's gap handles spacing; the global rule
   would compound to ~1.2rem between items). */
.users__pw-row {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	flex-wrap: wrap;
}
.users__pw-action.link-action {
	margin-left: 0;
}

/* Admin block on sessions.php. Columns: User / Server / Expires / Actions. */
.sessions__admin-search {
	margin-bottom: 0.75rem;
}
.sessions__admin-head,
.sessions__admin-row {
	grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.4fr) 13rem 6rem;
}
/* UUID/SID mono link sits under the friendly name; smaller so the eye
   reads "name (small id)" rather than two same-weight lines. */
.sessions__admin-row .data-table__mono {
	display: block;
	font-size: 0.78rem;
}
/* Friendly-name line is also clickable (re-runs search filtered to it)
   but must read as plain text, not a link. */
.sessions__admin-name-link,
.sessions__admin-name-link:hover,
.sessions__admin-name-link:focus {
	color: inherit;
	text-decoration: none;
}
.sessions__admin-cell-actions {
	text-align: right;
}
/* Zero the global .link-action margin-left inside admin cells where the
   link is the only child (the 0.6rem default is for inline action chains). */
.sessions__admin-list .link-action {
	margin-left: 0;
}

/* Centre actions and pin to card bottom so cards line up row-to-row
   even when meta blocks differ in height. */
.sessions__card-actions.profile__actions {
	justify-content: center;
	margin-top: auto;
	padding-top: 0.5rem;
}
.sessions__card-actions.profile__actions .btn {
	min-width: 0;
}

/* Per-tool divider; first tool skips it (intro hint sits above). */
.settings__tool {
	margin-top: 1.25rem;
	padding-top: 1.25rem;
	border-top: 1px solid var(--card-border);
}
.settings__tool:first-of-type {
	margin-top: 0.75rem;
	padding-top: 0;
	border-top: 0;
}
.settings__tool-title {
	margin: 0;
	font-size: 0.95rem;
	font-weight: 600;
	cursor: pointer;
	list-style: none;
	display: flex;
	align-items: center;
	gap: 0.5rem;
	user-select: none;
}
.settings__tool-title::-webkit-details-marker { display: none; }
.settings__tool-title::before {
	content: '\25B6';
	font-size: 0.7rem;
	opacity: 0.6;
	transition: transform 120ms ease;
	display: inline-block;
}
.settings__tool[open] > .settings__tool-title::before {
	transform: rotate(90deg);
}
/* Indent tool body to nest under the flush summary heading. */
.settings__tool[open] > :not(summary) {
	margin-top: 0.5rem;
	margin-left: 1.5rem;
}

/* Inline label/input + submit row (cache-clear tool). */
.settings__inline-row {
	display: flex;
	align-items: flex-end;
	gap: 0.75rem;
	flex-wrap: wrap;
	max-width: 40rem;
	margin: 0 auto;
	justify-content: center;
}
.settings__inline-row .field {
	flex: 1 1 18rem;
	min-width: 0;
}

/* Centre tool action rows (override .profile__actions' default left). */
.settings__tool .profile__actions {
	justify-content: center;
}

/* Tag input widget. Wraps a hidden <input> for form submission; the
   widget renders chips + an inline "add" field. Mimics .field__input chrome. */
.tag-input {
	padding: 0.35rem;
	border: 1px solid transparent;
	border-radius: 0.4rem;
	cursor: text;
	transition: background-color 80ms ease, border-color 80ms ease;
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
}

.tag-input__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: 0.3rem;
	align-items: center;
}

.tag-input__field {
	width: 100%;
	background: transparent;
	border: 0;
	outline: none;
	font: inherit;
	color: inherit;
	padding: 0.25rem 0.2rem;
}

/* Danger variant of the inline row action (Delete on each server row). */
.link-action--danger {
	background: none;
	border: 0;
	font: inherit;
	cursor: pointer;
	padding: 0;
}

.data-table__inline-form {
	display: inline;
	margin: 0;
}

/* Clickable level-tag variant (impersonate trigger on users.php). */
button.tag--actionable {
	font-family: inherit;
	font-size: 0.85rem;
	line-height: 1.45;
	color: inherit;
	cursor: pointer;
	transition: background-color 80ms ease, border-color 80ms ease;
}
button.tag--actionable:hover,
button.tag--actionable:focus-visible {
	background-color: rgba(255, 165, 0, 0.18);
	border-color: rgba(255, 165, 0, 0.55);
	outline: none;
}

/* Impersonation banner. Loud on purpose: an operator forgetting they're
   impersonating risks destructive action under the wrong audit context. */
.impersonate-banner {
	background: linear-gradient(180deg, rgba(255, 165, 0, 0.95), rgba(255, 140, 0, 0.92));
	color: #1f1500;
	border-bottom: 2px solid rgba(120, 70, 0, 0.7);
	box-shadow: 0 1px 0 rgba(0, 0, 0, 0.18) inset;
	font-weight: 600;
}

.impersonate-banner__inner {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 1rem;
	padding: 0.55rem 1.25rem;
	flex-wrap: wrap;
}

.impersonate-banner__icon {
	font-size: 1.05rem;
	line-height: 1;
}

.impersonate-banner__text {
	font-size: 0.95rem;
	line-height: 1.4;
	letter-spacing: 0.01em;
}

.impersonate-banner__text strong {
	font-weight: 800;
}

.impersonate-banner__stop-form {
	display: inline;
	margin: 0;
}

.impersonate-banner__stop {
	font-family: inherit;
	font-size: 0.85rem;
	font-weight: 700;
	line-height: 1;
	padding: 0.4rem 0.85rem;
	border-radius: 4px;
	border: 1px solid rgba(40, 25, 0, 0.65);
	background: rgba(255, 255, 255, 0.92);
	color: #1f1500;
	cursor: pointer;
	transition: background-color 80ms ease, transform 60ms ease;
}
.impersonate-banner__stop:hover,
.impersonate-banner__stop:focus-visible {
	background: #ffffff;
	outline: none;
}
.impersonate-banner__stop:active {
	transform: translateY(1px);
}

/* Web SSH terminal page (terminal.php). Full viewport, no topbar/foot. */
html.terminal-page {
	height: 100%;
}
body.terminal-page {
	margin: 0;
	min-height: 0;
	height: 100vh;
	display: flex;
	flex-direction: column;
	overflow: hidden;
}

.terminal__head {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 1rem;
	flex-wrap: wrap;
	padding: 0.5rem 1rem;
	background: var(--card-bg);
	border-bottom: 1px solid var(--card-border);
}

.terminal__title {
	margin: 0;
	font-size: 1.05rem;
	flex: 1;
	min-width: 0;
	display: flex;
	align-items: baseline;
	gap: 0.65rem;
	overflow: hidden;
}

.terminal__sid {
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 0.8rem;
	opacity: 0.55;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* Session-mode badge (only rendered for privileged modes). */
.terminal__mode {
	font-size: 0.7rem;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	padding: 0.1rem 0.5rem;
	border-radius: 999px;
	border: 1px solid currentColor;
}

.terminal__mode--root {
	color: #dc2626;
	background: rgba(220, 38, 38, 0.12);
}

/* Status pill in the header: state attr drives the colour. */
.terminal__status {
	font-size: 0.85rem;
	padding: 0.15rem 0.6rem;
	border-radius: 999px;
	border: 1px solid var(--card-border);
	background: var(--card-bg);
}
.terminal__status[data-state="connecting"]   { color: #d97706; }
.terminal__status[data-state="connected"]    { color: #16a34a; }
.terminal__status[data-state="disconnected"] { opacity: 0.65; }
.terminal__status[data-state="error"]        { color: #dc2626; }

/* Row-flex container that wraps .terminal__shell (xterm) AND the
   optional side-pane SFTP browser. Single flex parent so opening
   the pane just unhides it — no DOM restructure, no relayout cost
   beyond the resize observer that refits xterm. min-height: 0 +
   min-width: 0 let both flex children shrink below their intrinsic
   sizes (xterm + the file list are happiest scrolling, not
   overflowing the page). */
.terminal__split {
	flex: 1;
	display: flex;
	flex-direction: row;
	min-height: 0;
	min-width: 0;
}

/* min-height: 0 / min-width: 0 (in the split context) let this flex
   child shrink so xterm-addon-fit's intrinsic measurements don't
   pin a minimum width. Background MUST match xterm's own theme
   background (terminal.js sets it to var(--card-bg, '#0e0e1a'))
   so the few-pixel cell-rounding remainder under the canvas blends
   in instead of showing as a pure-#000 strip against xterm's
   dark-navy theme. */
.terminal__shell {
	flex: 1 1 0;
	min-height: 0;
	min-width: 0;
	background: var(--card-bg, #0e0e1a);
	overflow: hidden;
}

/* Padding on .xterm (not .terminal__shell) because xterm-addon-fit
   reads .xterm's padding when measuring available space. */
.terminal__shell .xterm {
	padding: 0.5rem;
	box-sizing: border-box;
}

/* Side-pane SFTP browser hosted on terminal.php. Width is
   clamp(22rem, 35%, 38rem) — 35% of viewport between the bounds,
   but pinned to ~22rem when the viewport is narrow (so file names
   don't compress past readability) and capped at ~38rem when the
   viewport is very wide (so the terminal doesn't lose more space
   than necessary on ultrawide monitors). Operator can drag the
   .terminal__split-handle to override, which sets flex-basis as
   an inline style (clamp() only applies until the operator drags). */
.terminal__sftp-pane {
	flex: 0 0 clamp(22rem, 35%, 38rem);
	display: flex;
	flex-direction: column;
	min-width: 0;
	min-height: 0;
	background: var(--card-bg);
}

/* Drag handle between the terminal shell and the SFTP pane. 5px
   wide so it has a real hit-target without claiming much screen
   real estate. The vertical border line you see in the closed-pane
   layout is replaced by the handle when the pane is open; visually
   it reads the same when idle, brighter on hover/drag. */
.terminal__split-handle {
	flex: 0 0 5px;
	background: var(--card-border);
	cursor: col-resize;
	transition: background-color 80ms ease;
	user-select: none;
}
.terminal__split-handle[hidden] {
	display: none;
}
.terminal__split-handle:hover,
.terminal__split--dragging .terminal__split-handle {
	background: #7e57c2;
}

/* Body-level class added during a drag so the cursor stays
   consistent even when the mouse moves off the handle, and text
   selection doesn't accidentally flicker as the pointer crosses
   text-bearing children. */
body.terminal__split--dragging,
body.terminal__split--dragging * {
	cursor: col-resize !important;
	user-select: none !important;
}
.terminal__sftp-pane[hidden] {
	display: none;
}

.terminal__sftp-pane-head {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 0.6rem;
	padding: 0.35rem 0.75rem;
	border-bottom: 1px solid var(--card-border);
}
.terminal__sftp-pane-title {
	margin: 0;
	font-size: 0.9rem;
	font-weight: 600;
	flex: 1;
}
/* The pane's #terminal-sftp-shell host fills the rest of the column. */
.terminal__sftp-shell {
	flex: 1;
	min-height: 0;
}

/* Toggle button in the terminal header that opens/hides the pane.
   Visually the same shape as the rest of the header pills so the
   header line reads as one row of equally-weighted controls. */
.terminal__sftp-toggle {
	font: inherit;
	font-size: 0.85rem;
	padding: 0.15rem 0.7rem;
	border-radius: 999px;
	border: 1px solid var(--card-border);
	background: var(--card-bg);
	color: inherit;
	cursor: pointer;
}
.terminal__sftp-toggle:hover,
.terminal__sftp-toggle:focus-visible {
	background: rgba(126, 87, 194, 0.18);
	border-color: #7e57c2;
	outline: none;
}
.terminal__sftp-toggle[aria-expanded="true"] {
	background: rgba(126, 87, 194, 0.18);
	border-color: #7e57c2;
}

.terminal__error {
	margin: 1rem;
}

/* Web SFTP page (sftp.php). Same layout idiom as the terminal page
   — full viewport, no topbar/foot — but the shell area hosts a file
   browser rather than a PTY, so no black background. Phase 9-10
   replace the placeholder with the actual browser UI. */
html.sftp-page {
	height: 100%;
}
body.sftp-page {
	margin: 0;
	min-height: 0;
	height: 100vh;
	display: flex;
	flex-direction: column;
	overflow: hidden;
}

.sftp__head {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 1rem;
	flex-wrap: wrap;
	padding: 0.5rem 1rem;
	background: var(--card-bg);
	border-bottom: 1px solid var(--card-border);
}

.sftp__title {
	margin: 0;
	font-size: 1.05rem;
	flex: 1;
	min-width: 0;
	display: flex;
	align-items: baseline;
	gap: 0.65rem;
	overflow: hidden;
}

.sftp__sid {
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 0.8rem;
	opacity: 0.55;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.sftp__mode {
	font-size: 0.7rem;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	padding: 0.1rem 0.5rem;
	border-radius: 999px;
	border: 1px solid currentColor;
}

.sftp__mode--root {
	color: #dc2626;
	background: rgba(220, 38, 38, 0.12);
}

.sftp__status {
	font-size: 0.85rem;
	padding: 0.15rem 0.6rem;
	border-radius: 999px;
	border: 1px solid var(--card-border);
	background: var(--card-bg);
}
.sftp__status[data-state="connecting"]   { color: #d97706; }
.sftp__status[data-state="connected"]    { color: #16a34a; }
.sftp__status[data-state="disconnected"] { opacity: 0.65; }
.sftp__status[data-state="error"]        { color: #dc2626; }

/* Clickable home pill — built dynamically by sftp.js when the
   `ready` frame lands. Sits between the title and the status pill.
   Visually matches .sftp__status (same pill shape) but interactive,
   with a hover state and pointer cursor. */
.sftp__home {
	font: inherit;
	font-size: 0.85rem;
	font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
	padding: 0.15rem 0.6rem;
	border-radius: 999px;
	border: 1px solid var(--card-border);
	background: var(--card-bg);
	color: inherit;
	cursor: pointer;
}
.sftp__home:hover,
.sftp__home:focus-visible {
	background: rgba(126, 87, 194, 0.18);
	border-color: #7e57c2;
	outline: none;
}

.sftp__shell {
	flex: 1;
	min-height: 0;
	width: 100%;
	overflow: auto;
	padding: 1rem;
	background: var(--page-bg, transparent);
}

/* When the browser DOM is built, drop the phase-8 placeholder
   padding so the breadcrumb sits flush and the list fills the
   shell. */
.sftp__shell--browser {
	padding: 0;
	overflow: hidden;
	display: flex;
	flex-direction: column;
}

.sftp__error {
	margin: 1rem;
}

/* File-browser container (phase 9). Owns its own scrolling so the
   toolbar stays pinned at the top and the status bar at the bottom. */
.sftp__browser {
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
}

.sftp__toolbar {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 0.75rem;
	padding: 0.5rem 1rem;
	background: var(--card-bg);
	border-bottom: 1px solid var(--card-border);
	overflow: hidden;
}

.sftp__breadcrumb {
	flex: 1;
	min-width: 0;
	display: flex;
	align-items: center;
	flex-wrap: nowrap;
	gap: 0.15rem;
	overflow-x: auto;
	overflow-y: hidden;
	font-size: 0.9rem;
}

.sftp__breadcrumb-segment {
	background: transparent;
	border: 0;
	color: inherit;
	font: inherit;
	padding: 0.15rem 0.45rem;
	border-radius: 0.25rem;
	cursor: pointer;
	white-space: nowrap;
}
.sftp__breadcrumb-segment:hover,
.sftp__breadcrumb-segment:focus-visible {
	background: var(--accent-bg, rgba(255, 255, 255, 0.08));
	outline: none;
}

.sftp__breadcrumb-separator {
	opacity: 0.4;
	user-select: none;
}

.sftp__toolbar-btn {
	flex-shrink: 0;
	background: transparent;
	border: 1px solid var(--card-border);
	color: inherit;
	font: inherit;
	padding: 0.25rem 0.75rem;
	border-radius: 0.4rem;
	cursor: pointer;
	font-size: 0.85rem;
	/* <a> elements styled as toolbar buttons need this to read as
	   buttons rather than underlined links. */
	text-decoration: none;
	display: inline-block;
}
.sftp__toolbar-btn:hover,
.sftp__toolbar-btn:focus-visible {
	background: var(--accent-bg, rgba(255, 255, 255, 0.08));
	outline: none;
}

/* List grid. CSS Grid with a 5-column template — same columns as
   the head row. Body is scrollable independently of head/footer. */
.sftp__list {
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
	font-size: 0.9rem;
}
.sftp__list:focus-visible {
	outline: none;
}

.sftp__list-head,
.sftp__list-row {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 6rem 11rem 5rem 5rem;
	align-items: center;
	gap: 0.5rem;
	padding: 0.35rem 1rem;
}

.sftp__list-head {
	flex-shrink: 0;
	font-size: 0.78rem;
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	background: var(--card-bg);
	border-bottom: 1px solid var(--card-border);
}

.sftp__list-head-cell {
	background: transparent;
	border: 0;
	color: inherit;
	font: inherit;
	text-align: left;
	padding: 0;
	cursor: pointer;
	letter-spacing: inherit;
	text-transform: inherit;
}
.sftp__list-head-cell[data-sort]::after {
	content: ' ▲';
	font-size: 0.7em;
	opacity: 0.7;
}
.sftp__list-head-cell[data-sort="desc"]::after {
	content: ' ▼';
}
/* Right-align the numeric columns' headers. */
.sftp__list-head-cell[data-col="size"],
.sftp__list-head-cell[data-col="mode"] {
	text-align: right;
}

.sftp__list-body {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
}

.sftp__list-row {
	cursor: pointer;
	border-bottom: 1px solid var(--card-border);
}
.sftp__list-row:hover {
	background: var(--accent-bg, rgba(255, 255, 255, 0.04));
}
.sftp__list-row[aria-selected="true"] {
	background: var(--accent-bg, rgba(126, 87, 194, 0.20));
	outline: 1px solid var(--card-border);
	outline-offset: -1px;
}
.sftp__list-row:focus-visible {
	outline: 2px solid var(--accent-bg, rgba(255, 255, 255, 0.3));
	outline-offset: -2px;
}

.sftp__cell {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.sftp__cell-name {
	display: flex;
	align-items: center;
	gap: 0.4rem;
}
.sftp__row-glyph {
	flex-shrink: 0;
	font-size: 0.95em;
	width: 1.2em;
	text-align: center;
	opacity: 0.8;
}
.sftp__row-name {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.sftp__row-warning {
	flex-shrink: 0;
	color: #d97706;
	font-size: 0.9em;
	cursor: help;
}

.sftp__cell-size,
.sftp__cell-mode {
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 0.85rem;
	text-align: right;
	opacity: 0.85;
}
.sftp__cell-mtime {
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 0.82rem;
	opacity: 0.75;
}
.sftp__cell-kind {
	font-size: 0.8rem;
	opacity: 0.7;
}

.sftp__list-empty {
	padding: 2rem 1rem;
	text-align: center;
	opacity: 0.55;
	font-style: italic;
}

.sftp__statusbar {
	flex-shrink: 0;
	padding: 0.35rem 1rem;
	background: var(--card-bg);
	border-top: 1px solid var(--card-border);
	font-size: 0.82rem;
	opacity: 0.75;
}

/* Hidden <input type=file> kept around for the Upload… button to
   .click() into. display:none is the only way to keep it out of
   the layout while still being clickable programmatically. */
.sftp__upload-input {
	display: none;
}

/* Transfer strip — sits between the list and the statusbar. Hidden
   (via the `hidden` HTML attribute) when no transfers are running. */
.sftp__transfer-strip {
	flex-shrink: 0;
	display: flex;
	flex-direction: column;
	gap: 0.25rem;
	padding: 0.5rem 1rem;
	background: var(--card-bg);
	border-top: 1px solid var(--card-border);
	max-height: 9rem;
	overflow-y: auto;
}

.sftp__transfer-row {
	display: grid;
	grid-template-columns: 1.2rem minmax(0, 1fr) 10rem 1fr 3rem 1.5rem;
	align-items: center;
	gap: 0.6rem;
	font-size: 0.85rem;
}
.sftp__transfer-row[data-state="ok"]        { opacity: 0.7; }
.sftp__transfer-row[data-state="cancelled"] { opacity: 0.5; }
.sftp__transfer-row[data-state="error"]     { color: #dc2626; }

.sftp__transfer-dir {
	text-align: center;
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	opacity: 0.7;
}

.sftp__transfer-name {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.sftp__transfer-bytes {
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 0.78rem;
	opacity: 0.75;
}

.sftp__transfer-bar {
	width: 100%;
	height: 0.5rem;
	/* Browsers render <progress> natively; the appearance reset
	   below normalises the dark-theme look. */
	-webkit-appearance: none;
	appearance: none;
	border: 1px solid var(--card-border);
	border-radius: 0.25rem;
	background: rgba(255, 255, 255, 0.05);
	overflow: hidden;
}
/* Vendor pseudo-elements style the inner bar + value fill. */
.sftp__transfer-bar::-webkit-progress-bar {
	background: rgba(255, 255, 255, 0.05);
}
.sftp__transfer-bar::-webkit-progress-value {
	background: #7e57c2;
	transition: width 100ms linear;
}
.sftp__transfer-bar::-moz-progress-bar {
	background: #7e57c2;
}

.sftp__transfer-pct {
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 0.78rem;
	text-align: right;
	opacity: 0.75;
}

.sftp__transfer-cancel {
	background: transparent;
	border: 1px solid var(--card-border);
	color: inherit;
	font: inherit;
	width: 1.6rem;
	height: 1.6rem;
	border-radius: 0.3rem;
	cursor: pointer;
	padding: 0;
	font-size: 0.8rem;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
.sftp__transfer-cancel:hover:not(:disabled) {
	background: rgba(220, 38, 38, 0.18);
	color: #f87171;
}
.sftp__transfer-cancel:disabled {
	opacity: 0.35;
	cursor: default;
}

/* Drag-drop overlay covering the browser pane. Hidden via the
   `hidden` HTML attribute when no drag is active. Positioned
   absolute over the shell so it doesn't reflow the layout. */
.sftp__shell {
	position: relative;
}
.sftp__drop-overlay {
	position: absolute;
	inset: 0;
	background: rgba(126, 87, 194, 0.18);
	border: 3px dashed #7e57c2;
	display: flex;
	align-items: center;
	justify-content: center;
	pointer-events: none;
	z-index: 5;
}
.sftp__drop-msg {
	background: var(--card-bg);
	padding: 1rem 1.5rem;
	border-radius: 0.5rem;
	font-size: 1rem;
	color: #b39ddb;
	border: 1px solid #7e57c2;
}

/* SFTP editor modal — full-viewport overlay hosting the lazy-loaded
   ACE editor. Built by sftp.js on first Edit click. Z-index sits
   above the drop overlay so an accidental drag during edit doesn't
   surface the drop hint underneath. */
.sftp__editor {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.65);
	z-index: 1000;
	display: flex;
	padding: 1.5rem;
	box-sizing: border-box;
}
.sftp__editor[hidden] {
	display: none;
}

.sftp__editor-window {
	flex: 1;
	display: flex;
	flex-direction: column;
	background: var(--card-bg);
	border: 1px solid var(--card-border);
	border-radius: 0.5rem;
	overflow: hidden;
	min-height: 0;
}

.sftp__editor-head {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 1rem;
	padding: 0.6rem 1rem;
	border-bottom: 1px solid var(--card-border);
}

.sftp__editor-path {
	flex: 1;
	min-width: 0;
	font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
	font-size: 0.9rem;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.sftp__editor-state {
	font-size: 0.85rem;
	opacity: 0.85;
}
.sftp__editor-state[data-state="loading"] { color: #d97706; }
.sftp__editor-state[data-state="saving"]  { color: #d97706; }
.sftp__editor-state[data-state="ready"]   { color: #16a34a; }
.sftp__editor-state[data-state="saved"]   { color: #16a34a; }
.sftp__editor-state[data-state="error"]   { color: #dc2626; }

.sftp__editor-banner {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 0.75rem;
	flex-wrap: wrap;
	padding: 0.55rem 1rem;
	background: rgba(220, 38, 38, 0.12);
	border-bottom: 1px solid rgba(220, 38, 38, 0.5);
	color: #fca5a5;
	font-size: 0.85rem;
}
.sftp__editor-banner[hidden] {
	display: none;
}
.sftp__editor-banner-msg {
	flex: 1;
}
.sftp__editor-banner-btn {
	background: transparent;
	border: 1px solid currentColor;
	color: inherit;
	font: inherit;
	font-size: 0.85rem;
	padding: 0.2rem 0.6rem;
	border-radius: 0.25rem;
	cursor: pointer;
}
.sftp__editor-banner-btn:hover,
.sftp__editor-banner-btn:focus-visible {
	background: rgba(255, 255, 255, 0.08);
	outline: none;
}

.sftp__editor-host {
	flex: 1;
	min-height: 0;
	overflow: hidden;
	/* Monokai-ish fallback so the area isn't blank during the brief
	   window between modal-show and ACE mount. */
	background: #272822;
}

.sftp__editor-foot {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 0.75rem;
	padding: 0.55rem 1rem;
	border-top: 1px solid var(--card-border);
}

.sftp__editor-info {
	flex: 1;
	font-size: 0.85rem;
	opacity: 0.7;
}

.sftp__editor-btn {
	background: transparent;
	border: 1px solid var(--card-border);
	color: inherit;
	font: inherit;
	padding: 0.3rem 0.9rem;
	border-radius: 0.3rem;
	cursor: pointer;
}
.sftp__editor-btn:hover:not(:disabled),
.sftp__editor-btn:focus-visible:not(:disabled) {
	background: rgba(255, 255, 255, 0.08);
	outline: none;
}
.sftp__editor-btn:disabled {
	opacity: 0.4;
	cursor: not-allowed;
}
.sftp__editor-btn--save {
	background: rgba(126, 87, 194, 0.18);
	border-color: #7e57c2;
}
.sftp__editor-btn--save:hover:not(:disabled),
.sftp__editor-btn--save:focus-visible:not(:disabled) {
	background: rgba(126, 87, 194, 0.32);
}

/* Right-click context menu. position:fixed so the JS-set
   left/top coords are in viewport space, with the dismiss
   handlers re-hiding it on outside click / Escape / scroll / blur. */
.sftp__menu {
	position: fixed;
	z-index: 1100;
	min-width: 11rem;
	background: var(--card-bg);
	border: 1px solid var(--card-border);
	border-radius: 0.4rem;
	box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
	padding: 0.25rem 0;
	font-size: 0.9rem;
}
.sftp__menu[hidden] {
	display: none;
}

.sftp__menu-items {
	display: flex;
	flex-direction: column;
}

.sftp__menu-item {
	background: transparent;
	border: 0;
	color: inherit;
	font: inherit;
	text-align: left;
	padding: 0.4rem 0.9rem;
	cursor: pointer;
}
.sftp__menu-item:hover:not(:disabled),
.sftp__menu-item:focus-visible:not(:disabled) {
	background: rgba(255, 255, 255, 0.08);
	outline: none;
}
.sftp__menu-item:disabled {
	opacity: 0.4;
	cursor: not-allowed;
}
.sftp__menu-item--danger {
	color: #fca5a5;
}
.sftp__menu-item--danger:hover:not(:disabled),
.sftp__menu-item--danger:focus-visible:not(:disabled) {
	background: rgba(220, 38, 38, 0.15);
}

.sftp__menu-sep {
	margin: 0.25rem 0;
	border-top: 1px solid var(--card-border);
	opacity: 0.7;
}

/* Tiny scoped helpers for the rename + perms dialogs — everything
   else they need (.modal, .modal--narrow, .modal__form / __head /
   __title / __close / __foot, .field, .field__input, .btn variants,
   .card__error) is reused from the existing modal primitives below. */
.sftp__dialog-path {
	margin: 0;
	font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
	font-size: 0.85rem;
	word-break: break-all;
	opacity: 0.7;
}
.sftp__dialog-info {
	margin: 0;
	font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
	font-size: 0.85rem;
	opacity: 0.75;
}

/* Delete-confirm body — tighter than the .modal__form gap (0.85rem)
   so the path / message / hint read as one block. */
.sftp__delete-body {
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
}
.sftp__delete-msg {
	margin: 0;
}

/* Native <dialog> modal. */
.modal[open] {
	width: min(40rem, calc(100vw - 2rem));
	max-height: calc(100vh - 4rem);
	border: 1px solid transparent;
	border-radius: 0.75rem;
	padding: 0;
	overflow: auto;
}

/* Narrow modal variant for confirm prompts. */
.modal--narrow[open] {
	width: min(26rem, calc(100vw - 2rem));
}

/* Search prefix help modal contents. */
.search-help__intro {
	margin: 0;
	font-size: 0.9rem;
	opacity: 0.85;
}

.search-help__list {
	margin: 0;
	padding-left: 1.5rem;
	display: grid;
	grid-template-columns: max-content 1fr;
	column-gap: 1.25rem;
	row-gap: 0.5rem;
	align-items: center;
}

.search-help__list dt {
	white-space: nowrap;
}

.search-help__list dd {
	margin: 0;
	font-size: 0.95rem;
}

.search-help__examples {
	margin: 0;
	padding-left: 2.75rem;
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
	font-size: 0.95rem;
}

.modal::backdrop {
	background: rgba(0, 0, 0, 0.55);
}

.modal__form {
	display: flex;
	flex-direction: column;
	gap: 0.85rem;
	padding: 1.25rem 1.5rem 1.5rem;
	margin: 0;
}

.modal__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
}

.modal__title {
	margin: 0;
	font-size: 1.25rem;
}

.modal__close {
	font: inherit;
	font-size: 1.5rem;
	background: none;
	border: 0;
	cursor: pointer;
	padding: 0 0.25rem;
	line-height: 1;
	color: inherit;
	opacity: 0.7;
}

.modal__close:hover,
.modal__close:focus {
	opacity: 1;
}

.modal__grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0.75rem;
}

.modal__foot {
	display: flex;
	gap: 0.5rem;
	margin-top: 0.5rem;
	justify-content: center;
}

@media (max-width: 540px) {
	.modal__grid {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* Surviving data-table primitives (the full <table> chrome was replaced
   by the grid-list family above; only these three classes are still used). */
.data-table__row {
	cursor: pointer;
	transition: background-color 80ms ease;
}
.data-table__row:focus {
	outline: none;
}
.data-table__mono {
	font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
	font-size: 0.9rem;
}

.link-action {
	font-size: 0.85rem;
	text-decoration: none;
	margin-left: 0.6rem;
}

.link-action:hover {
	text-decoration: underline;
}

/* Disabled-looking but still clickable so JS can explain why it's unavailable. */
.link-action--disabled {
	opacity: 0.4;
	cursor: not-allowed;
}

.link-action--disabled:hover {
	text-decoration: none;
}

/* Disabled-looking button. Keeps pointer-events so JS can intercept the click. */
.btn.is-disabled {
	opacity: 0.4;
	cursor: not-allowed;
}

.muted {
	opacity: 0.55;
}

/* Status badges */
.badge {
	display: inline-block;
	font-size: 0.75rem;
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	padding: 0.15rem 0.55rem;
	border-radius: 999px;
	border: 1px solid transparent;
}

/* Empty state for stub pages */
.empty {
	padding: 2rem 1rem;
	text-align: center;
	border-radius: 0.6rem;
	font-size: 0.95rem;
	opacity: 0.85;
}

/* Inline card error/success banner; pre-line preserves \n without HTML. */
.card__error,
.card__success {
	margin: 0;
	font-size: 0.85rem;
	padding: 0.5rem 0.65rem;
	border-radius: 0.4rem;
	text-align: center;
	white-space: pre-line;
}

/* Honeypot: offscreen (NOT display:none) so bots still fill it; width:1px
   so an autofill dropdown can't paint over real fields. */
.hp {
	position: absolute;
	left: -10000px;
	top: auto;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

/* ============================================================
   server_transfer.php — page-specific layout. Section chrome is
   inherited from .settings__section (the <details> on each block
   carries both classes); form layout reuses .list__search
   (extended to align-items: end in the union below) + .field
   primitives. Tag colours for the state enum live with the other
   .tag--state-* rules above. Only genuinely new visuals
   (progress bar, split-pane) get their own rules.
   ============================================================ */

/* Override .settings__section > :not(summary)'s 1.5rem indent —
   the page's dense forms / split-pane look cramped on either side
   with that much side padding. 1rem matches the visual cadence of
   the section title's arrow + gap offset. */
.server-transfer__section > :not(summary) {
	margin-left: 1rem;
	margin-right: 1rem;
}
/* Breathing room between the section title / error pill / form /
   browsers etc. .card__error sets margin: 0 so consecutive blocks
   would touch without this rule. */
.server-transfer__section > :not(summary) + :not(summary) {
	margin-top: 0.75rem;
}

/* Tighter top strip: header → notice → hint stack at the top of the
   page sits inside .list which has gap: 1rem between flex children.
   That's too loose for the page's "title bar with status" feel —
   shrink the gap on these three items via negative-margin offsets.
   Last-of-kind notice/hint pair keeps the natural 1rem before the
   first <details>. */
.server-transfer__notice,
.server-transfer__hint {
	margin: 0;
}
.list__head + .server-transfer__notice,
.list__head + .server-transfer__hint {
	margin-top: -0.5rem;
}
.server-transfer__notice + .server-transfer__notice,
.server-transfer__notice + .server-transfer__hint {
	margin-top: -0.5rem;
}

/* End-align the bare buttons next to the .field stacks so they
   sit on the input baseline, not stretched to label-height. Same
   override .sftp-audit__filters needs; joined into one union. */
.sftp-audit__filters,
.server-transfer__form {
	align-items: end;
	flex-wrap: wrap;
}
.sftp-audit__filters .sftp-audit__filter-field,
.server-transfer__form .server-transfer__field {
	flex: 1;
	min-width: 0;
}

/* Match users.php / servers.php — Actions cell right-aligns its
   links; font-size:0 collapses inter-tag whitespace that would
   otherwise push the right-edge ~4px left. .link-action restores
   its own font-size on each link. */
.server-transfer__cell-actions {
	text-align: right;
	white-space: nowrap;
	font-size: 0;
}

/* Manual-refresh button in the In-flight summary. Pushed to the
   right edge of the flex row; smaller padding so it doesn't
   crowd the section title. */
.server-transfer__refresh {
	margin-left: auto;
	padding: 0.2rem 0.6rem;
	font-size: 0.8rem;
	font-weight: 500;
}

/* Inline progress bar on in-flight rows. No equivalent primitive in
   the codebase (the workerbee dashboard uses tag badges, not bars). */
.server-transfer__progress {
	height: 6px;
	background-color: rgba(127, 127, 127, 0.18);
	border-radius: 3px;
	overflow: hidden;
	margin-bottom: 0.2rem;
}
.server-transfer__progress-bar {
	height: 100%;
	background-color: rgba(37, 99, 235, 0.85);
	transition: width 200ms ease;
}
.server-transfer__progress-text {
	font-size: 0.8rem;
}

/* Stacked SFTP panes (source above destination). Side-by-side was
   tried first but the 5-column file listing (NAME/SIZE/MODIFIED/MODE/
   KIND) crowds badly at half-card width — even on a 1080p display the
   columns visibly squeeze. Stacking gives each pane the full card
   width and the file table breathes. The trade-off is more vertical
   scroll, mitigated by capping each pane at 50vh. */
.server-transfer__split {
	display: grid;
	grid-template-columns: 1fr;
	gap: 1rem;
	margin-bottom: 1rem;
}
.server-transfer__pane {
	display: flex;
	flex-direction: column;
	min-height: 50vh;
	border: 1px solid var(--card-border);
	border-radius: 0.5rem;
	overflow: hidden;
}
.server-transfer__pane-head {
	padding: 0.4rem 0.6rem;
	background-color: rgba(127, 127, 127, 0.08);
	border-bottom: 1px solid var(--card-border);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.5rem;
}
/* Right-edge group for the status pill + home chip (sftp.js inserts
   the home chip via statusEl.after() so anchoring this wrapper at
   the right keeps them together rather than the status pill landing
   in the centre via space-between with three children). */
.server-transfer__pane-head-right {
	display: flex;
	align-items: center;
	gap: 0.4rem;
}
.server-transfer__pane-title {
	margin: 0;
	font-size: 0.95rem;
	font-weight: 600;
}
.server-transfer__sftp-shell {
	flex: 1;
	min-height: 0;
}


