/*
 * GENERATED by `rummage styles` — do not edit by hand.
 * One file per primitive/builtin owns its rules; this concatenates them in
 * cascade order. Edit the co-located <name>.css files and re-run the generator.
 */


/* ╭─ source: styles/tokens.css ─╮ */
/*
 * @cldmv/rummage — theme tokens
 *
 * Dark theme with green accent. Tokens are the public theming surface — apps
 * can override any of these in a stylesheet loaded after this one to reskin
 * without touching the host stylesheets.
 *
 * Reference baseline: pdf-tools rebuild/ai-processor:src/electron/renderer/styles.css.
 */

:root {
	/* Backgrounds (sourced from cldmv-color-theme.json) */
	--bg-primary: #1e1e1e; /* statusBar.background */
	--bg-secondary: #252526; /* notifications.background, editorWidget.background, menu.background */
	--bg-tertiary: #2d2d2d; /* tab.inactiveBackground */
	--bg-hover: #2a2d2e; /* list.hoverBackground */
	--bg-active: rgba(152, 195, 121, 0.5); /* selection — primary green at 50% */

	/* Selection / active state — change once, retheme everywhere */
	--selection-bg: var(--bg-hover);
	--selection-accent-bg: var(--accent-green);
	/* Dark text on the full primary-green hover so menubar / context-menu
	   rows remain readable. List/tree selection backgrounds use the muted
	   rgba versions below where --tree-selection-text (light) is fine. */
	--selection-accent-text: var(--bg-primary);
	--tree-selection-bg: rgba(152, 195, 121, 0.25); /* primary green at 25% */
	--tree-selection-text: var(--text-primary);

	/* Text (cldmv theme) */
	--text-primary: #cccccc; /* foreground */
	--text-secondary: #bbbbbb; /* sideBarTitle.foreground */
	--text-muted: #808080;
	--text-disabled: #585858; /* tree.indentGuidesStroke */

	/* Lines (cldmv theme — slightly more visible than the pdf-tools defaults) */
	--border-color: #454545; /* editorSuggestWidget.border */
	--divider-color: #303031; /* notifications.border */
	--panel-border: rgba(128, 128, 128, 0.35); /* panel.border #80808059 */

	/* Accent — two greens for two purposes.
	   PRIMARY  (`--accent-green`)   = large surfaces: buttons, success fills,
	                                   progress bars, focus borders, active tab
	                                   underline, large-hover backgrounds.
	   SMALL    (`--accent-small`)   = small accent moments: checkbox / toggle
	                                   checked states, multi-select chips/check
	                                   marks, slider thumb, small badges, small
	                                   icons. Keeps tiny accents distinct from
	                                   the dominant brand-green moments. */
	--accent-green: #98c379; /* primary brand green */
	--accent-green-hover: #aad08e; /* slightly brighter for :hover */
	--accent-green-dark: #7ea65f; /* darker step, for borders / dark-text-on-green */
	--accent-small: #97ca61; /* cldmv theme focusBorder — small element green */
	--accent-small-hover: #98c06c; /* button.hoverBackground from cldmv theme */

	/* Buttons specifically — user wants #98c06c, which is the cldmv theme
	   button.hoverBackground. Separate from --accent-green so we can theme
	   buttons independently of focus borders / tabs / progress fills. */
	--btn-primary-bg: #98c06c;
	/* Darker on hover (was #a8d089 — user said it was too light). Mirrors
	   the usual filled-button convention where hover goes darker for grip. */
	--btn-primary-bg-hover: #7ea65f;

	/* Semantic */
	--danger-red: #f44336; /* pdf-tools reference red */
	--danger-red-hover: #f55a4e;
	--success-green: var(--accent-green);
	--warning-yellow: #cca700; /* editorWarning.foreground / notificationsWarningIcon */

	/* Depth */
	--shadow: rgba(0, 0, 0, 0.36);
	--shadow-heavy: rgba(0, 0, 0, 0.5);

	/* Typography */
	--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
	--font-family-mono: "Consolas", "Monaco", "Courier New", monospace;

	/* Sizing */
	--titlebar-height: 40px;
	--bottom-bar-height: 28px;
	--sidebar-width: 280px;
	--right-panel-width: 350px;
	--radius-sm: 4px;
	--radius-md: 6px;
	--radius-lg: 8px;
}

html,
body {
	margin: 0;
	padding: 0;
	/* dvh, not %: iOS standalone boots in a short layout viewport and expands it at the
	 * load event; a percentage height keeps whatever viewport it last resolved against
	 * (measured stuck 59pt short after boot), while dvh re-resolves with the flip. The
	 * plain % stays as the fallback for engines without dvh. */
	height: 100%;
	height: 100dvh;
	background-color: var(--bg-primary);
	color: var(--text-primary);
	font-family: var(--font-family);
	font-size: 13px;
	line-height: 1.5;
	-webkit-font-smoothing: antialiased;
	user-select: none;
	/* iOS Safari "text autosizing" inflates fonts in text-heavy blocks (the chat
	 * view especially) whenever it re-evaluates layout — sizes are design-fixed
	 * here, so opt out entirely. */
	-webkit-text-size-adjust: 100%;
	text-size-adjust: 100%;
}

* {
	box-sizing: border-box;
}


/* ╭─ source: styles/base.css ─╮ */
/**
 *	@Project: @cldmv/rummage
 *	@Filename: /src/styles/base.css
 *	@Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
 */

/*
 * base.css — the shared base layer: reset, bare elements (body, a, input,
 * select, scrollbars) and the cross-unit `.btn*` / form classes consumed by
 * many built-in extensions. Loads right after tokens.css; unit-owned rules
 * live co-located at src/api/{ui,extensions,shell}/<name>/<name>.css.
 * (Originally ported from pdf-tools' renderer styles via components.css.)
 */

* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}

::-webkit-scrollbar {
	width: 8px;
	height: 8px;
}

::-webkit-scrollbar-track {
	background: var(--bg-secondary);
}

::-webkit-scrollbar-thumb {
	background: var(--bg-tertiary);
	border-radius: 4px;
	transition: background-color 80ms ease;
}

::-webkit-scrollbar-thumb:hover {
	background: var(--accent-green);
}

::-webkit-scrollbar-thumb:active {
	background: var(--accent-green-dark, var(--accent-green));
}

::-webkit-scrollbar-corner {
	background: var(--bg-secondary);
}

body {
	font-family: var(--font-family);
	font-size: 14px;
	color: var(--text-primary);
	background-color: var(--bg-primary);
	overflow: hidden;
	/* Selection defaults to ALLOWED — paths, log lines, error messages, code
	 * blocks, table cells etc. are content the user wants to copy. Chrome
	 * elements (titlebar, tabs, icon bars, menus, buttons, badges) opt OUT
	 * via the .chrome-no-select rule below + their own per-component rules.
	 * The previous default was user-select:none which forced every content
	 * surface to opt back in — backwards from what users expect. */
	user-select: text;
	-webkit-user-select: text;
	-webkit-app-region: no-drag;
	border-radius: 0px; /* Your desired roundness */
	overflow: hidden; /* Clips content to the rounded corners */
}

/* Global anchor styling — the browser default is bright blue + underline which
 * clashes with every theme. Tie to the accent token instead. Any <a> in the
 * renderer should also use the host's shell.openExternal bridge (or just rely
 * on the createAppWindow setWindowOpenHandler that routes target=_blank /
 * window.open through shell.openExternal automatically). */

a {
	color: var(--accent-green);
	text-decoration: none;
	cursor: pointer;
}

a:hover {
	text-decoration: underline;
}

a:focus-visible {
	outline: 1px dotted var(--accent-green);
	outline-offset: 2px;
}

a:visited {
	color: var(--accent-green);
}

/* ── Buttons ────────────────────────────────────────────────────────────── */

.btn {
	padding: 8px 16px;
	border: none;
	border-radius: 4px;
	font-size: 13px;
	font-weight: 500;
	cursor: pointer;
	transition: all 0.15s ease;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	white-space: nowrap;
	font-family: inherit;
}

.btn:hover {
	transform: translateY(-1px);
	box-shadow: 0 2px 8px var(--shadow);
}

.btn:active {
	transform: translateY(0);
}

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

.btn-primary {
	background-color: var(--btn-primary-bg);
	color: var(--bg-primary);
}

.btn-primary:hover {
	background-color: var(--btn-primary-bg-hover);
}

.btn-secondary {
	background-color: var(--bg-tertiary);
	color: var(--text-primary);
	border: 1px solid var(--border-color);
}

/* Default gray buttons promote to the green/primary button's RESTING look on
 * hover — flat green fill, no lift or shadow. (For a button that must stay gray
 * on hover, use .btn-neutral below instead.) */

.btn-secondary:hover {
	background-color: var(--btn-primary-bg);
	color: var(--bg-primary);
	border-color: var(--btn-primary-bg);
	transform: none;
	box-shadow: none;
}

.btn-secondary:active {
	transform: none;
}

/* Opt-in gray-on-gray button: no green promotion on hover. Not the default. */

.btn-neutral {
	background-color: var(--bg-tertiary);
	color: var(--text-primary);
	border: 1px solid var(--border-color);
}

.btn-neutral:hover {
	background-color: var(--bg-hover);
	color: var(--text-primary);
	border-color: var(--border-color);
}

.btn-success {
	background-color: var(--btn-primary-bg);
	color: var(--bg-primary);
}

.btn-success:hover {
	background-color: var(--btn-primary-bg-hover);
}

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

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

.btn-warning {
	background-color: #e6a020;
	color: #1a1a1a;
}

.btn-warning:hover {
	background-color: #f0b030;
}

.btn-large {
	padding: 12px 24px;
	font-size: 15px;
}

.btn-small,
.btn-sm {
	padding: 4px 10px;
	font-size: 11px;
	border-radius: 4px;
	border: 1px solid var(--border-color);
	background-color: var(--bg-tertiary);
	color: var(--text-primary);
	cursor: pointer;
	transition: all 0.15s ease;
}

.btn-small:hover,
.btn-sm:hover {
	background-color: var(--btn-primary-bg);
	color: var(--bg-primary);
	border-color: var(--btn-primary-bg);
	transform: none;
	box-shadow: none;
}

.btn-small:active,
.btn-sm:active {
	transform: none;
}

.btn-icon {
	width: 28px;
	height: 28px;
	padding: 0;
	border: none;
	background-color: transparent;
	color: var(--text-secondary);
	font-size: 16px;
	cursor: pointer;
	border-radius: 4px;
	display: flex;
	align-items: center;
	justify-content: center;
	transition: all 0.15s ease;
}

.btn-icon:hover {
	background-color: var(--bg-hover);
	color: var(--text-primary);
}

.btn-icon--warning {
	color: #e6a020;
}

.btn-icon--warning:hover {
	background-color: rgba(230, 160, 32, 0.15);
	color: #f0b030;
}

.icon {
	font-size: 16px;
	line-height: 1;
}

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

.form-group {
	margin-bottom: 20px;
}

.form-group label {
	display: block;
	margin-bottom: 8px;
	font-size: 13px;
	font-weight: 500;
	color: var(--text-primary);
}

.form-group input[type="text"],
.form-group input[type="number"],
.form-group input[type="password"],
.form-group input[type="email"],
.form-group textarea,
.form-group select {
	width: 100%;
	padding: 10px 12px;
	background-color: var(--bg-primary);
	border: 1px solid var(--border-color);
	border-radius: 4px;
	color: var(--text-primary);
	font-size: 13px;
	font-family: inherit;
	transition: border-color 0.15s ease;
}

.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
	outline: none;
	border-color: var(--accent-green);
}

/* Hover on text-like inputs + selects + textareas — matches the focus
   color so the affordance stays the same once you click in.
   Skip read-only inputs (they're not interactive). */

.form-group input:not(:focus):not(:read-only):hover,
.form-group select:not(:focus):hover,
.form-group textarea:not(:focus):hover {
	border-color: var(--accent-green);
}

.form-group input[type="text"]:read-only,
.form-group input[type="number"]:read-only {
	background-color: var(--bg-primary);
	cursor: default;
}

.file-input-group {
	display: flex;
	gap: 8px;
}

.file-input-group input {
	flex: 1;
}

/* Browse buttons inherit the default .btn-secondary green-promotion hover —
   no special-casing needed, so they stay uniform with every other default
   button. */

.form-row {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 16px;
}

.form-hint {
	font-size: 11px;
	color: var(--text-secondary);
	margin-top: 4px;
	margin-bottom: 0;
}

.checkbox-label {
	display: flex;
	align-items: center;
	cursor: pointer;
	padding: 8px 0;
}

.checkbox-label input[type="checkbox"] {
	width: 18px;
	height: 18px;
	margin-right: 10px;
	cursor: pointer;
	accent-color: var(--accent-green);
}

.checkbox-label span {
	font-size: 13px;
	color: var(--text-primary);
}

/* Small inline form control (sm size) — selects, inputs */

.form-control-sm {
	background: var(--bg-primary);
	border: 1px solid var(--border-color);
	border-radius: 4px;
	color: var(--text-primary);
	font-size: 12px;
	height: 26px;
	padding: 0 6px;
	max-width: 200px;
}

.form-control-sm:focus {
	outline: 2px solid var(--accent-green);
	outline-offset: 1px;
}

.form-control-sm:disabled {
	opacity: 0.45;
	cursor: not-allowed;
}

/* Note: appearance:none on <select> hides the selected option text in some
   Electron builds; keep the native chevron and just theme via background +
   color so the user always sees the current value. */

select.form-control-sm {
	cursor: pointer;
}

/* ── Select / option / number input native styling fixes ───────────────── */

select option {
	background-color: var(--bg-primary);
	color: var(--text-primary);
}

/* Hide native number spin buttons; we provide our own */

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
	-webkit-appearance: none;
	margin: 0;
}

input[type="number"] {
	-moz-appearance: textfield;
}

/* ── Range slider input ─────────────────────────────────────────────────── */

input[type="range"] {
	-webkit-appearance: none;
	appearance: none;
	width: 100%;
	height: 24px;
	background: transparent;
	cursor: pointer;
	padding: 0;
}

input[type="range"]:focus {
	outline: none;
}

/* Track — uses a linear-gradient so the green fill follows the thumb.
   Demo wiring sets the --fill custom property (0-100) on input. */

input[type="range"] {
	--fill: 50;
}

input[type="range"]::-webkit-slider-runnable-track {
	width: 100%;
	height: 6px;
	background: linear-gradient(
		to right,
		var(--accent-small) 0%,
		var(--accent-small) calc(var(--fill) * 1%),
		var(--bg-primary) calc(var(--fill) * 1%),
		var(--bg-primary) 100%
	);
	border: 1px solid var(--border-color);
	border-radius: 3px;
}

input[type="range"]::-moz-range-progress {
	height: 6px;
	background: var(--accent-small);
	border-radius: 3px;
}

input[type="range"]::-moz-range-track {
	width: 100%;
	height: 6px;
	background: var(--bg-primary);
	border: 1px solid var(--border-color);
	border-radius: 3px;
}

/* Thumb — uses --accent-small (small interactive element) */

input[type="range"]::-webkit-slider-thumb {
	-webkit-appearance: none;
	appearance: none;
	width: 14px;
	height: 14px;
	margin-top: -5px;
	background: var(--accent-small);
	border: 2px solid var(--bg-primary);
	border-radius: 50%;
	box-shadow: 0 0 0 1px var(--border-color);
	cursor: pointer;
	transition:
		background-color 0.15s ease,
		transform 0.1s ease;
}

input[type="range"]:hover::-webkit-slider-thumb {
	background: var(--accent-small-hover);
}

input[type="range"]:active::-webkit-slider-thumb,
input[type="range"]:focus::-webkit-slider-thumb {
	transform: scale(1.15);
}

input[type="range"]::-moz-range-thumb {
	width: 12px;
	height: 12px;
	background: var(--accent-small);
	border: 2px solid var(--bg-primary);
	border-radius: 50%;
	cursor: pointer;
}

input[type="range"]:disabled {
	opacity: 0.5;
	cursor: not-allowed;
}

body.host-busy {
	cursor: wait;
}

body.host-busy .host-busy-overlay {
	display: flex;
}

/* ── Utilities ──────────────────────────────────────────────────────────── */

.hidden {
	display: none !important;
}

.full-width {
	width: 100%;
}


/* ╭─ source: api/extensions/accordion/accordion.css ─╮ */
/*
 * accordion extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── Accordion (extension) — stacked collapsible/resizable panes ─────────── */
.accordion {
	display: flex;
	flex-direction: column;
	height: 100%;
	min-height: 0;
	overflow: hidden;
}
.accordion-pane {
	position: relative;
	display: flex;
	flex-direction: column;
	min-height: 0;
	overflow: hidden;
	border-bottom: 1px solid var(--border-color);
}
.accordion-pane:last-child {
	border-bottom: none;
}
.accordion-pane__header {
	display: flex;
	align-items: center;
	gap: 6px;
	width: 100%;
	flex: 0 0 auto;
	/* 8px left padding lines the pane chevron up with the tree chevrons inside. */
	padding: 0 8px;
	font-size: 11px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.4px;
	color: var(--text-secondary);
	background: var(--bg-primary);
	border: none;
	text-align: left;
	cursor: pointer;
	font-family: inherit;
	user-select: none;
}
.accordion-pane__header:hover {
	color: var(--text-primary);
}
.accordion-pane__caret {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 16px;
	height: 16px;
	flex: 0 0 16px;
	color: var(--text-secondary);
}
.accordion-pane__caret--right {
	margin-left: auto;
}
.accordion-pane__caret svg {
	width: 16px;
	height: 16px;
}
.accordion-pane__title {
	flex: 1 1 auto;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
/* Pane-header action buttons — revealed on header hover, pinned to the right.
   Populated via the pane render's setActions(). */
.accordion-pane__actions {
	display: inline-flex;
	align-items: center;
	flex: 0 0 auto;
	margin-left: 6px;
	opacity: 0;
	transition: opacity 120ms ease;
}
.accordion-pane__header:hover .accordion-pane__actions,
.accordion-pane__header:focus-within .accordion-pane__actions {
	opacity: 1;
}
.accordion-pane__action svg {
	width: 14px;
	height: 14px;
}
.accordion-pane__body {
	flex: 1 1 auto;
	min-height: 0;
	overflow: auto; /* each open pane owns its own scroll */
}
.accordion-pane--collapsed .accordion-pane__body {
	display: none;
}
/* Sash — the drag handle between two adjacent open panes. Sits across the top
   edge of the lower pane. */
.accordion-sash {
	position: absolute;
	top: -3px;
	left: 0;
	right: 0;
	height: 6px;
	z-index: 4;
	cursor: row-resize;
	background: transparent;
	transition: background-color 100ms ease;
}
.accordion-sash:hover,
.accordion-sash.dragging {
	background: var(--accent-green);
}
.accordion--resizing,
.accordion--resizing * {
	user-select: none !important;
}
/* Reorder UX — the pane being dragged dims; the drop target paints a 2px
 * accent line on the edge corresponding to where the drop would land
 * (above/below the target pane). Mirrors the simpleAccordion primitive. */
.accordion-pane--dragging {
	opacity: 0.55;
}
.accordion-pane--drop-above {
	box-shadow: inset 0 2px 0 0 var(--accent-green);
}
.accordion-pane--drop-below {
	box-shadow: inset 0 -2px 0 0 var(--accent-green);
}
/* Tab-mode header (allowMultiPerPane) — the header itself is the tab strip,
   with the collapse chevron pinned to the right. Used for the right panel. */
.accordion-pane__header--tabs {
	padding: 0 6px 0 0;
	text-transform: none;
	letter-spacing: normal;
	font-weight: normal;
}
.accordion-pane__tabs {
	display: flex;
	flex: 1 1 auto;
	min-width: 0;
	overflow-x: auto;
	scrollbar-width: thin;
	/* Stretch to the header row's full height — tab buttons inside use
	 * `height: 100%` to fill that height. Without this stretch the header
	 * `align-items: center` would size the strip to content height and the
	 * tabs would shrink-fit too; clicking the empty space above/below the
	 * tab text would hit the header's click listener and toggle-collapse
	 * the whole section. Confirmed UX bug: first click on a non-active
	 * tab collapsed the section instead of activating the tab. */
	align-self: stretch;
}
.accordion-pane__tab {
	padding: 0 12px;
	height: 100%;
	background: transparent;
	border: none;
	border-bottom: 2px solid transparent;
	color: var(--text-secondary);
	font-size: 11px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.4px;
	cursor: pointer;
	white-space: nowrap;
}
.accordion-pane__tab:hover {
	color: var(--text-primary);
}
.accordion-pane__tab.active {
	color: var(--accent-green);
	border-bottom-color: var(--accent-green);
}


/* ╭─ source: api/extensions/bottom-bar/bottom-bar.css ─╮ */
/*
 * bottom-bar extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── App bottom bar — IDE-style status strip ────────────────────────────── */

.app-bottom-bar {
	flex-shrink: 0;
	height: var(--bottom-bar-height);
	display: flex;
	align-items: center;
	justify-content: space-between;
	background-color: var(--bg-secondary);
	border-top: 1px solid var(--border-color);
	padding: 0 8px;
	z-index: 1;
	font-size: 11px;
	color: var(--text-secondary);
}

.app-bottom-bar__left,
.app-bottom-bar__right {
	display: flex;
	align-items: center;
	gap: 8px;
	height: 100%;
	/* Edge zones hold fixed-size items (ext-count, bell, widgets) — never let them shrink or
	   wrap. The middle segment row (flex: 1, overflow: hidden) is the only elastic zone, so a
	   crowded bar clips segments there instead of wrapping the counter/bell onto a second line. */
	flex-shrink: 0;
	white-space: nowrap;
}

.app-bottom-bar__right {
	margin-left: auto;
}

/* Clickable items inside the bottom bar (status counters, action chips) */

.bottom-bar-btn {
	background: none;
	border: none;
	color: var(--text-secondary);
	font-size: 11px;
	cursor: pointer;
	height: 100%;
	padding: 0 8px;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-family: inherit;
	white-space: nowrap; /* the ext-count label stays on one line — never wraps the bar taller */
	flex-shrink: 0;
}

.bottom-bar-btn:hover {
	color: var(--text-primary);
	background-color: var(--bg-hover);
}

.bottom-bar-icon {
	font-size: 12px;
	line-height: 1;
}

/* Bottom-bar central segment row — pushed in by other extensions via
 * `bottom-bar.setSegment(id, text, {...})`. */

.app-bottom-bar__middle {
	flex: 1 1 auto;
	display: flex;
	align-items: center;
	gap: 12px;
	min-width: 0;
	overflow: hidden;
	padding: 0 12px;
}

.bottom-bar-segment {
	background: transparent;
	border: none;
	color: var(--text-secondary);
	font-size: 11px;
	font-family: inherit;
	cursor: default;
	padding: 0 4px;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 360px;
	animation: bottom-bar-segment-in 120ms ease;
}

.bottom-bar-segment:not([disabled]) {
	cursor: pointer;
}

.bottom-bar-segment:not([disabled]):hover {
	color: var(--text-primary);
}

/* Touch devices (phone / tablet): a 28px, 11px status strip is far too small to tap. On a coarse
 * pointer, grow the bar and its items into comfortable tap targets. Coarse-pointer only — a desktop
 * mouse keeps the compact strip. */
@media (pointer: coarse) {
	:root {
		--bottom-bar-height: 40px;
	}
	.app-bottom-bar {
		font-size: 13px;
	}
	.bottom-bar-btn {
		font-size: 14px;
		padding: 0 12px;
		gap: 8px;
	}
	.bottom-bar-icon {
		font-size: 20px;
	}
	.bottom-bar-segment {
		font-size: 13px;
	}
}


/* ╭─ source: api/extensions/context-menu/context-menu.css ─╮ */
/*
 * context-menu extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── Context menu (used by ContextMenu lib + menubar dropdowns) ─────────── */

.context-menu {
	position: fixed;
	z-index: 9999;
	min-width: 160px;
	padding: 4px 0;
	background-color: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 6px;
	box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
	user-select: none;
}

.context-menu--square {
	border-radius: 0;
}

.context-menu-item {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 7px 14px;
	font-size: 13px;
	color: var(--text-primary);
	cursor: pointer;
	white-space: nowrap;
}

.context-menu-item:hover {
	background-color: var(--selection-accent-bg);
	color: var(--selection-accent-text);
}

.context-menu-item.disabled {
	color: var(--text-secondary);
	pointer-events: none;
	opacity: 0.5;
}

.context-menu-icon {
	width: 16px;
	text-align: center;
	flex-shrink: 0;
	font-size: 12px;
	opacity: 0.75;
}

.context-menu-text {
	flex: 1;
}

.context-menu-separator {
	margin: 4px 0;
	border: none;
	border-top: 1px solid var(--border-color);
}


/* ╭─ source: api/extensions/conversation/conversation.css ─╮ */
/*
 * conversation() — full-screen chat view. rummage owns the layout; the app themes via
 * tokens (fallbacks keep it usable without a theme). While a conversation is open the
 * shell is hidden and the document itself is LOCKED (no page scroll) — a fixed flex
 * column takes over: header on top, a CONTAINED scrolling transcript in the middle (its
 * own scroll indicator, like the native app), a composer on the bottom. The shell is
 * sized to the visual viewport in JS so the soft keyboard just shrinks the middle.
 */
/* The document is kept MINUTELY scrollable (body is taller than the viewport, all behind
   the fixed shell) purely so iOS' status-bar tap has a document scroller to zero — the JS
   "tap bridge" forwards that to the transcript. Its scrollbar is hidden: the only visible
   scroll indicator is the transcript's own. */
html.hui-convo-open { height: auto; min-height: 100%; overflow-y: scroll; overscroll-behavior: none; background: var(--bg-primary, #1e1e1e); scrollbar-width: none; -ms-overflow-style: none; }
html.hui-convo-open body { min-height: calc(100dvh + 240px); overflow: visible; overscroll-behavior: none; }
html.hui-convo-open::-webkit-scrollbar { width: 0; height: 0; display: none; }
/* While the composer is focused, flatten the document (drop the tap-bridge scroll range) so
   the soft keyboard can't pan it and drag the fixed shell up with it — the iOS fixed+keyboard
   "jumping screen" drift. Restored on blur so the status-bar tap works again when reading. */
html.hui-convo-open.hui-convo-typing { overflow: hidden; }
html.hui-convo-open.hui-convo-typing body { min-height: 100%; }
html.hui-convo-open #shell-root,
html.hui-convo-open .shell { display: none; }

/* The shell: a fixed flex column filling the viewport via inset:0 — reliable across
   dvh/visualViewport quirks (the Chrome-shortcut view reports a short vv.height). JS
   raises only `bottom` to sit the composer above the soft keyboard. */
.hui-convo {
	position: fixed;
	left: 0;
	right: 0;
	top: 0;
	bottom: 0;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	background: var(--bg-primary, #1e1e1e);
	color: var(--text-primary, #d4d4d4);
}

/* Header — always present (a flex row, not an overlay). Pads below the notch strip;
   carries the load bar on its bottom edge. Tapping its background scrolls to top. */
.hui-convo-bar { flex: 0 0 auto; position: relative; z-index: 2; display: flex; align-items: center; gap: 10px; padding: calc(8px + env(safe-area-inset-top, 0px)) 12px 8px; background: var(--bg-secondary, #252526); border-bottom: 1px solid var(--border-color, #333); }
.hui-convo-back { flex: 0 0 auto; width: 36px; height: 36px; border: 0; border-radius: 8px; background: var(--bg-tertiary, #3a3a3a); color: var(--text-primary, #ddd); font-size: 22px; line-height: 1; cursor: pointer; }
.hui-convo-title { flex: 1 1 auto; font-size: 15px; color: var(--text-primary, #cfd3d6); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hui-convo-action { flex: 0 0 auto; height: 34px; min-width: 34px; padding: 0 12px; border: 0; border-radius: 8px; background: var(--bg-tertiary, #3a3a3a); color: var(--text-primary, #ddd); font-size: 13px; line-height: 1; cursor: pointer; }
.hui-convo-menu-btn { font-size: 22px; font-weight: 700; padding: 0 10px; }
/* Overflow "…" popover — an app-supplied panel of controls anchored below the header button. */
.hui-convo-menu-pop { position: fixed; z-index: 30; width: 250px; max-width: min(88vw, 320px); box-sizing: border-box; padding: 12px; border-radius: 12px; background: var(--bg-secondary, #252526); border: 1px solid var(--border-color, #333); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); display: flex; flex-direction: column; gap: 8px; }
.hui-convo-action:hover { background: var(--bg-hover, #4a4a4a); }

/* Indeterminate load bar on the header's bottom edge. */
.hui-convo-loadbar { position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; overflow: hidden; opacity: 0; transition: opacity 0.15s; }
.hui-convo-loadbar--on { opacity: 1; }
.hui-convo-loadbar__strip { position: absolute; left: 0; top: 0; bottom: 0; width: 40%; background: var(--accent, #98c379); animation: hui-convo-slide 1.1s ease-in-out infinite; }
@keyframes hui-convo-slide { 0% { left: -40%; } 100% { left: 100%; } }

/* The scroll region: grows to fill between header and composer; positions the
   scroll-to-newest button. min-height:0 lets it actually shrink inside the flex column. */
.hui-convo-scroll { flex: 1 1 auto; position: relative; min-height: 0; }
/* The chat COLUMN: fills the scroll region (mobile), caps to a centered max-width on wide screens
   (the @media below). It's the positioned containing block for BOTH the scroller and the absolutely
   positioned scroll-to-newest button — so the button is constrained to the column and follows it,
   with no viewport math to keep in sync. */
.hui-convo-col { position: absolute; inset: 0; }
/* The transcript — the CONTAINED scroller. Fills the column. Its own scrollbar (desktop) / fling
   indicator (touch) is the scroll indicator; -webkit-overflow-scrolling gives momentum on iOS. */
.hui-convo-body { position: absolute; inset: 0; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
/* Chat TEXT is selectable — the app chrome opts out of selection globally (tokens.css), but a
   transcript is content, not chrome: users copy replies, and iOS Speak Selection (the built-in
   read-aloud) can only target selectable text. Buttons/controls inside re-opt-out via shared.css. */
.hui-convo-body { -webkit-user-select: text; user-select: text; }
.hui-convo-empty { color: var(--text-secondary, #9aa0a6); font-size: 13px; padding: 16px; text-align: center; }

/* ── Message schema → rendered content (rummage owns this) ─────────────────── */
/* Text bubbles, aligned by role. */
.hui-convo-msg { display: flex; }
.hui-convo-msg--user { justify-content: flex-end; }
.hui-convo-msg--assistant,
.hui-convo-msg--system { justify-content: flex-start; }
/* Chat message text scales with --hui-convo-font-scale (default 1) so a consuming app can offer a
   per-device "chat font size" (e.g. claude-launcher's client-scoped chatFontScale setting). Set the
   var on any ancestor (:root, the conversation host) and the message text follows. */
.hui-convo-text { white-space: pre-wrap; word-break: break-word; font-size: calc(15px * var(--hui-convo-font-scale, 1)); line-height: 1.5; padding: 9px 12px; border-radius: 12px; max-width: 88%; }
.hui-convo-msg--user .hui-convo-text { background: var(--accent, #98c379); color: var(--bg-primary, #1e1e1e); border-bottom-right-radius: 3px; }
.hui-convo-msg--assistant .hui-convo-text,
.hui-convo-msg--system .hui-convo-text { background: var(--bg-secondary, #252526); color: var(--text-primary, #d4d4d4); border-bottom-left-radius: 3px; }
/* App-owned per-message action buttons (e.g. a read-aloud speaker), rendered by rummage in a small row
   at the bubble's bottom edge. Small + muted; each button brightens on hover. The app adds its own
   modifier class (e.g. an "--on" playing state) via the action's className. */
.hui-convo-msg-actions { align-self: flex-end; flex: 0 0 auto; display: inline-flex; align-items: center; gap: 2px; margin: 0 0 2px 4px; }
.hui-convo-msg-action { display: inline-flex; align-items: center; gap: 5px; width: auto; min-width: 28px; height: 28px; padding: 0 6px; border: none; background: transparent; color: var(--text-secondary, #9aa0a6); border-radius: 7px; cursor: pointer; opacity: 0.5; font: inherit; font-size: 12px; touch-action: manipulation; -webkit-user-select: none; user-select: none; transition: opacity 0.15s ease, color 0.15s ease; }
.hui-convo-msg-action:hover { opacity: 1; color: var(--text-primary, #e4e4e4); }
.hui-convo-msg-action:active { transform: translateY(1px); }
.hui-convo-msg-action svg { display: block; }
.hui-convo-msg-action--on { opacity: 1; color: var(--accent, #98c379); }
/* Read-aloud (opts.readAloud): each top-level block of an assistant message is wrapped in a section
   row [ content | speaker ], so the small read button lines up beside its paragraph / list / code block.
   Read sections tint accent + fade; the section currently reading gets an inset accent bar. */
.hui-convo-sec { display: flex; align-items: flex-start; gap: 4px; }
/* Reserve a small LEFT gutter for the read-aloud accent bar, then pull it back into the bubble's own left
   padding with a matching negative margin — so the bar (inset box-shadow, below) sits LEFT of the text
   instead of overlapping the first characters, and the text position is unchanged (no shift on read). */
.hui-convo-sec-body { flex: 1 1 auto; min-width: 0; padding-left: 8px; margin-left: -8px; } /* holds the group's block(s) beside the read button */
/* markdown's own first/last-child margin resets targeted `.hui-md > :first/last-child`; now that blocks
   are grouped into a section body, re-scope them to the body's first/last block so message top/bottom
   stay flush (inner blocks keep their normal inter-paragraph margins). */
.hui-md > .hui-convo-sec:first-child > .hui-convo-sec-body > :first-child { margin-top: 0; }
.hui-md > .hui-convo-sec:last-child > .hui-convo-sec-body > :last-child { margin-bottom: 0; }
.hui-convo-sec-btn { flex: 0 0 auto; margin-top: 1px; width: 26px; height: 26px; padding: 4px; display: inline-flex; align-items: center; justify-content: center; border: none; background: transparent; color: var(--text-secondary, #9aa0a6); border-radius: 7px; cursor: pointer; opacity: 0.4; touch-action: manipulation; -webkit-tap-highlight-color: transparent; -webkit-user-select: none; user-select: none; transition: opacity 0.15s ease, color 0.15s ease; }
.hui-convo-sec-btn svg { display: block; width: 16px; height: 16px; }
.hui-convo-sec-btn:hover { opacity: 0.9; color: var(--text-primary, #e4e4e4); }
.hui-convo-sec-btn:active { transform: translateY(1px); }
.hui-convo-sec--read .hui-convo-sec-btn { color: var(--accent, #98c379); opacity: 0.55; }
.hui-convo-sec-btn--on { opacity: 1; color: var(--accent, #98c379); }
/* Played (read) sections fade so you can see at a glance what's been read vs. not. */
.hui-convo-sec--read > :first-child { opacity: 0.5; transition: opacity 0.2s ease; }
/* The section currently being read: bright + an accent bar + a tint (overrides the read fade when re-reading). */
.hui-convo-sec--reading > :first-child { opacity: 1; box-shadow: inset 3px 0 0 var(--accent, #98c379); background: rgba(152, 195, 121, 0.1); border-radius: 4px; }
/* Rail read buttons reuse .hui-convo-rail-btn; the reading state glows accent (like the send button). */
.hui-convo-rail-read--on { border-color: var(--accent, #98c379); color: var(--accent, #98c379); }
/* Read-aloud voice/speed picker (speech-settings.mjs) — the Settings section rummage provides for read-aloud. */
.hui-convo-speech { display: flex; flex-direction: column; gap: 14px; max-width: 720px; }
.hui-convo-speech-note { margin: 0; color: var(--text-secondary, #9aa0a6); font-size: 13px; line-height: 1.5; }
.hui-convo-speech-row { display: flex; align-items: center; gap: 14px; }
.hui-convo-speech-label { flex: 0 0 64px; font-size: 14px; color: var(--text-secondary, #b7bcc2); }
.hui-convo-speech-select { flex: 1 1 auto; min-width: 0; height: 40px; padding: 0 10px; border: 1px solid var(--border-color, #3a3a3a); border-radius: 10px; background: var(--bg-secondary, #2d2d2d); color: var(--text-primary, #e4e4e4); font-size: 15px; -webkit-tap-highlight-color: transparent; }
.hui-convo-speech-rate { flex: 1 1 auto; min-width: 0; accent-color: var(--accent, #98c379); }
.hui-convo-speech-ratev { flex: 0 0 auto; min-width: 52px; text-align: right; font-size: 15px; color: var(--text-primary, #e4e4e4); font-variant-numeric: tabular-nums; }
.hui-convo-speech-hint { margin: -6px 0 0; color: var(--text-secondary, #6b7075); font-size: 12px; line-height: 1.4; }
.hui-convo-speech-hint:empty { display: none; }
.hui-convo-speech-test { align-self: flex-start; height: 44px; padding: 0 18px; border: 1px solid var(--border-color, #3a3a3a); border-radius: 10px; background: var(--bg-secondary, #2d2d2d); color: var(--text-primary, #e4e4e4); font-size: 15px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hui-convo-speech-test:active { background: var(--bg-tertiary, #3a3a3a); }
.hui-convo-speech-test--on { border-color: var(--accent, #98c379); color: var(--accent, #98c379); }
.hui-convo-speech-toggle { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; margin-top: 4px; }
.hui-convo-speech-check { flex: 0 0 auto; width: 20px; height: 20px; margin-top: 2px; accent-color: var(--accent, #98c379); }
.hui-convo-speech-toggle-text { display: flex; flex-direction: column; gap: 2px; }
.hui-convo-speech-toggle-label { font-size: 15px; color: var(--text-primary, #e4e4e4); }
.hui-convo-speech-toggle-desc { font-size: 12px; color: var(--text-secondary, #6b7075); line-height: 1.4; }
/* Queued/pending outgoing message — greyed until the other side picks it up (confirmed in source). */
.hui-convo-msg--pending { opacity: 0.45; transition: opacity 0.25s ease; }
.hui-convo-inline { display: flex; flex-direction: column; gap: 6px; }
/* Typing / "thinking" indicator — a status line at the bottom of the transcript. Default is
   rotating periods (driven in JS); { text, icon } override. */
.hui-convo-typing { display: flex; align-items: center; gap: 8px; padding: 8px 4px 8px 2px; color: var(--text-secondary, #9aa0a6); font-size: 14px; }
.hui-convo-typing-icon { flex: 0 0 auto; display: inline-flex; align-items: center; color: var(--accent, #98c379); }
.hui-convo-typing-icon svg { width: 18px; height: 18px; display: block; }
.hui-convo-typing-dots { min-width: 1.4em; font-weight: 700; letter-spacing: 2px; }

/* Markdown (markdown.mjs = marked + DOMPurify → standard HTML in .hui-md). Tight chat rhythm. */
.hui-convo-text .hui-md,
.hui-convo-blocktext .hui-md { white-space: normal; }
.hui-md > :first-child { margin-top: 0; }
.hui-md > :last-child { margin-bottom: 0; }
.hui-md p { margin: 0 0 8px; }
.hui-md h1, .hui-md h2, .hui-md h3, .hui-md h4, .hui-md h5, .hui-md h6 { margin: 10px 0 6px; font-weight: 700; line-height: 1.3; }
.hui-md h1 { font-size: 1.25em; }
.hui-md h2 { font-size: 1.15em; }
.hui-md h3 { font-size: 1.05em; }
.hui-md ul, .hui-md ol { margin: 0 0 8px; padding-left: 1.4em; }
.hui-md li { margin: 2px 0; }
.hui-md li > input[type="checkbox"] { margin-right: 6px; }
.hui-md code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.88em; background: rgba(127, 127, 127, 0.18); padding: 1px 5px; border-radius: 4px; }
.hui-md pre { margin: 6px 0; padding: 10px 12px; background: rgba(0, 0, 0, 0.28); border: 1px solid var(--border-color, #333); border-radius: 8px; overflow-x: auto; }
.hui-md pre code { font-size: 0.85em; white-space: pre; background: none; padding: 0; }
/* Copy ICON for code blocks. The button lives in a zero-height STICKY anchor pinned to the block's top,
   so it stays in frame the whole time any part of a long block is scrolled into view; the button itself
   is absolutely positioned at an EQUAL 12px inset on top and right (and the codewrap's 12px
   padding-bottom gives the same gap at the bottom), so it reads as a symmetric corner button. */
.hui-md-codewrap { position: relative; padding-bottom: 12px; }
.hui-md-copyanchor { position: sticky; top: -12px; height: 0; z-index: 3; }
/* opacity < 1 promotes the button to its own compositing LAYER; on iOS that layer, sitting over a
   horizontally-scrollable <pre> (which only happens on a narrow phone, where the code overflows), has its
   taps swallowed by the scroll layer — so it reads as "there but translucent and unclickable" on phone yet
   works on tablet (code fits, no h-scroll). Keep it fully opaque + touch-action:manipulation so the tap
   always lands. */
.hui-md-copy { position: absolute; top: 12px; right: 12px; z-index: 4; display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; box-sizing: border-box; padding: 0; border: 1px solid var(--border-color, #3a3a3a); border-radius: 7px; background: var(--bg-secondary, #252526); color: var(--text-secondary, #9aa0a6); cursor: pointer; touch-action: manipulation; -webkit-user-select: none; user-select: none; }
.hui-md-copy:hover { color: var(--text-primary, #e4e4e4); }
.hui-md-copy:active { transform: translateY(1px); }
.hui-md-copy svg { width: 16px; height: 16px; display: block; }
/* Single-line block: no scrolling, so drop the sticky anchor + bottom pad and CENTER the button against
   the block (the 12px top inset hangs it low on one line). Static anchor → the button positions relative
   to .hui-md-codewrap. Center with top:0/bottom:0 + margin:auto (NOT translateY) — a persistent `transform`
   promotes the button to its own compositing layer, which on iOS can misroute a tap over the code to the
   layer beneath it; margin-auto centering keeps it a plain, reliably-tappable box. */
.hui-md-codewrap--oneline { padding-bottom: 0; }
.hui-md-codewrap--oneline .hui-md-copyanchor { position: static; }
.hui-md-codewrap--oneline .hui-md-copy { top: 0; bottom: 0; margin-top: auto; margin-bottom: auto; }
/* Compound selector (0,2,0) so the copied state beats .hui-md-copy:hover (also 0,2,0, but earlier). */
.hui-md-copy.hui-md-copy--done { color: #14240b; border-color: var(--accent, #98c379); background: var(--accent, #98c379); opacity: 1; }
.hui-md a { color: var(--accent, #98c379); text-decoration: underline; word-break: break-word; }
.hui-md blockquote { margin: 6px 0; padding: 2px 0 2px 12px; border-left: 3px solid var(--border-color, #444); color: var(--text-secondary, #9aa0a6); }
.hui-md hr { border: 0; border-top: 1px solid var(--border-color, #333); margin: 10px 0; }
.hui-md strong { font-weight: 700; }
.hui-md em { font-style: italic; }
/* GFM tables — scroll horizontally inside the bubble rather than SQUISHING. The wrapper is
   capped at the bubble width and scrolls; the table keeps its natural (content) width. */
.hui-md-tablewrap { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 6px 0; }
.hui-md table { width: max-content; max-width: none; border-collapse: collapse; font-size: 12px; line-height: 1.35; }
.hui-md th, .hui-md td { white-space: nowrap; border: 1px solid var(--border-color, #3a3a3a); padding: 4px 8px; text-align: left; }
.hui-md th { background: rgba(127, 127, 127, 0.12); font-weight: 700; }
/* Collapsible cards (tool calls, thinking) + their blocks. */
.hui-convo-card { background: var(--bg-secondary, #232324); border: 1px solid var(--border-color, #333); border-radius: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.hui-convo-card--muted { border-style: dashed; }
.hui-convo-card > summary { display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px 10px; font-size: 12px; color: var(--accent, #98c379); list-style: none; }
.hui-convo-card > summary::-webkit-details-marker,
.hui-convo-card > summary::marker { display: none; }
.hui-convo-card-chev { flex: 0 0 auto; font-size: 15px; line-height: 1; color: var(--accent, #98c379); transition: transform 150ms ease; }
.hui-convo-card[open] > summary .hui-convo-card-chev { transform: rotate(90deg); }
.hui-convo-card-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Spinner for an ongoing card (e.g. a still-running background task). */
.hui-convo-card-spin { flex: 0 0 auto; width: 13px; height: 13px; border-radius: 50%; border: 2px solid var(--accent, #98c379); border-top-color: transparent; animation: hui-convo-spin 700ms linear infinite; }
@keyframes hui-convo-spin { to { transform: rotate(360deg); } }
.hui-convo-card-body { display: flex; flex-direction: column; }
/* Nested lazy accordion (e.g. "view a background task's output file"). */
.hui-convo-card--nested { margin: 6px 0 2px; border-radius: 8px; }
.hui-convo-lazy-out { max-height: 320px; overflow: auto; }
.hui-convo-blocktext { white-space: pre-wrap; word-break: break-word; font-size: 13px; line-height: 1.5; padding: 6px 10px; }
.hui-convo-card--muted .hui-convo-blocktext { color: var(--text-secondary, #9aa0a6); font-size: 12px; }
.hui-convo-note-line { padding: 6px 10px; border-top: 1px solid var(--border-color, #333); font-size: 11px; color: var(--text-secondary, #9aa0a6); word-break: break-all; }
.hui-convo-file-path { padding: 6px 10px; border-top: 1px solid var(--border-color, #333); font-size: 11px; font-weight: 600; color: var(--accent, #98c379); word-break: break-all; } /* file/diff path header */
.hui-convo-code { margin: 0; padding: 8px 10px; border-top: 1px solid var(--border-color, #333); font-size: 11px; color: var(--text-primary, #cfd3d6); white-space: pre-wrap; word-break: break-word; max-height: 340px; overflow: auto; }
.hui-convo-diff { margin: 0; padding: 6px 0; border-top: 1px solid var(--border-color, #333); font-size: 11px; max-height: 360px; overflow: auto; }
.hui-convo-diff > div { padding: 0 10px; white-space: pre-wrap; word-break: break-word; }
.hui-convo-diff-del { color: #e06c75; background: rgba(224, 108, 117, 0.1); }
.hui-convo-diff-add { color: var(--accent, #98c379); background: rgba(152, 195, 121, 0.12); }
/* Edit stat: +adds / -dels next to the card name (Claude-app style). */
.hui-convo-card-stat { flex: 0 0 auto; display: inline-flex; gap: 6px; margin-left: 8px; font-size: 12px; font-variant-numeric: tabular-nums; }
.hui-convo-stat-add { color: var(--accent, #98c379); }
.hui-convo-stat-del { color: #e06c75; }
/* Unified patch (structuredPatch): gutter line number + context/removed/added rows, ⋯ between hunks. */
.hui-convo-patch { margin: 0; padding: 6px 0; border-top: 1px solid var(--border-color, #333); font-size: 11px; line-height: 1.45; max-height: 420px; overflow: auto; -webkit-overflow-scrolling: touch; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.hui-convo-patch-row { display: flex; }
.hui-convo-patch-num { flex: 0 0 auto; width: 3.2em; padding: 0 8px 0 4px; text-align: right; color: var(--text-secondary, #6a6a6a); user-select: none; }
.hui-convo-patch-line { flex: 1 1 auto; white-space: pre-wrap; word-break: break-word; padding-right: 8px; }
.hui-convo-patch-del { background: rgba(224, 108, 117, 0.12); }
.hui-convo-patch-del .hui-convo-patch-line { color: #e06c75; }
.hui-convo-patch-add { background: rgba(152, 195, 121, 0.14); }
.hui-convo-patch-add .hui-convo-patch-line { color: var(--accent, #98c379); }
.hui-convo-patch-ctx .hui-convo-patch-line { color: var(--text-primary, #b8bcc0); }
.hui-convo-patch-gap { padding: 2px 10px; color: var(--text-secondary, #6a6a6a); text-align: center; }
.hui-convo-out { padding: 8px 10px; border-top: 1px solid var(--border-color, #333); font-size: 11px; color: var(--text-secondary, #9aa0a6); white-space: pre-wrap; word-break: break-word; max-height: 240px; overflow: auto; }
/* Prompt-panel options (rendered from the prompt schema into .hui-convo-panel). */
.hui-convo-prompt-title { font-size: 13px; font-weight: 600; color: var(--accent, #98c379); padding-right: 30px; }
.hui-convo-prompt-msg { font-size: 13px; color: var(--text-primary, #c4c4c4); line-height: 1.4; }
.hui-convo-prompt-opts { display: flex; flex-direction: column; gap: 6px; margin-top: 2px; }
.hui-convo-prompt-opt { display: flex; flex-direction: column; gap: 3px; text-align: left; padding: 10px 12px; border: 1px solid var(--border-color, #3a3a3a); border-radius: 4px; background: var(--bg-tertiary, #2d2d2d); color: var(--text-primary, #e4e4e4); font-size: 14px; cursor: pointer; }
.hui-convo-prompt-opt-label { font-weight: 500; }
.hui-convo-prompt-opt-desc { font-size: 12px; font-weight: 400; color: var(--text-secondary, #9a9a9a); line-height: 1.35; white-space: normal; }
.hui-convo-prompt-opt:hover { border-color: var(--accent, #98c379); }
.hui-convo-prompt-opt:disabled { opacity: 0.5; cursor: default; }
.hui-convo-prompt-opt--sel { border-color: var(--accent, #98c379); }
/* Option preview accordion (a <details> sibling of the option button) — Claude-app-style collapsed
   code/preview under an option. Groups with the button as one visual card. */
.hui-convo-prompt-optwrap { display: flex; flex-direction: column; }
.hui-convo-prompt-opt-preview { margin: 2px 0 0 4px; font-size: 12px; }
.hui-convo-prompt-opt-preview-sum { color: var(--text-secondary, #9a9a9a); cursor: pointer; padding: 4px 2px; list-style: revert; user-select: none; }
.hui-convo-prompt-opt-preview-sum:hover { color: var(--accent, #98c379); }
.hui-convo-prompt-opt-preview-body { margin: 4px 0 2px; padding: 8px 10px; border-radius: 4px; background: var(--bg-secondary, #232323); color: var(--text-primary, #e4e4e4); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.4; white-space: pre; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.hui-convo-prompt-hint { font-size: 11px; color: var(--text-secondary, #7a7a7a); }
/* Multi-question form: a PAGED wizard — a tab row to jump between questions, one page
   visible at a time (also swipeable), Back/Next, and a Submit that fires the batch. */
/* The prompt/wizard is a CARD in the transcript flow (not a fixed bottom panel), so you can
   scroll up to the conversation above it — matching Claude Code's own app. */
/* margin-bottom clears the home indicator: the prompt card renders in the transcript with the composer
   HIDDEN, so nothing else provides the bottom safe-area inset and the card (Send button + border) would
   run under the home indicator and get clipped. */
/* margin-top:auto DOCKS the card to the bottom of the transcript when the content is shorter than the
   viewport (the composer is hidden during a prompt, so nothing else holds the bottom) — otherwise the
   flex-start column floated it at the top with a big gap above the keyboard. Collapses to 0 when the
   content overflows, so a tall card still scrolls normally. */
.hui-convo-prompt-card { position: relative; margin-top: auto; display: flex; flex-direction: column; gap: 8px; padding: 12px; border: 1px solid var(--accent, #98c379); border-radius: 8px; background: var(--bg-secondary, #232323); margin-bottom: env(safe-area-inset-bottom, 0px); }
/* Keyboard up → the keyboard (not the home indicator) is below the card, so drop the safe-area gap. */
html.hui-convo-open.hui-convo-typing .hui-convo-prompt-card { margin-bottom: 0; }
/* Cancel = an × in the card's top-right corner (like Claude Code's app). */
.hui-convo-prompt-x { position: absolute; top: 6px; right: 6px; z-index: 1; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; padding: 0; border: none; border-radius: 6px; background: transparent; color: var(--text-secondary, #9a9a9a); font-size: 16px; line-height: 1; cursor: pointer; }
.hui-convo-prompt-x:hover { background: var(--bg-tertiary, #2d2d2d); color: var(--text-primary, #e4e4e4); }
.hui-convo-form { display: flex; flex-direction: column; gap: 8px; overflow: hidden; }
/* Animated page turns (forward slides in from the right, back from the left). */
.hui-convo-form-page--fwd { animation: hui-convo-page-fwd 200ms ease; }
.hui-convo-form-page--back { animation: hui-convo-page-back 200ms ease; }
@keyframes hui-convo-page-fwd { from { opacity: 0.35; transform: translateX(28px); } to { opacity: 1; transform: none; } }
@keyframes hui-convo-page-back { from { opacity: 0.35; transform: translateX(-28px); } to { opacity: 1; transform: none; } }
/* Question tabs — the SAME squared, underline-on-current style as the app's tab strip
   (.shell-tabs__btn): flat, no pills, an accent underline marks the current question and
   a ✓ marks answered ones. */
.hui-convo-form-tabs { display: flex; flex-wrap: wrap; gap: 2px; border-bottom: 1px solid var(--border-color, #333); }
.hui-convo-form-tab { padding: 7px 14px; border: none; border-bottom: 2px solid transparent; background: transparent; color: var(--text-secondary, #9a9a9a); font-size: 13px; cursor: pointer; }
.hui-convo-form-tab--done { color: var(--text-primary, #e4e4e4); }
.hui-convo-form-tab--done::after { content: " ✓"; color: var(--accent, #98c379); }
.hui-convo-form-tab--cur { color: var(--text-primary, #e4e4e4); border-bottom-color: var(--accent, #98c379); font-weight: 600; }
.hui-convo-form-page { display: flex; flex-direction: column; gap: 6px; }
.hui-convo-prompt-gtitle { font-size: 13px; font-weight: 600; color: var(--accent, #98c379); }
.hui-convo-prompt-gmsg { font-size: 13px; color: var(--text-primary, #c4c4c4); line-height: 1.4; }
.hui-convo-form-text { width: 100%; box-sizing: border-box; margin-top: 4px; padding: 10px 12px; border: 1px solid var(--border-color, #3a3a3a); border-radius: 4px; background: var(--bg-secondary, #232323); color: var(--text-primary, #e4e4e4); font-size: 16px; }
.hui-convo-form-text:focus { outline: none; border-color: var(--accent, #98c379); }
/* Single-question free-text: an input + a Send button on one row (the answer submits
   immediately, so it needs its own send — Enter works too). */
.hui-convo-prompt-textrow { display: flex; gap: 6px; margin-top: 4px; align-items: stretch; }
.hui-convo-prompt-textrow .hui-convo-form-text { flex: 1; margin-top: 0; }
/* Form text input + voice placeholder — one row, identical for single- and multi-question forms. */
.hui-convo-form-textrow { display: flex; gap: 6px; margin-top: 4px; align-items: stretch; }
.hui-convo-form-textrow .hui-convo-form-text { flex: 1; min-width: 0; margin-top: 0; }
/* Voice-input placeholder beside prompt text inputs (parity with the composer's mic) — inert until STT. */
.hui-convo-prompt-mic { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 40px; padding: 0; background: var(--bg-secondary, #232323); border: 1px solid var(--border-color, #3a3a3a); border-radius: 4px; color: var(--text-secondary, #9aa0a6); opacity: 0.55; cursor: default; }
.hui-convo-prompt-textsend { flex: 0 0 auto; padding: 0 16px; border: none; border-radius: 4px; background: var(--accent, #98c379); color: #14210a; font-size: 14px; font-weight: 600; cursor: pointer; }
.hui-convo-prompt-textsend:disabled { opacity: 0.4; cursor: default; }
.hui-convo-form-nav { display: flex; justify-content: space-between; gap: 8px; }
.hui-convo-form-navbtn { padding: 8px 14px; border: 1px solid var(--border-color, #3a3a3a); border-radius: 4px; background: var(--bg-tertiary, #2d2d2d); color: var(--text-primary, #e4e4e4); font-size: 13px; cursor: pointer; }
.hui-convo-prompt-submit { margin-top: 4px; padding: 11px 12px; border: none; border-radius: 4px; background: var(--accent, #98c379); color: #14210a; font-size: 14px; font-weight: 600; cursor: pointer; }
.hui-convo-prompt-submit:disabled { opacity: 0.4; cursor: default; }

/* Pinned action panel (e.g. an interactive prompt) — a flex row above the composer. */
.hui-convo-panel { flex: 0 0 auto; border-top: 2px solid var(--accent, #98c379); background: var(--bg-secondary, #232323); padding: 12px; display: flex; flex-direction: column; gap: 8px; max-height: 72%; overflow-y: auto; -webkit-overflow-scrolling: touch; }

/* Composer — a flex row on the bottom (always present; the keyboard just shrinks the scroll
   region above it). safe-area-inset-bottom clears the home indicator; the extra below it is
   kept small so the controls sit LOW (near the bottom edge, like a native chat) rather than
   floating on a tall pad. Sides inset a touch more so the controls don't hug the edges. */
.hui-convo-composer { flex: 0 0 auto; position: relative; z-index: 2; border-top: 1px solid var(--border-color, #2a2a2a); background: var(--bg-primary, #1e1e1e); padding: 8px calc(14px + env(safe-area-inset-right, 0px)) calc(3px + env(safe-area-inset-bottom, 0px)) calc(14px + env(safe-area-inset-left, 0px)); display: flex; flex-direction: column; gap: 6px; }
/* Composer input (textarea) — auto-grows in JS; font-size 16px keeps iOS from zooming on focus. */
.hui-convo-input { width: 100%; box-sizing: border-box; resize: none; border: 1px solid var(--border-color, #3a3a3a); border-radius: 12px; background: var(--bg-tertiary, #2a2a2a); color: var(--text-primary, #e4e4e4); font: inherit; font-size: 16px; line-height: 1.35; padding: 10px 12px; max-height: 140px; overflow-y: auto; }
.hui-convo-input::placeholder { color: var(--text-secondary, #7a7a7a); }
.hui-convo-input:focus { outline: none; border-color: var(--accent, #98c379); }
.hui-convo-composer-bar { display: flex; align-items: center; gap: 8px; }
.hui-convo-composer-spacer { flex: 1 1 auto; }
/* Boot-reconcile indicator — a small centred "Updating…" pill above the textarea (shown only
   while the open-from-cache background reconcile is genuinely in flight; see showSync). */
/* Persistent notice banner (activity poll's `notice`) — a session-state the user must act on,
   above the composer. Amber, full-width; tappable variant (action:"menu") shows it's interactive. */
.hui-convo-notice { display: block; width: 100%; box-sizing: border-box; margin: 0; padding: 10px 14px; border: 0; border-top: 1px solid rgba(224, 108, 117, 0.4); background: rgba(224, 108, 117, 0.14); color: #e06c75; font-size: 13px; font-weight: 600; text-align: center; font-family: inherit; }
.hui-convo-notice--tap { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hui-convo-notice--tap::after { content: " ›"; opacity: 0.7; }
@media (hover: hover) { .hui-convo-notice--tap:hover { background: rgba(224, 108, 117, 0.22); } }
.hui-convo-sync { display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 11px; color: var(--text-secondary, #9aa0a6); padding-top: 2px; }
.hui-convo-sync-spin { flex: 0 0 auto; width: 11px; height: 11px; border-radius: 50%; border: 2px solid var(--text-secondary, #9aa0a6); border-top-color: transparent; animation: hui-convo-spin 700ms linear infinite; }

/* ── Side rail (opts.rail): giant gutter buttons on wide viewports ──────────
   The chat column caps at 780px centered (see the ≥800px media block), so wide screens have
   real gutters — the rail parks big touch targets in the right one. Hidden narrower than
   1024px (780px column + room for the 88px buttons on both sides), so phones never see it.
   It lives inside the fixed shell, so it lifts with the keyboard like the composer. */
.hui-convo-rail { display: none; }
@media (min-width: 1024px) {
	.hui-convo-rail { position: absolute; right: 18px; bottom: 188px; z-index: 25; display: flex; flex-direction: column; align-items: center; gap: 20px; width: 96px; }
}
.hui-convo-rail-btn { width: 88px; height: 88px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 1px solid var(--border-color, #3a3a3a); border-radius: 24px; background: var(--bg-secondary, #252526); color: var(--text-primary, #ddd); cursor: pointer; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35); -webkit-tap-highlight-color: transparent; }
.hui-convo-rail-btn svg { width: 40px; height: 40px; }
.hui-convo-rail-btn:disabled { opacity: 0.35; cursor: default; }
.hui-convo-rail-send { border: 0; background: var(--accent, #98c379); color: #14210a; font-size: 44px; font-weight: 700; line-height: 1; }
/* Recording: the mic becomes a big ✓ with a red pulse — unmissable "I'm listening" state. */
.hui-convo-rail-mic--on { border-color: #e06c75; color: #e06c75; font-size: 44px; line-height: 1; animation: hui-rail-pulse 1.2s ease-in-out infinite; }
/* Transcribing: the clip is uploading/decoding — inert until the text lands. */
.hui-convo-rail-mic--busy { opacity: 0.6; font-size: 44px; line-height: 1; cursor: default; }
@keyframes hui-rail-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(224, 108, 117, 0.45); } 50% { box-shadow: 0 0 0 16px rgba(224, 108, 117, 0); } }
@media (prefers-reduced-motion: reduce) { .hui-convo-rail-mic--on { animation: none; } }
/* Rail status/error message — a floating TOAST above the buttons, NOT an inline element: it must
   never reflow the rail (an error string used to push the buttons up). Empty = hidden. */
.hui-convo-rail-hint { position: absolute; right: 0; bottom: 100%; margin-bottom: 10px; width: max-content; max-width: 240px; padding: 8px 12px; border-radius: 10px; background: rgba(20, 20, 22, 0.94); border: 1px solid var(--border-color, #3a3a3a); color: var(--text-primary, #e4e4e4); font-size: 12px; line-height: 1.3; text-align: center; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45); pointer-events: none; }
.hui-convo-rail-hint:empty { display: none; }
/* Transcribing spinner on the mic button (replaces the old static "…"). */
.hui-convo-rail-spin { display: inline-block; width: 34px; height: 34px; border-radius: 50%; border: 3px solid rgba(152, 195, 121, 0.28); border-top-color: var(--accent, #98c379); animation: hui-convo-spin 700ms linear infinite; }

/* Composer mic (opts.transcribe) — a small round voice button in the composer bar, present on EVERY
   viewport (the phone has no rail, so this is its voice input). Shares the rail mic's recording
   states through the one shared recorder, so a recording started from either shows on both. */
.hui-convo-composer-mic { flex: 0 0 auto; width: 36px; height: 36px; padding: 0; display: flex; align-items: center; justify-content: center; border: 1px solid var(--border-color, #3a3a3a); border-radius: 50%; background: var(--bg-secondary, #252526); color: var(--text-secondary, #9aa0a6); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hui-convo-composer-mic svg { width: 18px; height: 18px; }
.hui-convo-composer-mic--on { border-color: #e06c75; color: #e06c75; font-size: 18px; line-height: 1; animation: hui-rail-pulse 1.2s ease-in-out infinite; }
.hui-convo-composer-mic--busy { opacity: 0.6; cursor: default; }
@media (prefers-reduced-motion: reduce) { .hui-convo-composer-mic--on { animation: none; } }
/* Shared transcribing spinner (both mic surfaces) — sized to whichever button it sits in. */
.hui-convo-mic-spin { display: inline-block; border-radius: 50%; border: 3px solid rgba(152, 195, 121, 0.28); border-top-color: var(--accent, #98c379); animation: hui-convo-spin 700ms linear infinite; }
.hui-convo-rail-mic .hui-convo-mic-spin { width: 34px; height: 34px; }
.hui-convo-composer-mic .hui-convo-mic-spin { width: 16px; height: 16px; border-width: 2px; }
/* Shared recorder hint — a floating toast centered above the composer, visible on phone + tablet
   (the rail's own hint only serves the Web Speech fallback). Empty = hidden. */
.hui-convo-rec-hint { position: absolute; left: 50%; transform: translateX(-50%); bottom: 92px; z-index: 30; width: max-content; max-width: min(80vw, 300px); padding: 8px 12px; border-radius: 10px; background: rgba(20, 20, 22, 0.94); border: 1px solid var(--border-color, #3a3a3a); color: var(--text-primary, #e4e4e4); font-size: 12px; line-height: 1.3; text-align: center; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45); pointer-events: none; }
.hui-convo-rec-hint:empty { display: none; }

/* ── Background-tasks sheet (opts.tasks) ────────────────────────────────────
   Opened by tapping the typing/status indicator. A bottom sheet over the chat shell:
   backdrop dims the transcript; the panel lists running tasks (live elapsed) then
   finished ones; a task with output expands on tap. */
.hui-convo-typing--tap { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hui-convo-tasks { position: absolute; inset: 0; z-index: 40; display: flex; align-items: flex-end; background: rgba(0, 0, 0, 0.45); }
.hui-convo-tasks-panel { width: 100%; max-height: 78%; display: flex; flex-direction: column; background: var(--bg-primary, #1e1e1e); border-top: 1px solid var(--border-color, #333); border-radius: 14px 14px 0 0; box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.4); }
.hui-convo-tasks-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 8px 8px 16px; font-size: 15px; font-weight: 600; color: var(--text-primary, #ddd); }
.hui-convo-tasks-close { flex: 0 0 auto; background: none; border: 0; color: var(--text-secondary, #9aa0a6); font-size: 16px; line-height: 1; padding: 6px 12px; cursor: pointer; }
.hui-convo-tasks-body { overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; padding: 0 12px calc(12px + env(safe-area-inset-bottom, 0px)); }
.hui-convo-tasks-sec { font-size: 12px; color: var(--text-secondary, #9aa0a6); margin: 10px 2px 6px; }
.hui-convo-tasks-empty { padding: 18px 4px 22px; text-align: center; font-size: 13px; color: var(--text-secondary, #9aa0a6); }
.hui-convo-task { background: var(--bg-secondary, #252526); border: 1px solid var(--border-color, #333); border-radius: 10px; padding: 10px 12px; margin-bottom: 8px; }
.hui-convo-task--tap .hui-convo-task-head { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hui-convo-task-label { font-size: 13px; color: var(--text-primary, #ddd); overflow-wrap: anywhere; }
.hui-convo-task-meta { display: flex; align-items: center; gap: 6px; margin-top: 4px; font-size: 12px; color: var(--text-secondary, #9aa0a6); }
.hui-convo-task-meta--bad { color: #e06c75; }
.hui-convo-task-more { margin-top: 4px; }
.hui-convo-task-cap { margin: 8px 2px 4px; font-size: 11px; font-weight: 600; color: var(--text-secondary, #9aa0a6); text-transform: uppercase; letter-spacing: 0.04em; }
.hui-convo-task-cmd,
.hui-convo-task-out { margin: 0; padding: 8px; max-height: 40vh; overflow: auto; -webkit-overflow-scrolling: touch; background: var(--bg-tertiary, #161616); border-radius: 6px; font-size: 11px; line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text-secondary, #b8bcc2); }
.hui-convo-task-cmd { max-height: 28vh; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.hui-convo-send { flex: 0 0 auto; width: 36px; height: 36px; border: 0; border-radius: 50%; background: var(--accent, #98c379); color: var(--bg-primary, #1e1e1e); font-size: 18px; font-weight: 700; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.hui-convo-send:disabled { opacity: 0.5; cursor: default; }
/* While the session is working, the send button becomes a STOP button (interrupt). Same accent
   circle as the native app; the ■ glyph is sized down so it reads as a square, not a giant block. */
.hui-convo-send--stop { font-size: 13px; }
.hui-convo-send--stop:disabled { opacity: 1; cursor: pointer; } /* stop stays actionable */
.hui-convo-composer--locked .hui-convo-input { opacity: 0.5; }
/* While the keyboard is up, the composer sits ABOVE the keyboard, not at the physical bottom — so its
   home-indicator padding (env(safe-area-inset-bottom)) is dead space between the toolbar and the
   keyboard. Drop it when typing so the composer meets the keyboard flush. */
html.hui-convo-open.hui-convo-typing .hui-convo-composer { padding-bottom: 3px; }
/* Attach button + attachment chips (composer.onAttach). */
.hui-convo-attach-btn { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--text-secondary, #9aa0a6); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hui-convo-attach-btn:hover { color: var(--text-primary, #e4e4e4); }
.hui-convo-attach-btn svg { display: block; }
.hui-convo-attach-row { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 4px 2px; }
.hui-convo-attach-chip { display: inline-flex; align-items: center; gap: 6px; max-width: 220px; padding: 4px 6px 4px 10px; border: 1px solid var(--border-color, #3a3a3a); border-radius: 14px; background: var(--bg-tertiary, #2d2d2d); font-size: 12px; color: var(--text-primary, #e4e4e4); }
.hui-convo-attach-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hui-convo-attach-x { flex: 0 0 auto; width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--text-secondary, #9a9a9a); font-size: 11px; cursor: pointer; }
.hui-convo-attach-x:hover { color: var(--text-primary, #e4e4e4); background: var(--bg-secondary, #232323); }

/* Scroll-to-newest — floats at the bottom-right of the chat COLUMN (its .hui-convo-col parent),
   so it stays over the chat area on any width and needs no keyboard/viewport math. */
.hui-convo-scrolldown { position: absolute; right: 14px; bottom: 14px; width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--border-color, #3a3a3a); background: var(--bg-tertiary, #2d2d2d); color: var(--text-primary, #e4e4e4); font-size: 20px; line-height: 1; cursor: pointer; display: none; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45); z-index: 3; }
.hui-convo-scrolldown--show { display: flex; }
/* A new message landed while the reader is scrolled up: pulse a ring + tint accent so it's noticed
   without yanking their scroll. Cleared once they reach the bottom (onScroll) or tap the button. */
.hui-convo-scrolldown--pulse { border-color: var(--accent, #98c379); color: var(--accent, #98c379); animation: hui-convo-scrolldown-pulse 1.15s ease-in-out infinite; }
@keyframes hui-convo-scrolldown-pulse {
	0%, 100% { box-shadow: 0 0 0 0 rgba(152, 195, 121, 0.45), 0 2px 8px rgba(0, 0, 0, 0.45); }
	50% { box-shadow: 0 0 0 7px rgba(152, 195, 121, 0), 0 2px 8px rgba(0, 0, 0, 0.45); }
}
@media (prefers-reduced-motion: reduce) { .hui-convo-scrolldown--pulse { animation: none; } }

/* ── All platforms/sizes ─────────────────────────────────────────────────────
   Mobile is full-width. On tablet/desktop, constrain the transcript + composer to a
   readable centered column so messages don't stretch edge-to-edge on a wide monitor. */
@media (min-width: 800px) {
	/* Cap the chat column to a centered max-width. The scroll-to-newest button lives INSIDE this
	   column, so it stays over the chat automatically — no per-width offset to maintain. */
	.hui-convo-col,
	.hui-convo-panel,
	.hui-convo-composer > * { max-width: 780px; margin-inline: auto; width: 100%; box-sizing: border-box; }
	.hui-convo-bar { padding-inline: max(12px, calc((100% - 780px) / 2)); }
}
/* Pointer devices: a touch of hover feedback the touch build doesn't need. */
@media (hover: hover) {
	.hui-convo-back:hover { background: var(--bg-hover, #4a4a4a); }
	.hui-convo-scrolldown:hover { border-color: var(--accent, #98c379); }
}


/* ╭─ source: api/extensions/drag-drop/drag-drop.css ─╮ */
/*
 * drag-drop extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── Drag-drop demo (remap-style: pool + drop targets) ──────────────────── */

.dnd-columns {
	display: grid;
	grid-template-columns: 1fr 260px;
	gap: 16px;
	min-height: 240px;
}

.dnd-targets,
.dnd-pool {
	display: flex;
	flex-direction: column;
	border: 1px solid var(--border-color);
	border-radius: 4px;
	overflow: hidden;
}

.dnd-col-header {
	padding: 8px 12px;
	font-size: 11px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.5px;
	color: var(--text-secondary);
	background-color: var(--bg-tertiary);
	border-bottom: 1px solid var(--border-color);
}

.dnd-list {
	flex: 1;
	overflow-y: auto;
	padding: 8px;
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.dnd-target {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 8px 10px;
	border: 1px dashed var(--border-color);
	border-radius: 4px;
	background-color: var(--bg-primary);
	font-size: 12px;
}

.dnd-target.drop-active {
	border-color: var(--accent-green);
	background-color: rgba(152, 195, 121, 0.1);
}

.dnd-target-label {
	flex-shrink: 0;
	width: 160px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.dnd-target-slot {
	flex: 1;
	display: flex;
	flex-wrap: wrap;
	gap: 4px;
	min-height: 22px;
	align-items: center;
}

/* Multi-target row variant — taller, vertical alignment, chips wrap freely */

.dnd-target.dnd-target--multi {
	align-items: flex-start;
	min-height: 56px;
}

.dnd-target--multi .dnd-target-label {
	padding-top: 4px;
}

.dnd-target-empty {
	font-size: 11px;
	color: var(--text-disabled);
	font-style: italic;
}

.dnd-chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 4px 10px;
	border-radius: 12px;
	background-color: var(--bg-tertiary);
	border: 1px solid var(--border-color);
	font-size: 12px;
	color: var(--text-primary);
	cursor: grab;
}

.dnd-chip:active {
	cursor: grabbing;
}

/* Pool chip hover feedback — green accent, matches active-on-hover theme. */

.dnd-pool .dnd-chip:hover {
	background-color: rgba(151, 202, 97, 0.18);
	border-color: var(--accent-green);
	color: var(--text-primary);
}

.dnd-chip.matched {
	background-color: rgba(151, 202, 97, 0.18);
	border-color: var(--accent-small);
	color: var(--accent-small);
}

.dnd-chip-x {
	background: none;
	border: none;
	color: var(--accent-small);
	cursor: pointer;
	font-size: 11px;
	padding: 0;
	margin-left: 2px;
}

.dnd-chip-x:hover {
	color: var(--danger-red);
}


/* ╭─ source: api/extensions/extensions-manager/extensions-manager.css ─╮ */
/*
 * extensions-manager extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── Extensions Manager ─────────────────────────────────────────────────── */

.ext-manager {
	display: flex;
	flex-direction: column;
	gap: 10px;
}

.ext-manager-section {
	border: 1px solid var(--border-color);
	background: var(--bg-secondary);
}

/* Collapsed: the section's own bottom border is enough — drop the header's so
   they don't stack into a double line. */

.ext-manager-section.collapsed > .ext-manager-section-header {
	border-bottom: none;
}

.ext-manager-section-header {
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
	padding: 9px 16px;
	background: var(--bg-tertiary, #2a2a2a);
	border: none;
	border-bottom: 1px solid var(--border-color);
	color: var(--text-primary);
	font-size: 13px;
	font-weight: 600;
	cursor: pointer;
	text-align: left;
}

.ext-manager-section-header:hover {
	background: var(--bg-primary);
}

.ext-manager-section-caret {
	display: inline-flex;
	align-items: center;
	color: var(--text-secondary);
	transition: transform 120ms ease;
}

.ext-manager-section-caret.open {
	transform: rotate(90deg);
}

.ext-manager-section-count {
	margin-left: auto;
	background: var(--accent-green);
	color: var(--bg-primary);
	border-radius: 10px;
	padding: 1px 9px;
	font-size: 11px;
	font-weight: 600;
}

.ext-manager-toolbar {
	display: flex;
	gap: 8px;
	/* Full-width header: bleed past the page side padding so the toolbar spans
	   edge-to-edge; the buttons keep their own inset. The accordion sections below
	   stay inset like every other settings page. */
	margin: 0 -20px 10px;
	padding: 8px 20px 12px;
	border-bottom: 1px solid var(--border-color);
}

.ext-manager-empty {
	color: var(--text-muted);
	font-size: 13px;
	padding: 16px 4px;
	text-align: center;
}

.ext-manager-item {
	background: transparent;
	border: none;
	border-bottom: 1px solid var(--border-color);
	padding: 12px 16px;
	transition: background-color 80ms ease;
}

.ext-manager-item:hover {
	background: var(--bg-tertiary);
}

.ext-manager-section-body > .ext-manager-item:last-child {
	border-bottom: none;
}

.ext-manager-item--disabled {
	opacity: 0.72;
}

.ext-manager-item--error {
	border-color: var(--danger-red);
}

.ext-manager-item-head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 12px;
}

.ext-manager-item-title {
	font-size: 14px;
	font-weight: 600;
	color: var(--text-primary);
}

.ext-manager-item-meta {
	font-size: 11px;
	color: var(--text-muted);
	margin-top: 2px;
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.ext-manager-item-desc {
	color: var(--text-secondary);
	font-size: 12px;
	margin-top: 6px;
	line-height: 1.4;
}

.ext-manager-item-error {
	color: var(--danger-red);
	font-size: 12px;
	margin-top: 6px;
}

.ext-manager-item-toggle {
	display: flex;
	align-items: center;
}

/* Per-row reload button — small, icon-only, sits between the title block and the
   enable toggle. Reload is the only lifecycle action a built-in offers, and a
   handy "kick it" for any wedged extension. */

.ext-manager-item-reload {
	flex-shrink: 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	margin-left: auto;
	padding: 0;
	background: transparent;
	border: 1px solid var(--border-color);
	border-radius: 4px;
	color: var(--text-secondary);
	cursor: pointer;
	transition:
		background-color 0.12s,
		color 0.12s,
		border-color 0.12s;
}

.ext-manager-item-reload:hover {
	background: var(--bg-tertiary);
	color: var(--text-primary);
	border-color: var(--accent-blue);
}

.ext-manager-item-reload:active {
	transform: scale(0.96);
}

.ext-manager-item-reload svg {
	display: block;
}

.ext-manager-required-tag {
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.08em;
	color: var(--accent-small);
	border: 1px solid var(--accent-small);
	padding: 2px 6px;
	border-radius: 4px;
	background: transparent;
}

/* `.ext-manager-item-settings` used to wrap the per-card settings panel; that
   panel moved to the Settings pages (grouped by category) — rule dropped.
   The row + label + control rules below stayed under their original class
   names so they style settings-pages' rendered rows as-is. */

.ext-manager-setting-row {
	display: grid;
	grid-template-columns: 1fr 220px;
	gap: 12px;
	align-items: center;
}

.ext-manager-setting-label-text {
	color: var(--text-primary);
	font-size: 12px;
}

.ext-manager-setting-desc {
	color: var(--text-muted);
	font-size: 11px;
	margin-top: 2px;
	line-height: 1.4;
}

.ext-manager-setting-control {
	display: flex;
	justify-content: flex-end;
}

.ext-manager-setting-control .form-control-sm {
	width: 100%;
	max-width: 200px;
}


/* ╭─ source: api/extensions/menubar/menubar.css ─╮ */
/*
 * menubar extension — styles (co-located; concatenated by `rummage styles`).
 */

.app-menubar {
	display: flex;
	align-items: stretch;
	height: 100%;
	-webkit-app-region: no-drag;
}

.app-menu-btn {
	height: 100%;
	padding: 0 12px;
	background: none;
	border: none;
	color: var(--text-primary);
	font-size: 13px;
	font-family: var(--font-family);
	cursor: pointer;
	-webkit-app-region: no-drag;
	user-select: none;
}

/* :hover gated to real hover pointers so a touch tap doesn't leave the button
 * "stuck" highlighted (and pop on the next tap); .active (menu open) is NOT gated
 * — it must light up on touch too. */
@media (hover: hover) {
	.app-menu-btn:hover {
		background-color: var(--selection-accent-bg);
		color: var(--selection-accent-text);
	}
}
.app-menu-btn.active {
	background-color: var(--selection-accent-bg);
	color: var(--selection-accent-text);
}

/* Suppress the UA focus ring. After closing DevTools, focus came back
   to the last-clicked menubar button and the default orange/white outline
   was lingering with rounded corners. */

.app-menu-btn:focus {
	outline: none;
}


/* ╭─ source: api/extensions/modal/modal.css ─╮ */
/*
 * modal extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── Modal ──────────────────────────────────────────────────────────────── */

.modal {
	display: none;
	position: fixed;
	z-index: 1000;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	background-color: rgba(0, 0, 0, 0.7);
	align-items: center;
	justify-content: center;
}

.modal.active {
	display: flex;
}

/* Fill mode — modal targets an element instead of the window. The mount gets
   position:relative (set by the extension if static) so this overlays just that
   region. Mirrors the spinner primitive's overlay:"fill" vs "window". */

.modal--fill {
	position: absolute;
	z-index: 20;
}

.modal-prompt-label {
	display: block;
	margin-bottom: 8px;
	font-size: 13px;
	color: var(--text-secondary);
}

.modal-content {
	background-color: var(--bg-secondary);
	border-radius: 8px;
	width: 90%;
	max-width: 600px;
	max-height: 90vh;
	display: flex;
	flex-direction: column;
	box-shadow: 0 8px 32px var(--shadow-heavy);
}

.modal-small {
	max-width: 400px;
}

.modal-lg {
	max-width: 720px;
}

.modal-xl {
	max-width: 860px;
	width: 95%;
}

.modal-header {
	padding: 20px 24px;
	border-bottom: 1px solid var(--border-color);
	display: flex;
	justify-content: space-between;
	align-items: center;
}

.modal-header h2 {
	font-size: 18px;
	font-weight: 600;
	color: var(--text-primary);
}

.modal-close {
	width: 32px;
	height: 32px;
	border: none;
	background: none;
	color: var(--text-secondary);
	font-size: 24px;
	cursor: pointer;
	border-radius: 4px;
	transition: all 0.15s ease;
	display: flex;
	align-items: center;
	justify-content: center;
	line-height: 1;
}

/* Modal close × turns red on hover — matches win-ctrl-btn--close + the
   notify close button. Consistent "destructive intent" hover. */

.modal-close:hover {
	background-color: var(--danger-red);
	color: #fff;
}

.modal-body {
	flex: 1;
	padding: 24px;
	overflow-y: auto;
}

.modal-footer {
	padding: 16px 24px;
	border-top: 1px solid var(--border-color);
	display: flex;
	justify-content: flex-end;
	gap: 12px;
}

.modal-description {
	font-size: 13px;
	color: var(--text-secondary);
	margin-bottom: 12px;
}

/* error() dialog flavor — applied to the dialog header via headerClass */
.error-header {
	display: flex;
	align-items: center;
	gap: 12px;
	background-color: rgba(244, 67, 54, 0.1);
	border-bottom: 2px solid var(--danger-red);
}


/* ╭─ source: api/extensions/multi-select/multi-select.css ─╮ */
/*
 * multi-select extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── Multi-select dropdown ──────────────────────────────────────────────── */

.ms {
	position: relative;
	display: block;
	width: 100%;
}

.ms-input {
	display: flex;
	align-items: center;
	flex-wrap: nowrap;
	gap: 4px;
	height: 38px;
	padding: 6px 28px 6px 8px;
	background-color: var(--bg-primary);
	border: 1px solid var(--border-color);
	border-radius: 4px;
	cursor: pointer;
	color: var(--text-primary);
	font-size: 13px;
	position: relative;
	overflow: hidden;
}

.ms.ms-open .ms-input,
.ms-input:focus {
	border-color: var(--accent-green);
	outline: none;
}

/* Hover on the dropdown field — matches focus + open colour. */

.ms-input:hover:not(:focus) {
	border-color: var(--accent-green);
}

.ms.ms-open .ms-input:hover {
	border-color: var(--accent-green);
}

.ms-placeholder {
	color: var(--text-secondary);
}

.ms-arrow {
	position: absolute;
	right: 8px;
	top: 50%;
	transform: translateY(-50%);
	color: var(--text-secondary);
	pointer-events: none;
	display: inline-flex;
	width: 12px;
	height: 12px;
	align-items: center;
	justify-content: center;
}

.ms-arrow svg {
	width: 12px;
	height: 12px;
	stroke: currentColor;
	stroke-width: 2;
	fill: none;
}

.ms.ms-open .ms-arrow {
	color: var(--accent-small);
	transform: translateY(-50%) rotate(180deg);
}

.ms-chip {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	padding: 2px 4px 2px 8px;
	background-color: rgba(151, 202, 97, 0.18);
	border: 1px solid var(--accent-small);
	border-radius: 12px;
	font-size: 11px;
	color: var(--accent-small);
}

.ms-chip-more {
	background: var(--bg-tertiary);
	border-color: var(--border-color);
	color: var(--text-secondary);
}

.ms-chip-x {
	width: 16px;
	height: 16px;
	border: none;
	background: none;
	color: var(--accent-small);
	cursor: pointer;
	font-size: 13px;
	line-height: 1;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
}

.ms-chip-x:hover {
	color: var(--danger-red);
	background-color: rgba(244, 67, 54, 0.15);
}

.ms-panel {
	position: absolute;
	top: calc(100% + 4px);
	left: 0;
	right: 0;
	background-color: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 4px;
	box-shadow: 0 6px 18px var(--shadow-heavy);
	z-index: 100;
	display: flex;
	flex-direction: column;
	max-height: 260px;
}

/* Auto-flip (open.js adds .ms-up when there's no room below): drop the panel UPWARD. */
.ms.ms-up .ms-panel {
	top: auto;
	bottom: calc(100% + 4px);
}

/* Pending state (setBusy(true)) — the choice is being applied asynchronously and hasn't
   confirmed yet. Swap the chevron for a spinner and block re-opening until it clears. */
.ms.ms-busy .ms-input {
	pointer-events: none;
	opacity: 0.7;
}
.ms.ms-busy .ms-arrow svg {
	display: none;
}
.ms.ms-busy .ms-arrow::after {
	content: "";
	display: block;
	/* .ms-arrow is a 12x12 inline-flex box; the 2px border makes this ::after wider than that, so as a
	   flex item it gets width-shrunk while its height is preserved → a spinning vertical OVAL. Pin the box
	   to the container size (border-box) and opt out of flex shrink/grow so it stays a true circle. */
	box-sizing: border-box;
	flex: none;
	width: 12px;
	height: 12px;
	border: 2px solid var(--border-color);
	border-top-color: var(--primary, #98c379);
	border-radius: 50%;
	animation: ms-spin 0.7s linear infinite;
}
@keyframes ms-spin {
	to {
		transform: rotate(360deg);
	}
}

/* display:flex on .ms-panel overrides the [hidden] attribute's UA default
   (display:none) unless we explicitly opt back in — without this, toggling
   .hidden = true on the panel has no visual effect and the dropdown stays
   open forever. */

.ms-panel[hidden] {
	display: none !important;
}

.ms-filter-row {
	padding: 6px;
	border-bottom: 1px solid var(--border-color);
}

.ms-filter-row input {
	width: 100%;
	max-width: none;
}

.ms-list {
	flex: 1;
	overflow-y: auto;
	padding: 4px 0;
}

.ms-option {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 7px 12px;
	cursor: pointer;
	font-size: 13px;
}

.ms-option:hover {
	background-color: var(--bg-hover);
}

.ms-option.selected {
	color: var(--accent-small);
}

/* Single-select rows — context-menu look (no checkmark column).
   Hover gets the full primary-green with dark text; selected stays subtle
   so the hover state is still visible. */

.ms-option-single {
	gap: 0;
}

.ms-option-single:hover {
	background-color: var(--selection-accent-bg);
	color: var(--selection-accent-text);
}

.ms-option-single.selected {
	background-color: var(--tree-selection-bg);
	color: var(--accent-small);
}

.ms-option-single.selected:hover {
	background-color: var(--selection-accent-bg);
	color: var(--selection-accent-text);
}

/* When an option carries a description, the row stacks the label on top
   and the description below in a muted second line. Hover/select colors
   apply to the description text too. */

.ms-option-has-description {
	flex-direction: column;
	align-items: flex-start;
	gap: 2px;
	padding: 7px 12px;
}

.ms-option-description {
	font-size: 11px;
	color: var(--text-muted);
	line-height: 1.4;
}

.ms-option-single.ms-option-has-description:hover .ms-option-description,
.ms-option-single.ms-option-has-description.selected:hover .ms-option-description {
	color: var(--selection-accent-text);
	opacity: 0.85;
}

.ms-check {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 14px;
	height: 14px;
	border: 1px solid var(--border-color);
	border-radius: 3px;
	font-size: 10px;
	color: var(--accent-small);
	background: var(--bg-primary);
}

.ms-option.selected .ms-check {
	border-color: var(--accent-small);
	background: rgba(151, 202, 97, 0.12);
}

.ms-empty {
	padding: 14px;
	text-align: center;
	color: var(--text-secondary);
	font-size: 12px;
}


/* ╭─ source: api/extensions/notify/notify.css ─╮ */
/*
 * notify extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── Notification system (faithful port of notify.js) ───────────────────── */

.app-notify-bell {
	position: relative;
	width: 26px;
	height: 22px;
	background: none;
	border: none;
	color: var(--text-secondary);
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 3px;
	transition:
		background-color 0.1s,
		color 0.1s;
}

.app-notify-bell:hover {
	background-color: var(--bg-hover);
	color: var(--text-primary);
}

/* Defensive: size the bell glyph in CSS too (it also carries inline width/height
 * attrs) so the icon never collapses if the inline attrs are stripped. */

.app-notify-bell svg {
	width: 14px;
	height: 14px;
	flex: 0 0 auto;
}
/* Touch devices: bigger bell so it's an easy tap target in the (also-enlarged) bottom bar. */
@media (pointer: coarse) {
	.app-notify-bell {
		width: 40px;
		height: 100%;
	}
	.app-notify-bell svg {
		width: 20px;
		height: 20px;
	}
}

.app-notify-badge {
	position: absolute;
	top: -2px;
	right: 0;
	min-width: 14px;
	height: 14px;
	padding: 0 3px;
	border-radius: 7px;
	background: var(--danger-red);
	color: #fff;
	font-size: 9px;
	font-weight: 700;
	line-height: 14px;
	text-align: center;
	pointer-events: none;
	transform: translateX(35%);
}

/* Toast container — bottom-right; sits above the bottom bar.
   z-index 10000; panel is 10001 so it floats above the toasts.

   Cap the container at viewport height (minus titlebar + bottom-bar +
   small margin) and let it scroll, so a long subtitle / a pile of
   toasts can't push the stack off the top of the window. The CSS-only
   way to do this is `pointer-events: auto` here so the scrollbar is
   interactive — in practice the container only becomes scrollable when
   it's full of toasts, so there's no gap-click pass-through to lose. */

.notify-toast-container {
	position: fixed;
	/* Sit above the bottom bar AND clear the device home indicator / rounded corner
	 * when installed as a PWA (env() is 0 in a normal browser tab, so this is inert
	 * there). Keeps toasts within view instead of clipped by the corner. */
	bottom: calc(var(--bottom-bar-height, 28px) + 4px + env(safe-area-inset-bottom));
	right: calc(4px + env(safe-area-inset-right));
	z-index: 10000;
	display: flex;
	flex-direction: column-reverse;
	gap: 8px;
	max-width: 380px;
	max-height: calc(100vh - var(--bottom-bar-height, 28px) - var(--titlebar-height, 40px) - 16px);
	overflow-y: auto;
	pointer-events: none;
}

/* Restore pointer-events on the inner stack so scrolling works — but
   only when the container has actually overflowed. The :hover guard
   avoids stealing clicks that should pass through to the app. */

.notify-toast-container > * {
	pointer-events: auto;
}

.notify-toast {
	pointer-events: auto;
	position: relative; /* anchor for absolutely-positioned actions */
	display: grid;
	/* Two columns: icon | body. The action buttons (close/copy/expand) are
	   absolutely positioned in the top-right, so the body claims the full
	   width to the right edge — long subtitles + messages no longer get
	   squeezed into a narrow column next to the action buttons. */
	grid-template-columns: 18px 1fr;
	gap: 8px;
	align-items: start;
	padding: 10px 12px;
	background: var(--bg-tertiary);
	border: 1px solid var(--border-color);
	border-left-width: 3px;
	color: var(--text-primary);
	box-shadow: 0 4px 16px var(--shadow-heavy);
	font-size: 12px;
	line-height: 1.4;
	max-width: 380px;
	/* Cap a single toast so a runaway subtitle/expanded message can't
	   eat the whole viewport. The expanded message section has its own
	   inner scroll when fully expanded. */
	max-height: 60vh;
	overflow: hidden;
	word-break: break-word;
	transform: translateX(420px);
	opacity: 0;
	transition:
		transform 0.18s ease-out,
		opacity 0.18s ease-out;
}

.notify-toast__actions {
	position: absolute;
	top: 8px;
	right: 8px;
	z-index: 1;
	display: flex;
	gap: 2px;
	align-items: center;
}

/* Only the title needs to dodge the action buttons — title's first
   line shares row 1 with the icon and would otherwise crash into the
   close/copy/expand. Subtitle / message / progress / expanded sit
   below the action area and reclaim the full body width. */

.notify-toast__title {
	padding-right: 72px; /* 3 × 22px button + small breathing room */
}

.notify-toast__expanded {
	margin-top: 8px;
	padding: 6px 8px;
	background: var(--bg-primary);
	border-left: 2px solid var(--border-color);
	font-size: 11px;
	color: var(--text-primary);
	max-height: 40vh;
	overflow: auto;
}

.notify-toast__message {
	white-space: pre-wrap;
	word-break: break-word;
	font-family: var(--font-family-mono, "Consolas", monospace);
	font-size: 11px;
}

.notify-toast__source {
	margin-top: 4px;
	color: var(--text-secondary);
	font-size: 10px;
	font-style: italic;
}

.notify-toast__expand:hover {
	background-color: var(--selection-accent-bg);
	color: var(--selection-accent-text);
}

.notify-toast--in {
	transform: translateX(0);
	opacity: 1;
}

.notify-toast--out {
	transform: translateX(420px);
	opacity: 0;
}

.notify-toast__icon {
	font-size: 14px;
	line-height: 1.3;
}

.notify-toast__body {
	min-width: 0;
	/* Body scrolls internally when subtitle/message overflows the toast's
	   60vh cap — keeps the icon column and the close/copy/expand actions
	   anchored in place while still letting the user see all the content
	   for very long descriptions. */
	max-height: calc(60vh - 24px);
	overflow-y: auto;
}

.notify-toast__title {
	font-weight: 600;
	color: var(--text-primary);
	white-space: pre-wrap;
	word-break: break-word;
}

.notify-toast__subtitle {
	margin-top: 2px;
	color: var(--text-secondary);
	font-size: 11px;
	white-space: pre-wrap;
	word-break: break-word;
}

.notify-toast__progress {
	margin-top: 6px;
	display: flex;
	flex-direction: column;
	gap: 3px;
}

.notify-toast__progress[hidden],
.notify-toast__expanded[hidden],
.notify-toast__expand[hidden],
.notify-panel-item__progress[hidden],
.notify-panel-item__body[hidden],
.notify-panel-item__expand[hidden] {
	display: none !important;
}

.notify-toast__close,
.notify-toast__copy,
.notify-toast__expand {
	width: 22px;
	height: 22px;
	background: none;
	border: none;
	color: var(--text-secondary);
	cursor: pointer;
	font-size: 13px;
	line-height: 1;
	padding: 0;
	/* Square — matches the title-bar window-control buttons */
	border-radius: 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

/* Hover mirrors the window control buttons:
   copy → primary-green bg + dark text (same as minimize/maximize hover);
   close → danger-red bg + white text (same as the win-ctrl-btn--close hover). */

.notify-toast__copy:hover {
	background-color: var(--selection-accent-bg);
	color: var(--selection-accent-text);
}

.notify-toast__close:hover {
	background-color: var(--danger-red);
	color: #fff;
}

.notify-toast__copy.is-copied {
	color: var(--accent-small);
}

/* Level accents — coloured left border + icon */

.notify-toast--error {
	border-left-color: var(--danger-red);
}

.notify-toast--error .notify-toast__icon {
	color: var(--danger-red);
}

.notify-toast--warn {
	border-left-color: var(--warning-yellow);
}

.notify-toast--warn .notify-toast__icon {
	color: var(--warning-yellow);
}

.notify-toast--info {
	border-left-color: #4ec9b0;
}

.notify-toast--info .notify-toast__icon {
	color: #4ec9b0;
}

.notify-toast--success {
	border-left-color: var(--accent-small);
}

.notify-toast--success .notify-toast__icon {
	color: var(--accent-small);
}

.notify-toast--progress {
	border-left-color: var(--accent-small);
}

.notify-toast--progress .notify-toast__icon {
	color: var(--accent-small);
}

/* Read state — fade the body text/icon but leave controls (close/copy/expand)
   at full opacity so they remain easy to target after a row has been read. */

.notify-toast--read .notify-toast__icon,
.notify-toast--read .notify-toast__body {
	opacity: 0.6;
}

/* Progress bar (shared by toast + panel) */

.notify-progress {
	height: 4px;
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	overflow: hidden;
}

.notify-progress__fill {
	height: 100%;
	width: 0%;
	background: var(--accent-green);
	transition: width 0.2s ease;
}

.notify-progress__text {
	font-size: 10px;
	color: var(--text-secondary);
	font-variant-numeric: tabular-nums;
}

/* Notification history panel */

.notify-panel {
	position: fixed;
	bottom: calc(var(--bottom-bar-height, 28px) + 4px);
	right: 4px;
	width: 380px;
	max-height: 70vh;
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	box-shadow: 0 -6px 20px var(--shadow-heavy);
	display: flex;
	flex-direction: column;
	opacity: 0;
	transform: translateY(6px);
	pointer-events: none;
	transition:
		opacity 0.12s ease,
		transform 0.12s ease;
	z-index: 10001;
}

.notify-panel.open {
	opacity: 1;
	transform: translateY(0);
	pointer-events: auto;
}

.notify-panel-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 8px 12px;
	border-bottom: 1px solid var(--border-color);
	flex-shrink: 0;
}

.notify-panel-title {
	font-size: 12px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.4px;
	color: var(--text-secondary);
}

.notify-panel-list {
	overflow-y: auto;
	flex: 1;
	min-height: 0;
}

.notify-panel-empty {
	padding: 18px 12px;
	color: var(--text-secondary);
	font-size: 12px;
	text-align: center;
	font-style: italic;
}

.notify-panel-item {
	padding: 8px 10px 8px 12px;
	border-bottom: 1px solid var(--divider-color);
	border-left: 3px solid transparent;
	font-size: 12px;
	transition: background-color 0.1s ease;
}

.notify-panel-item:last-child {
	border-bottom: none;
}

.notify-panel-item:hover {
	background-color: var(--bg-hover);
}

/* Per-level left stripe — square (no radius), matches the toast left border */

.notify-panel-item--error {
	border-left-color: var(--danger-red);
}

.notify-panel-item--warn {
	border-left-color: var(--warning-yellow);
}

.notify-panel-item--info {
	border-left-color: #4ec9b0;
}

.notify-panel-item--success {
	border-left-color: var(--accent-small);
}

.notify-panel-item--progress {
	border-left-color: var(--accent-small);
}

.notify-panel-item__head {
	display: grid;
	/* icon | head-text | right-group(time + actions, flex) — the right
	   group always sits at top-right; if expand is hidden the group
	   shrinks naturally and time stays flush right. */
	grid-template-columns: 18px 1fr auto;
	gap: 6px;
	align-items: start;
}

.notify-panel-item__right {
	display: flex;
	align-items: center;
	gap: 8px;
}

.notify-panel-item__actions {
	display: flex;
	gap: 2px;
	align-items: center;
}

.notify-panel-item__icon {
	font-size: 14px;
	line-height: 1.3;
}

.notify-panel-item__head-text {
	min-width: 0;
}

.notify-panel-item__title {
	font-weight: 600;
	color: var(--text-primary);
	word-break: break-word;
}

.notify-panel-item__subtitle {
	margin-top: 1px;
	color: var(--text-secondary);
	font-size: 11px;
	word-break: break-word;
}

.notify-panel-item__time {
	color: var(--text-secondary);
	font-size: 10px;
	font-variant-numeric: tabular-nums;
	padding-top: 2px;
	white-space: nowrap;
}

.notify-panel-item__progress {
	margin-top: 6px;
	display: flex;
	flex-direction: column;
	gap: 3px;
}

.notify-panel-item__body {
	margin-top: 6px;
	padding: 6px 8px;
	background: var(--bg-primary);
	border-left: 2px solid var(--border-color);
	font-size: 11px;
	color: var(--text-primary);
	/* Cap a single expanded item so a huge stack trace can't fill the
	   whole 70vh panel by itself — at least two items stay visible at
	   the same time. The body has its own inner scroll for the rest. */
	max-height: 28vh;
	overflow: auto;
}

.notify-panel-item__message {
	white-space: pre-wrap;
	word-break: break-word;
	font-family: var(--font-family-mono);
	font-size: 11px;
}

.notify-panel-item__source,
.notify-panel-item__action {
	margin-top: 4px;
	color: var(--text-secondary);
	font-size: 10px;
	font-style: italic;
}

.notify-panel-item__expand,
.notify-panel-item__close,
.notify-panel-item__copy {
	width: 22px;
	height: 22px;
	background: none;
	border: none;
	color: var(--text-secondary);
	cursor: pointer;
	font-size: 13px;
	line-height: 1;
	padding: 0;
	/* Square — matches the title-bar window-control buttons */
	border-radius: 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

/* Hover mirrors the window control buttons (same rule as the toast row):
   expand/copy → primary-green bg + dark text; close → red + white. */

.notify-panel-item__expand:hover,
.notify-panel-item__copy:hover {
	background-color: var(--selection-accent-bg);
	color: var(--selection-accent-text);
}

.notify-panel-item__close:hover {
	background-color: var(--danger-red);
	color: #fff;
}

.notify-panel-item__copy.is-copied {
	color: var(--accent-small);
}

.notify-panel-item--error .notify-panel-item__icon {
	color: var(--danger-red);
}

.notify-panel-item--warn .notify-panel-item__icon {
	color: var(--warning-yellow);
}

.notify-panel-item--info .notify-panel-item__icon {
	color: #4ec9b0;
}

.notify-panel-item--success .notify-panel-item__icon {
	color: var(--accent-small);
}

.notify-panel-item--progress .notify-panel-item__icon {
	color: var(--accent-small);
}

/* Read state — fade the icon + head-text/body, but keep the actions column
   (copy/close/expand buttons) at full opacity so they remain easy to target. */

.notify-panel-item--read .notify-panel-item__icon,
.notify-panel-item--read .notify-panel-item__head-text,
.notify-panel-item--read .notify-panel-item__time,
.notify-panel-item--read .notify-panel-item__body {
	opacity: 0.6;
}


/* ╭─ source: api/extensions/onboarding/onboarding.css ─╮ */
/*
 *	@Project: @cldmv/rummage
 *	@Filename: /src/api/extensions/onboarding/onboarding.css
 *	@Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
 *
 * First-run flow. One question on screen at a time, centred, with the whole width
 * given to the decision — this is the first thing anyone sees, and the runner-mode
 * choice in particular needs its descriptions read rather than skimmed.
 *
 * Everything is a token so it inherits the app's theme; nothing here hardcodes a
 * colour.
 */

.rum-onb {
	display: flex;
	flex-direction: column;
	gap: 1rem;
	width: min(34rem, 100%);
	margin: 0 auto;
	padding: 2rem 1.5rem;
}

/* On a viewport with room to spare, the flow reads as a CARD rather than text
   floating in the middle of an empty page — the same shape the boot splash takes
   once there is room for a window. Below that width the border and its padding
   would only steal usable space, so the flow stays flush. Centred vertically too:
   a short step pinned to the top of a tall screen looks unfinished. */
@media (min-width: 40rem) and (min-height: 34rem) {
	.rum-onb {
		margin: max(2rem, (100vh - 40rem) / 2) auto;
		padding: 2rem;
		/* Square, like the shell and the boot splash — rummage's radii belong to
		   small controls (buttons, toasts, menus), not to full panel surfaces. */
		border: 1px solid var(--panel-border, rgba(128, 128, 128, 0.35));
		background: var(--bg-secondary, #252526);
		box-shadow: 0 18px 48px var(--shadow, rgba(0, 0, 0, 0.36));
	}
}

/* Step navigation: ‹ N / M › — back over anything already answered, forward no
   further than the step the flow is actually asking. The counter sits between the
   arrows so the whole thing reads as one control. */
.rum-onb__nav {
	display: flex;
	align-items: center;
	gap: 0.75rem;
}

.rum-onb__nav-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.75rem;
	height: 1.75rem;
	padding: 0;
	border: 1px solid var(--panel-border, rgba(128, 128, 128, 0.35));
	border-radius: 50%;
	background: transparent;
	color: var(--text-secondary, #9aa0a6);
	font-size: 1.125rem;
	line-height: 1;
	cursor: pointer;
}

.rum-onb__nav-btn:hover:not(:disabled) {
	border-color: var(--accent-green, #98c379);
	color: var(--accent-green, #98c379);
}

/* Kept in the layout rather than hidden, so the counter doesn't shift sideways
   between steps as the arrows become available. */
.rum-onb__nav-btn:disabled {
	opacity: 0.3;
	cursor: default;
}

.rum-onb__progress {
	font-size: 0.8125rem;
	color: var(--text-muted, #8a8a8a);
	letter-spacing: 0.04em;
}

.rum-onb__title {
	margin: 0;
	font-size: 1.5rem;
	font-weight: 600;
	color: var(--text, inherit);
}

.rum-onb__body {
	margin: 0;
	color: var(--text-muted, #8a8a8a);
	line-height: 1.5;
}

.rum-onb__field {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

/* ── choice: option cards, not a segmented control ────────────────────────── */
.rum-onb__choices {
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
}

.rum-onb__choice {
	display: flex;
	flex-direction: column;
	gap: 0.25rem;
	padding: 1rem;
	text-align: left;
	cursor: pointer;
	border: 1px solid var(--border, #3a3a3a);
	border-radius: var(--radius-md, 8px);
	background: var(--surface-2, transparent);
	color: inherit;
	font: inherit;
	transition: border-color 0.12s ease, background 0.12s ease;
}

.rum-onb__choice:hover {
	border-color: var(--accent, #4285f4);
}

.rum-onb__choice.is-selected {
	border-color: var(--accent, #4285f4);
	/* A ring rather than a thicker border, so selecting does not reflow the card. */
	box-shadow: 0 0 0 1px var(--accent, #4285f4) inset;
}

.rum-onb__choice-label {
	font-weight: 600;
}

.rum-onb__choice-desc {
	font-size: 0.875rem;
	color: var(--text-muted, #8a8a8a);
	line-height: 1.4;
}

.rum-onb__hint,
.rum-onb__done {
	font-size: 0.875rem;
	color: var(--text-muted, #8a8a8a);
}

.rum-onb__problem {
	font-size: 0.875rem;
	color: var(--accent-red, #e35);
}

.rum-onb__actions {
	display: flex;
	justify-content: flex-end;
	gap: 0.5rem;
	margin-top: 0.5rem;
}

/*
 * The host app mounts the flow in this overlay. Fixed rather than absolute so it
 * covers the composed shell regardless of what is underneath, and scrollable so a
 * long step still reaches its buttons on a short viewport.
 */
.stratax-onboarding-overlay,
.rum-onb-overlay {
	position: fixed;
	inset: 0;
	z-index: 900;
	overflow: auto;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--bg, #1e1e1e);
}

.rum-onb__notice {
	padding: 0.75rem 1rem;
	border: 1px solid var(--accent-yellow, #c90);
	border-radius: var(--radius-md, 8px);
	font-size: 0.875rem;
	line-height: 1.4;
}

.rum-onb__upload-label {
	font-size: 0.875rem;
	color: var(--text-muted, #8a8a8a);
}

/* The native file input is hidden and driven by a ui.button (see render.mjs) —
   the browser's own "Choose File" widget cannot be themed and reads as foreign
   next to every other control here. The chosen filename sits beside the button,
   which is the one thing the native control gave that was worth keeping. */
.rum-onb__upload {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	margin-top: 0.375rem;
}

.rum-onb__upload-name {
	font-size: 0.8125rem;
	color: var(--text-secondary, #9aa0a6);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}


/* ╭─ source: api/extensions/problems-panel/problems-panel.css ─╮ */
/*
 * problems-panel extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── Terminal ───────────────────────────────────────────────────────────── */

/* ─── Problems panel ────────────────────────────────────────────────── */

.problems-panel {
	display: flex;
	flex-direction: column;
	height: 100%;
	min-height: 0;
	background: var(--bg-primary);
	color: var(--text-primary);
}

.problems-panel__header {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 6px 10px;
	border-bottom: 1px solid var(--divider-color);
	flex: 0 0 auto;
}

.problems-panel__list {
	flex: 1 1 auto;
	overflow: auto;
	min-height: 0;
	padding: 4px 0;
	/* The host's body has `user-select: none` so chrome / icon labels don't
	 * highlight on drag. Problems are LOG content — selecting + copying the
	 * message and stack is the whole point of the panel. Cascades to every
	 * descendant; the stack <details> summary still opts back out for click
	 * affordance. */
	user-select: text;
	-webkit-user-select: text;
	cursor: text;
}

.problems-panel__empty {
	padding: 20px 12px;
	color: var(--text-secondary);
	font-size: 12px;
	text-align: center;
}

.problems-panel__row {
	display: flex;
	gap: 8px;
	padding: 6px 12px;
	border-bottom: 1px solid var(--divider-color);
	align-items: flex-start;
}

.problems-panel__row:hover {
	background: var(--bg-secondary);
}

.problems-panel__icon {
	flex: 0 0 auto;
	width: 16px;
	text-align: center;
	font-size: 13px;
	line-height: 1.4;
}

.problems-panel__row--error .problems-panel__icon {
	color: var(--danger-red, #e35);
}

.problems-panel__row--warn .problems-panel__icon {
	color: #d59c11;
}

.problems-panel__row--info .problems-panel__icon {
	color: var(--accent-green);
}

.problems-panel__text {
	flex: 1 1 auto;
	min-width: 0;
}

.problems-panel__title {
	color: var(--text-primary);
	font-size: 12px;
	line-height: 1.4;
	word-break: break-word;
}

.problems-panel__msg {
	color: var(--text-secondary);
	font-size: 11px;
	line-height: 1.4;
	margin-top: 2px;
	word-break: break-word;
}

.problems-panel__meta {
	color: var(--text-secondary);
	font-size: 11px;
	margin-top: 2px;
}

.problems-panel__stack {
	margin-top: 4px;
	color: var(--text-secondary);
	font-size: 11px;
}

.problems-panel__stack summary {
	cursor: pointer;
	user-select: none;
}

.problems-panel__stack pre {
	margin: 4px 0 0;
	padding: 6px 8px;
	background: var(--bg-secondary);
	border-radius: 4px;
	overflow: auto;
	font-size: 11px;
	white-space: pre;
	user-select: text;
}

.problems-panel__actions {
	flex: 0 0 auto;
	display: flex;
	gap: 4px;
	align-items: flex-start;
	opacity: 0;
	transition: opacity 80ms ease;
	user-select: none;
}

.problems-panel__row:hover .problems-panel__actions,
.problems-panel__actions:focus-within {
	opacity: 1;
}

.problems-panel__action {
	padding: 2px 8px;
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 3px;
	color: var(--text-secondary);
	font: inherit;
	font-size: 10px;
	cursor: pointer;
	user-select: none;
}

.problems-panel__action:hover {
	color: var(--accent-green);
	border-color: var(--accent-green);
}


/* ╭─ source: api/extensions/progress-bar/progress-bar.css ─╮ */
/*
 * progress-bar extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── Progress bar (built-in extension) ──────────────────────────────────── */

/* Thin animated strip attached at the top (or bottom) of a container while an
 * async op runs. Hidden until busy. Uses a sliding pseudo-element via CSS
 * keyframes — no JS animation loop. */

.progress-bar {
	position: absolute;
	left: 0;
	right: 0;
	height: 2px;
	overflow: hidden;
	pointer-events: none;
	opacity: 0;
	transition: opacity 120ms ease;
	background: transparent;
	z-index: 5;
}

.progress-bar--top {
	top: 0;
}

.progress-bar--bottom {
	bottom: 0;
}

.progress-bar--busy {
	opacity: 1;
}

.progress-bar__strip {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 0;
	width: 0;
	border-radius: 1px;
	background: var(--accent-green);
}

/* Indeterminate cycle, steady (linear) rate across four phases:
 *   0–25%   grow   — left stays 0, width grows 0 → 30%
 *  25–75%   slide  — width stays 30%, left moves 0 → 70% (so the right edge
 *                   travels at the same constant rate as the left)
 *  75–100%  shrink — left stays 70%, width shrinks 30% → 0
 *
 * `linear` is intentional — the prior cubic-bezier felt slow / fast / slow;
 * constant rate reads as a steady "still working" pulse rather than a bounce.
 */

.progress-bar--busy .progress-bar__strip {
	animation: progress-bar-cycle 1.6s linear infinite;
}

.progress-bar--warn .progress-bar__strip {
	background: #d59c11;
}

.progress-bar--danger .progress-bar__strip {
	background: var(--danger-red, #e35);
}


/* ╭─ source: api/extensions/settings-pages/settings-pages.css ─╮ */
/*
 * settings-pages extension — styles (co-located; concatenated by `rummage styles`).
 */

/* Settings tabs (within a modal body) */
.settings-tabs {
	display: flex;
	border-bottom: 1px solid var(--border-color);
	margin-bottom: 20px;
	gap: 0;
}
.settings-tab-btn {
	background: none;
	border: none;
	border-bottom: 2px solid transparent;
	color: var(--text-secondary);
	cursor: pointer;
	padding: 8px 18px;
	font-size: 13px;
	margin-bottom: -1px;
	transition:
		color 0.15s,
		border-color 0.15s;
}
.settings-tab-btn:hover {
	color: var(--text-primary);
}
.settings-tab-btn.active {
	color: var(--accent-green);
	border-bottom-color: var(--accent-green);
}
.settings-tab-body {
	overflow-y: auto;
}
.settings-tab-panel {
	display: none;
}
.settings-tab-panel.active {
	display: block;
	padding-bottom: 8px;
}
/* ── Settings tabs body padding fix ─────────────────────────────────────── */
.settings-tab-body {
	flex: 1;
	min-height: 0;
}
.settings-tab-panel {
	padding: 0 4px;
}
/* ── Settings Pages (modal + content surfaces) ──────────────────────────── */
.settings-modal-overlay {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.55);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 9000;
}
.settings-modal {
	background: var(--bg-primary);
	color: var(--text-primary);
	border: 1px solid var(--border-color);
	border-radius: 6px;
	box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55);
	display: flex;
	flex-direction: column;
	overflow: hidden;
}
.settings-modal--compact {
	width: 440px;
	max-width: 90vw;
	height: 520px;
	max-height: 85vh;
}
.settings-modal--big {
	width: 880px;
	max-width: 95vw;
	height: 640px;
	max-height: 90vh;
}
.settings-page {
	display: flex;
	flex-direction: column;
	height: 100%;
	min-height: 0;
}
.settings-page-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 10px 16px;
	border-bottom: 1px solid var(--border-color);
	background: var(--bg-secondary);
	flex: 0 0 auto;
}
.settings-page-title {
	font-size: 14px;
	font-weight: 600;
	color: var(--text-primary);
}
.settings-page-close {
	background: transparent;
	border: 1px solid transparent;
	border-radius: 4px;
	color: var(--text-secondary);
	font-size: 20px;
	line-height: 1;
	cursor: pointer;
	padding: 2px 9px;
	transition:
		background-color 80ms ease,
		color 80ms ease,
		border-color 80ms ease;
}
.settings-page-close:hover {
	color: #fff;
	background: var(--danger-red, #c62828);
	border-color: var(--danger-red, #c62828);
}
.settings-page-close:active {
	filter: brightness(0.9);
}
.settings-page-body {
	display: grid;
	grid-template-columns: 180px 1fr;
	grid-template-rows: minmax(0, 1fr);
	flex: 1 1 auto;
	min-height: 0;
	overflow: hidden;
}
/* Wrapper for custom modal/content callbacks (anything passed via
 * openModal({ content }) / openContent({ content })). Default-padded so
 * forms / lists / etc. don't sit flush against the dialog edge.
 * Callers can opt out with `padded: false` for full-bleed content. */
.settings-page-custom-body {
	flex: 1 1 auto;
	min-height: 0;
	overflow: auto;
	padding: 16px 20px;
	display: flex;
	flex-direction: column;
	gap: 12px;
}
.settings-page-nav {
	display: flex;
	flex-direction: column;
	background: var(--bg-secondary);
	border-right: 1px solid var(--border-color);
	overflow-y: auto;
	padding: 8px 0;
}
.settings-page-nav-btn {
	display: block;
	width: 100%;
	text-align: left;
	padding: 8px 16px;
	background: transparent;
	border: none;
	color: var(--text-secondary);
	font-size: 13px;
	cursor: pointer;
}
.settings-page-nav-btn:hover {
	color: var(--text-primary);
	background: var(--bg-primary);
}
.settings-page-nav-btn.active {
	color: var(--text-primary);
	background: var(--bg-primary);
	box-shadow: inset 3px 0 0 var(--accent-green);
}
.settings-page-main {
	padding: 16px 20px;
	overflow-y: auto;
	min-height: 0;
	display: flex;
	flex-direction: column;
}
.settings-page-section {
	margin-bottom: 22px;
}
.settings-page-section h3 {
	margin: 0 0 4px;
	font-size: 12px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--text-secondary);
	border-bottom: 1px solid var(--border-color);
	padding-bottom: 6px;
}
.settings-page-subhead {
	margin: 14px 0 4px;
	font-size: 12px;
	font-weight: 600;
	color: var(--text-primary);
	padding-left: 2px;
}
.settings-page-setting-row {
	grid-template-columns: 1fr 240px;
	padding: 9px 10px;
	margin-left: 2px;
	border-radius: 4px;
	cursor: pointer;
}
.settings-page-setting-row:hover {
	background: var(--bg-secondary);
}
.settings-page-extensions {
	display: flex;
	flex-direction: column;
	flex: 1 1 auto;
	min-height: 0;
}
/* Content-takeover (used when no shell is mounted — the demo path). */
.settings-content-takeover {
	display: flex !important;
	flex-direction: column;
	overflow: hidden;
}
.settings-page-content {
	flex: 1 1 auto;
	height: 100%;
	min-height: 0;
	display: flex;
	flex-direction: column;
}
.settings-page-content--shell {
	/* Settings-pages appends this into middle.top (a flex column slot). Make
	 * it the only flex child so it fills the entire slot vertically. */
	flex: 1 1 0;
	min-height: 0;
	width: 100%;
	display: flex;
	flex-direction: column;
}
.settings-page-content--takeover {
	flex: 1 1 auto;
	min-height: 0;
}

/* ── Narrow (mobile): single-column accordion ─────────────────────────────────
 * The desktop two-pane (180px nav + main) leaves a phone ~half the width for the
 * content AND made checking a few categories a tap-in / tap-back slog. On narrow,
 * every category is a full-width row with a chevron; tapping expands that
 * category's settings inline right below it (settings-pages.mjs adds `.open`).
 * No drill-in, no back button. Scoped to the shell so the modal/takeover demo
 * paths (no .shell) keep the two-pane layout. */
.shell[data-viewport="narrow"] .settings-accordion {
	display: flex;
	flex-direction: column;
	/* Override the desktop `.settings-page-body` grid. */
	grid-template-columns: none;
	overflow-y: auto;
	min-height: 0;
}
.shell[data-viewport="narrow"] .settings-acc-header {
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	padding: 15px 16px;
	background: var(--bg-secondary);
	border: 0;
	border-bottom: 1px solid var(--border-color);
	color: var(--text-primary);
	font-size: 15px;
	text-align: left;
	cursor: pointer;
}
.shell[data-viewport="narrow"] .settings-acc-name {
	flex: 1 1 auto;
	min-width: 0;
}
.shell[data-viewport="narrow"] .settings-acc-chevron {
	flex: 0 0 auto;
	color: var(--text-secondary);
	font-size: 18px;
	line-height: 1;
	transition: transform 150ms ease;
}
.shell[data-viewport="narrow"] .settings-acc-item.open > .settings-acc-header {
	background: var(--bg-tertiary);
}
.shell[data-viewport="narrow"] .settings-acc-item.open > .settings-acc-header .settings-acc-chevron {
	transform: rotate(90deg);
	color: var(--text-primary);
}
.shell[data-viewport="narrow"] .settings-acc-body {
	background: var(--bg-primary);
	border-bottom: 1px solid var(--border-color);
	padding: 6px 16px 14px;
}
.shell[data-viewport="narrow"] .settings-acc-body[hidden] {
	display: none;
}
/* Setting rows stack on narrow so the fixed control column can't overflow a
 * phone-width body — label on top, control full-width below. The control cell
 * (ext-manager classes, rendered by settings-pages) right-aligns + caps at 200px
 * for the desktop 2-col row; on narrow make it a left-aligned full-width field. */
.shell[data-viewport="narrow"] .settings-page-setting-row {
	grid-template-columns: 1fr;
	gap: 8px;
}
.shell[data-viewport="narrow"] .ext-manager-setting-control {
	justify-content: flex-start;
}
.shell[data-viewport="narrow"] .ext-manager-setting-control .form-control-sm {
	max-width: none;
}


/* ╭─ source: api/extensions/system-deps/system-deps.css ─╮ */
/*
 * system-deps extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── System dependencies extension ──────────────────────────────────────── */

.hui-system-deps {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.hui-system-deps__header-bar {
	display: flex;
	align-items: center;
	gap: 16px;
	padding: 0 0 10px 0;
	border-bottom: 1px solid var(--divider-color);
	margin-bottom: 4px;
}

.hui-system-deps__intro {
	margin: 0;
	color: var(--text-muted);
	font-size: 12px;
	line-height: 1.5;
	flex: 1 1 auto;
}

/* Prominent re-probe-all button — text + icon, accent green, can't be missed. */

.hui-system-deps__refresh-all {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 6px 14px;
	background: transparent;
	border: 1px solid var(--accent-green);
	border-radius: 4px;
	color: var(--accent-green);
	font: inherit;
	font-size: 12px;
	font-weight: 500;
	cursor: pointer;
	transition:
		background-color 80ms ease,
		color 80ms ease;
}

.hui-system-deps__refresh-all:hover {
	background: var(--accent-green);
	color: var(--bg-primary);
}

.hui-system-deps__refresh-all .hui-system-deps__icon-svg svg {
	width: 14px;
	height: 14px;
}

.hui-system-deps__heading {
	margin: 12px 0 2px 0;
	font-size: 11px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--text-muted);
}

/* Use a grid on the WHOLE table so every row's columns line up — subgrid is the
 * standard CSS Grid feature for this and is supported in Chromium 117+ (well
 * before our Electron 32 / Chromium 128 baseline). Each row keeps its own
 * padding + background but inherits the outer column tracks. */

.hui-system-deps__table {
	display: grid;
	grid-template-columns: minmax(180px, 1.4fr) minmax(150px, 0.8fr) minmax(220px, 2.2fr) auto;
	gap: 8px;
}

.hui-system-deps__row {
	display: grid;
	grid-column: 1 / -1;
	grid-template-columns: subgrid;
	column-gap: 16px;
	row-gap: 6px;
	align-items: center;
	padding: 12px 14px;
	border: 1px solid var(--border-color);
	border-radius: var(--radius-sm);
	background: var(--bg-secondary);
}

.hui-system-deps__row[data-state="missing"] {
	border-color: color-mix(in srgb, var(--danger-red) 50%, var(--border-color));
}

.hui-system-deps__row[data-state="available"] {
	opacity: 0.92;
}

/* PM rows have no border tint when not detected — they're informational, not
 * a problem. Just fade them so the eye knows "this one isn't here". */

.hui-system-deps__row--pm {
	background: var(--bg-tertiary, var(--bg-secondary));
	border-color: var(--border-color) !important;
}

.hui-system-deps__row--pm[data-present="no"] {
	opacity: 0.55;
}

.hui-system-deps__pm-state {
	font-size: 11px;
	font-family: var(--font-mono);
}

.hui-system-deps__pm-state--present {
	color: var(--accent-green);
}

.hui-system-deps__pm-state--absent {
	color: var(--text-muted);
	font-style: italic;
}

.hui-system-deps__count {
	font-weight: 400;
	color: var(--text-muted);
	text-transform: none;
	letter-spacing: 0;
}

.hui-system-deps__subnote {
	margin: -2px 0 8px 0;
	color: var(--text-muted);
	font-size: 11px;
	font-style: italic;
	line-height: 1.45;
}

/* Headings + subnotes that sit INSIDE the single combined grid (so Tools +
 * PMs share the same column tracks for alignment) span every column. */

.hui-system-deps__heading--in-grid,
.hui-system-deps__table > .hui-system-deps__subnote {
	grid-column: 1 / -1;
}

.hui-system-deps__heading--in-grid {
	margin: 6px 0 0 0;
}

.hui-system-deps__heading--in-grid:first-child {
	margin-top: 0;
}

.hui-system-deps__table > .hui-system-deps__subnote {
	margin: -4px 0 4px 0;
}

.hui-system-deps__cell--name {
	display: flex;
	align-items: baseline;
	gap: 8px;
	min-width: 0;
}

.hui-system-deps__id {
	font-size: 11px;
	color: var(--text-muted);
	font-family: var(--font-mono);
}

.hui-system-deps__cell--status {
	display: flex;
	align-items: center;
	gap: 6px;
}

.hui-system-deps__version {
	font-size: 11px;
	color: var(--text-muted);
	font-family: var(--font-mono);
}

.hui-system-deps__cell--desc {
	font-size: 12px;
	color: var(--text-secondary);
	line-height: 1.45;
	min-width: 0;
}

.hui-system-deps__cell--actions {
	display: flex;
	gap: 6px;
	align-items: center;
	justify-content: flex-end;
	flex-wrap: wrap;
}

.hui-system-deps__action-btn {
	padding: 4px 12px;
	background: var(--accent-green);
	border: 1px solid var(--accent-green);
	border-radius: 3px;
	color: var(--bg-primary);
	font: inherit;
	font-size: 11px;
	font-weight: 500;
	cursor: pointer;
	white-space: nowrap;
}

.hui-system-deps__action-btn:hover {
	opacity: 0.88;
}

/* Icon-only buttons — no border at rest. Accent variant tints the glyph green
 * so per-row actions read as "the green ⟳" / "the green ↗" instead of subtle
 * gray. Hover gets a subtle background tint, no border outline. */

.hui-system-deps__icon-btn {
	flex: 0 0 auto;
	width: 26px;
	height: 26px;
	padding: 0;
	background: transparent;
	border: none;
	border-radius: 3px;
	color: var(--text-secondary);
	cursor: pointer;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	transition:
		background-color 80ms ease,
		color 80ms ease;
}

.hui-system-deps__icon-btn:hover {
	background: color-mix(in srgb, var(--accent-green) 18%, transparent);
	color: var(--accent-green);
}

.hui-system-deps__icon-btn--accent {
	color: var(--accent-green);
}

.hui-system-deps__icon-svg {
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

.hui-system-deps__icon-svg svg {
	width: 14px;
	height: 14px;
}

.hui-system-deps__cmd {
	user-select: text;
	font-size: 11px;
	font-family: var(--font-mono);
	color: var(--text-secondary);
	background: var(--bg-tertiary, var(--bg-secondary));
	padding: 2px 6px;
	border-radius: 3px;
	cursor: copy;
}

.hui-system-deps__cmd:hover {
	color: var(--accent-green);
}

.hui-system-deps__elevated {
	font-size: 10px;
	color: var(--color-warn, var(--accent-yellow, #d39e00));
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.hui-system-deps__none {
	font-size: 11px;
	color: var(--text-muted);
	font-style: italic;
}

/* Install output modal — streaming stdout/stderr from `host.deps.install`. */

.hui-deps-output__cmd {
	margin: 0 0 8px 0;
}

.hui-deps-output__cmd code {
	user-select: text;
	word-break: break-all;
}

.hui-deps-output__log {
	margin: 0;
	min-height: 220px;
	max-height: 50vh;
	padding: 8px 10px;
	background: var(--bg-primary);
	border: 1px solid var(--border-color);
	border-radius: 3px;
	color: var(--text-primary);
	font-family: var(--font-mono);
	font-size: 11px;
	line-height: 1.45;
	overflow: auto;
	white-space: pre-wrap;
	word-break: break-word;
	user-select: text;
}

.hui-deps-output__status {
	flex: 1 1 auto;
	font-family: var(--font-mono);
}


/* ╭─ source: api/extensions/terminal/terminal.css ─╮ */
/*
 * terminal extension — styles (co-located; concatenated by `rummage styles`).
 */

.terminal {
	display: flex;
	flex-direction: column;
	min-height: 0;
	width: 100%;
	height: 100%;
	background-color: var(--bg-primary);
	color: var(--text-primary);
	font-family: var(--font-family-mono);
	font-size: 12px;
	line-height: 1.45;
	border: 1px solid var(--panel-border);
	border-radius: var(--radius-sm);
	overflow: hidden;
}

.terminal__header {
	flex: 0 0 auto;
	min-height: 0; /* host can add content; empty by default takes no space */
	background-color: var(--bg-secondary);
	border-bottom: 1px solid var(--divider-color);
}

.terminal__header:empty {
	border-bottom: 0;
}

.terminal__scroll {
	flex: 1 1 auto;
	min-height: 0;
	overflow-y: auto;
	overflow-x: auto;
	padding: 6px 10px;
}

.terminal__buffer {
	margin: 0;
	padding: 0;
	font-family: inherit;
	font-size: inherit;
	line-height: inherit;
	color: inherit;
	background: transparent;
	white-space: pre-wrap;
	word-break: break-word;
	user-select: text;
}

.term-line {
	display: inline;
	font: inherit;
}

.term-line--info {
	color: var(--text-primary);
}

.term-line--warn {
	color: #d59c11;
}

.term-line--error {
	color: var(--danger-red);
}

.term-line--success {
	color: var(--accent-green);
}

.term-line--output {
	color: var(--text-muted);
}

.terminal__inputrow {
	flex: 0 0 auto;
	display: flex;
	align-items: baseline;
	gap: 4px;
	padding: 4px 10px 6px;
	border-top: 1px solid var(--divider-color);
	background-color: var(--bg-primary);
	font-family: inherit;
	font-size: inherit;
	line-height: inherit;
}

.terminal__inputrow--hidden {
	display: none;
}

.terminal__prompt {
	flex: 0 0 auto;
	color: var(--accent-green);
	white-space: pre;
	user-select: none;
}

.terminal__input {
	flex: 1 1 auto;
	min-width: 0;
	outline: none;
	color: var(--text-primary);
	caret-color: var(--accent-green);
	white-space: pre;
	overflow-x: auto;
	user-select: text;
}

.terminal__input:empty::before {
	/* keeps the line height stable when empty */
	content: "";
}


/* ╭─ source: api/extensions/tooltip/tooltip.css ─╮ */
/*
 * tooltip extension — styles (co-located; concatenated by `rummage styles`).
 */

/* ── Tooltip ────────────────────────────────────────────────────────────── */

#app-tooltip {
	position: fixed;
	/* Must sit ABOVE the notify toast container (10000) and notify panel
	   (10001), or the tooltip renders behind them when hovering a button
	   on a toast / panel item — looked like "hover was below the element". */
	z-index: 10100;
	pointer-events: none;
	padding: 5px 9px;
	background: var(--bg-tertiary);
	border: 1px solid var(--border-color);
	color: var(--text-primary);
	font-size: 12px;
	line-height: 1.5;
	border-radius: 4px;
	box-shadow: 0 3px 10px var(--shadow-heavy);
	max-width: 340px;
	white-space: pre-wrap;
	opacity: 0;
	transition: opacity 0.1s;
	top: -200px;
	left: -200px;
}

#app-tooltip.visible {
	opacity: 1;
}

/* Tooltip caret/tail. Two CSS-triangle pseudo-elements — ::before is the
   border colour (7px), ::after is the fill (6px), 1px peeks for a clean
   bordered look that continues the tooltip's own border. The caret tip
   tracks the target via --caret-x / --caret-y (computed in JS so the
   arrow still points at the target after viewport clamping). */

#app-tooltip::before,
#app-tooltip::after {
	content: "";
	position: absolute;
	width: 0;
	height: 0;
	pointer-events: none;
}

/* Tooltip BELOW target — caret at TOP pointing UP */

#app-tooltip.tip-bottom::before {
	top: -7px;
	left: var(--caret-x, 50%);
	transform: translateX(-50%);
	border: 7px solid transparent;
	border-bottom-color: var(--border-color);
	border-top: 0;
}

#app-tooltip.tip-bottom::after {
	top: -6px;
	left: var(--caret-x, 50%);
	transform: translateX(-50%);
	border: 6px solid transparent;
	border-bottom-color: var(--bg-tertiary);
	border-top: 0;
}

/* Tooltip ABOVE target — caret at BOTTOM pointing DOWN */

#app-tooltip.tip-top::before {
	bottom: -7px;
	left: var(--caret-x, 50%);
	transform: translateX(-50%);
	border: 7px solid transparent;
	border-top-color: var(--border-color);
	border-bottom: 0;
}

#app-tooltip.tip-top::after {
	bottom: -6px;
	left: var(--caret-x, 50%);
	transform: translateX(-50%);
	border: 6px solid transparent;
	border-top-color: var(--bg-tertiary);
	border-bottom: 0;
}

/* Tooltip to the RIGHT of target — caret on LEFT pointing LEFT */

#app-tooltip.tip-right::before {
	left: -7px;
	top: var(--caret-y, 50%);
	transform: translateY(-50%);
	border: 7px solid transparent;
	border-right-color: var(--border-color);
	border-left: 0;
}

#app-tooltip.tip-right::after {
	left: -6px;
	top: var(--caret-y, 50%);
	transform: translateY(-50%);
	border: 6px solid transparent;
	border-right-color: var(--bg-tertiary);
	border-left: 0;
}

/* Tooltip to the LEFT of target — caret on RIGHT pointing RIGHT */

#app-tooltip.tip-left::before {
	right: -7px;
	top: var(--caret-y, 50%);
	transform: translateY(-50%);
	border: 7px solid transparent;
	border-left-color: var(--border-color);
	border-right: 0;
}

#app-tooltip.tip-left::after {
	right: -6px;
	top: var(--caret-y, 50%);
	transform: translateY(-50%);
	border: 6px solid transparent;
	border-left-color: var(--bg-tertiary);
	border-right: 0;
}


/* ╭─ source: api/extensions/tree-view/tree-view.css ─╮ */
/*
 * tree-view extension — styles (co-located; concatenated by `rummage styles`).
 */

/* Resizable panels separated by a draggable divider */
.tree-panel,
.detail-panel {
	display: flex;
	flex-direction: column;
	overflow: hidden;
}
.tree-panel {
	flex: 1;
	min-width: 150px;
}
/* ── Tree view ──────────────────────────────────────────────────────────── */
.tree-view {
	flex: 1;
	overflow-y: auto;
	/* Vertical breathing room + a small 3px left inset so the tree's disclosure
	   chevrons sit slightly to the right of the section-header chevron (rather
	   than perfectly flush). Each row adds its own 8px padding + per-level
	   indent on top of this. */
	padding: 8px 0 8px 3px;
	user-select: none;
}
.tree-item {
	display: flex;
	align-items: center;
	padding: 6px 8px;
	cursor: pointer;
	transition: background-color 0.15s ease;
	/* Square edges — selection background fills the row edge-to-edge */
}
.tree-item:hover {
	background-color: var(--bg-hover);
}
.tree-item.active {
	background-color: var(--tree-selection-bg);
	color: var(--tree-selection-text);
}
/* (Anchor indicator while Shift is held was a white dashed outline —
   removed because it was confusing without context. Selection itself
   is sufficient feedback for the shift-range start point.) */
.tree-arrow {
	width: 16px;
	height: 16px;
	margin-right: 4px;
	flex-shrink: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 10px;
	cursor: pointer;
	user-select: none;
	color: var(--text-secondary);
	transition: color 0.12s;
}
/* Chevron icons already point the right way (down = expanded, right =
   collapsed), so no rotation is applied — unlike the old triangle glyphs. */
.tree-arrow svg {
	width: 14px;
	height: 14px;
}
.tree-arrow:hover {
	color: var(--text-primary);
}
.tree-arrow-spacer {
	width: 16px;
	margin-right: 4px;
	flex-shrink: 0;
}
.tree-children {
	display: block;
}
.tree-children.collapsed {
	display: none;
}
.tree-icon {
	width: 16px;
	height: 16px;
	margin-right: 8px;
	flex-shrink: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 12px;
	color: var(--text-secondary);
}
.tree-icon-folder {
	color: #e8a020;
}
.tree-label {
	flex: 1;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.tree-label-muted {
	color: var(--text-disabled);
}
/* Checkbox-style visibility toggle used in layer-style trees */
.tree-toggle {
	width: 20px;
	height: 20px;
	margin-right: 8px;
	flex-shrink: 0;
	border: 2px solid var(--border-color);
	border-radius: 3px;
	display: flex;
	align-items: center;
	justify-content: center;
	cursor: pointer;
	transition: all 0.15s ease;
}
.tree-toggle:hover {
	border-color: var(--accent-small);
}
.tree-toggle.checked {
	background-color: var(--accent-small);
	border-color: var(--accent-small);
}
.tree-toggle.checked::after {
	content: "✓";
	color: var(--bg-primary);
	font-size: 14px;
	font-weight: bold;
}
/* ── Tree-view: partial-check, badge, color dot extras ─────────────────── */
.tree-toggle.partial {
	background-color: var(--accent-small-hover);
	border-color: var(--accent-small-hover);
}
.tree-toggle.partial::after {
	content: "−";
	color: var(--bg-primary);
	font-size: 14px;
	font-weight: bold;
}
.tree-badge {
	margin-left: 6px;
	padding: 1px 6px;
	background: var(--bg-tertiary);
	color: var(--text-secondary);
	border-radius: 10px;
	font-size: 10px;
	font-variant-numeric: tabular-nums;
	flex-shrink: 0;
}
.tree-color-dot {
	width: 8px;
	height: 8px;
	border-radius: 50%;
	margin-right: 6px;
	flex-shrink: 0;
}
/* Per-row action buttons — show on hover only so the tree stays clean.
   Clicks bypass row selection (stopPropagation in the renderer). */
.tree-row-actions {
	display: inline-flex;
	gap: 2px;
	margin-left: auto;
	opacity: 0;
	transition: opacity 0.1s ease;
}
.tree-item:hover .tree-row-actions,
.tree-item.active .tree-row-actions {
	opacity: 1;
}
.tree-row-action {
	width: 22px;
	height: 22px;
	font-size: 12px;
	border-radius: 0;
}
.tree-row-action svg {
	width: 14px;
	height: 14px;
}
.tree-row-action--more {
	color: var(--text-secondary);
}
.tree-row-action:hover {
	background-color: var(--selection-accent-bg);
	color: var(--selection-accent-text);
	transform: none;
	box-shadow: none;
}
.tree-row-action--danger:hover {
	background-color: var(--danger-red);
	color: #fff;
}
/* Tree icons opt-in to being clickable via `onIconClick` — when wired, the
   icon gets cursor:pointer + a subtle hover to signal interactivity. */
.tree-icon.clickable {
	cursor: pointer;
	border-radius: 3px;
	padding: 0 2px;
	transition:
		background-color 0.1s ease,
		color 0.1s ease;
}
.tree-icon.clickable:hover {
	background-color: var(--bg-hover);
	color: var(--accent-small);
}


/* ╭─ source: styles/shared.css ─╮ */
/*
 * shared.css — host-shared component rules with no single owner (lib components:
 * busy overlay, tab-overflow; cross-unit toggles; the chrome user-select opt-out).
 * Concatenated by `rummage styles`.
 */

/* Chrome opt-out — these are surfaces where text selection is noise, not
 * content (icon labels, tab titles, button text, drag regions, etc.).
 * Adding a selector here is preferred over sprinkling per-component
 * user-select:none rules, but the existing per-component rules stay too
 * for surfaces with non-trivial children. */

.app-titlebar,
.app-titlebar *,
.shell__app-bar,
.shell__app-bar *,
.shell-tabs__btn,
.shell-tabs__btn__label,
.shell-tabs__btn__badge,
.shell-tabs__caret,
.shell-icon-btn,
.shell-icon-btn__badge,
.shell-icontabs__col,
.shell-icontabs__col *,
.shell__left-icon-bar,
.shell__left-icon-bar *,
.shell__bottom-bar,
.shell__bottom-bar *,
.menubar,
.menubar *,
.ctx-menu,
.ctx-menu *,
.modal-header,
.modal-footer,
.btn,
button,
.hui-badge,
.hui-tabs__tab,
.hui-segmented__option {
	user-select: none;
	-webkit-user-select: none;
}

.toggle-switch {
	position: relative;
	width: 48px;
	height: 26px;
	flex-shrink: 0;
	margin-left: auto;
}

.toggle-switch input[type="checkbox"] {
	opacity: 0;
	width: 0;
	height: 0;
	position: absolute;
}

.toggle-slider {
	position: absolute;
	inset: 0;
	background-color: var(--bg-hover);
	border-radius: 26px;
	transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
	cursor: pointer;
	box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
}

.toggle-slider:before {
	content: "";
	position: absolute;
	height: 20px;
	width: 20px;
	left: 3px;
	bottom: 3px;
	background-color: var(--text-secondary);
	border-radius: 50%;
	transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
	box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
}

.toggle-switch input[type="checkbox"]:checked + .toggle-slider {
	background-color: var(--accent-small);
	box-shadow:
		inset 0 1px 3px rgba(0, 0, 0, 0.2),
		0 0 8px rgba(151, 202, 97, 0.3);
}

.toggle-switch input[type="checkbox"]:checked + .toggle-slider:before {
	transform: translateX(22px);
	background-color: #fff;
}

/* No focus outline on toggle switches — visual cue would compete with the
   slider thumb animation, and the green checked state is enough feedback. */

.toggle-switch input[type="checkbox"]:focus + .toggle-slider {
	outline: none;
}

.toggle-switch input[type="checkbox"]:disabled + .toggle-slider {
	opacity: 0.5;
	cursor: not-allowed;
}

.toggle-switch input[type="checkbox"]:disabled + .toggle-slider:before {
	background-color: var(--text-disabled);
}

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

@keyframes bottom-bar-segment-in {
	from {
		opacity: 0;
		transform: translateY(3px);
	}
	to {
		opacity: 1;
		transform: translateY(0);
	}
}

/* Generic overflow "…" trigger emitted by createOverflowBar — keep its glyph
   sized like the other inline action icons regardless of host button class. */

.overflow-more {
	color: var(--text-secondary);
}

.overflow-more:hover {
	color: var(--accent-green);
}

.overflow-more svg {
	width: 14px;
	height: 14px;
}

/* ── Tab overflow chevrons + dropdown (installTabOverflow) ──────────────── */

/* Wraps a horizontal tab bar so the unstyled native scrollbar never shows up.
 * Three sibling buttons fold in around the bar; each is hidden when not needed.
 * The bar itself stops handling its own scrollbar (overflow:hidden) — the
 * helper drives `scrollLeft` programmatically via the chevrons. */

.tab-overflow {
	display: flex;
	align-items: stretch;
	min-width: 0;
	flex: 1 1 auto;
}

.tab-overflow__scroll {
	overflow-x: hidden !important;
	overflow-y: hidden !important;
	scrollbar-width: none;
	flex: 1 1 auto;
	min-width: 0;
}

.tab-overflow__scroll::-webkit-scrollbar {
	display: none;
}

.tab-overflow__nav,
.tab-overflow__dropdown {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 22px;
	padding: 0;
	background: transparent;
	border: 0;
	color: var(--text-secondary);
	cursor: pointer;
	transition: color 80ms ease;
}

.tab-overflow__nav:hover,
.tab-overflow__dropdown:hover {
	color: var(--accent-green);
}

.tab-overflow__nav[hidden],
.tab-overflow__dropdown[hidden] {
	display: none !important;
}

.tab-overflow__menu {
	position: fixed;
	z-index: 9999;
	min-width: 180px;
	max-width: 360px;
	max-height: 70vh;
	overflow-y: auto;
	padding: 4px 0;
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 4px;
	box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
	font-size: 12px;
}

.tab-overflow__menu-item {
	display: flex;
	align-items: center;
	gap: 6px;
	width: 100%;
	padding: 6px 10px;
	background: transparent;
	border: 0;
	color: var(--text-primary);
	cursor: pointer;
	text-align: left;
	font: inherit;
}

.tab-overflow__menu-item:hover {
	background: var(--selection-accent-bg);
	color: var(--selection-accent-text);
}

.tab-overflow__menu-item--active {
	color: var(--accent-green);
}

.tab-overflow__menu-marker {
	flex: 0 0 auto;
	width: 12px;
	text-align: center;
	color: var(--accent-green);
	font-size: 10px;
}

.tab-overflow__menu-label {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* ── Busy overlay ───────────────────────────────────────────────────────────
   Host-wide "the renderer is doing something, don't click around" indicator.
   Toggled via `<body class="host-busy">` (the host primitive in src/lib/renderer/
   busy.mjs). Covers everything, ignores pointer events on top of a dim, and
   spins a small ring in the centre. Sits below #app-tooltip but above every
   modal / panel — clicks pass through to the overlay (where they're swallowed)
   instead of reaching wedged UI. */

.host-busy-overlay {
	position: fixed;
	inset: 0;
	z-index: 9999;
	display: none;
	align-items: center;
	justify-content: center;
	flex-direction: column;
	gap: 12px;
	background: rgba(0, 0, 0, 0.32);
	cursor: wait;
	user-select: none;
	pointer-events: auto;
}

.host-busy-overlay__spinner {
	width: 32px;
	height: 32px;
	border: 3px solid var(--border-color);
	border-top-color: var(--accent-blue);
	border-radius: 50%;
	animation: host-busy-spin 0.9s linear infinite;
}

.host-busy-overlay__label {
	color: var(--text-primary);
	font-size: 13px;
	letter-spacing: 0.01em;
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}

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

@keyframes progress-bar-cycle {
	0% {
		left: 0;
		width: 0;
	}
	25% {
		left: 0;
		width: 30%;
	}
	75% {
		left: 70%;
		width: 30%;
	}
	100% {
		left: 100%;
		width: 0;
	}
}

@media (max-width: 700px) {
	.hui-system-deps__row {
		grid-template-columns: 1fr auto;
	}
	.hui-system-deps__cell--desc,
	.hui-system-deps__cell--actions {
		grid-column: 1 / -1;
	}
}


/* ╭─ source: styles/legacy.css ─╮ */
/*
 * legacy.css — QUARANTINE: demo-app-shell + unreferenced/dead rules relocated out
 * of components.css. None are owned by a host primitive/builtin. TODO: delete the
 * dead ones; move demo-shell rules into demo2's own stylesheet.
 */

/* ── Layout ─────────────────────────────────────────────────────────────── */

.app-container {
	display: flex;
	flex-direction: column;
	height: 100vh;
	width: 100vw;
}

.app-body {
	display: flex;
	flex: 1;
	overflow: hidden;
	min-height: 0;
}

/* ── Sidebar ────────────────────────────────────────────────────────────── */

.sidebar {
	width: 280px;
	background-color: var(--bg-secondary);
	border-right: 1px solid var(--border-color);
	display: flex;
	flex-direction: column;
	overflow: hidden;
}

.sidebar-section {
	flex: 1;
	display: flex;
	flex-direction: column;
	overflow: hidden;
}

.sidebar-section h2 {
	padding: 16px 20px 12px;
	font-size: 12px;
	font-weight: 600;
	text-transform: uppercase;
	color: var(--text-secondary);
	letter-spacing: 0.5px;
}

.sidebar-actions {
	padding: 0 20px 12px;
}

.sidebar-list {
	flex: 1;
	overflow-y: auto;
	padding: 0 12px 12px;
}

.sidebar-item {
	padding: 12px;
	background-color: var(--bg-tertiary);
	cursor: pointer;
	transition: all 0.15s ease;
	border-left: 3px solid transparent;
}

.sidebar-item:hover {
	background-color: var(--bg-hover);
}

.sidebar-item.active {
	background-color: var(--bg-active);
	border-left-color: var(--accent-green);
}

.sidebar-item-name {
	font-weight: 500;
	margin-bottom: 4px;
	color: var(--text-primary);
}

.sidebar-item-path {
	font-size: 11px;
	color: var(--text-secondary);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* ── Main content + toolbar + tabs + content area + panels ──────────────── */

.main-content {
	flex: 1;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	background-color: var(--bg-primary);
}

.welcome-screen {
	flex: 1;
	display: flex;
	align-items: center;
	justify-content: center;
}

.welcome-content {
	text-align: center;
	max-width: 500px;
	padding: 40px;
}

.welcome-icon {
	width: 120px;
	height: 120px;
	margin: 0 auto 30px;
	color: var(--accent-green);
	opacity: 0.8;
}

.welcome-content h2 {
	font-size: 28px;
	margin-bottom: 12px;
	color: var(--text-primary);
}

.welcome-content p {
	font-size: 16px;
	color: var(--text-secondary);
	margin-bottom: 30px;
}

.toolbar {
	height: var(--topbar-height);
	display: flex;
	justify-content: space-between;
	align-items: center;
	/* Generous padding so the toolbar-right buttons don't hit the window
	   edge — user reported this repeatedly. 16px top/bottom gives the
	   buttons themselves vertical breathing room too. */
	padding: 12px 32px;
	background-color: var(--bg-secondary);
	border-bottom: 1px solid var(--border-color);
	gap: 16px;
	box-sizing: border-box;
}

.toolbar > .toolbar-right {
	padding-right: 4px;
}

.toolbar h2 {
	font-size: 18px;
	font-weight: 600;
	color: var(--text-primary);
}

.toolbar-right {
	display: flex;
	gap: 12px;
}

/* Main tabs (row of buttons across the top of main content) */

.main-tabs {
	display: flex;
	border-bottom: 1px solid var(--border-color);
	flex-shrink: 0;
	background-color: var(--bg-secondary);
}

.main-tab-btn {
	background: none;
	border: none;
	border-bottom: 2px solid transparent;
	color: var(--text-secondary);
	cursor: pointer;
	padding: 8px 18px;
	font-size: 13px;
	margin-bottom: -1px;
	transition:
		color 0.15s,
		border-color 0.15s;
}

.main-tab-btn:hover {
	color: var(--text-primary);
}

.main-tab-btn.active {
	color: var(--accent-green);
	border-bottom-color: var(--accent-green);
}

.main-tab-panel {
	display: none;
	flex: 1;
	overflow: hidden;
}

.main-tab-panel.active {
	display: flex;
}

.content-area {
	flex: 1;
	display: flex;
	flex-direction: row;
	overflow: hidden;
}

.detail-panel {
	flex-shrink: 0;
	min-width: 200px;
	width: 350px;
}

.panel-divider {
	width: 5px;
	flex-shrink: 0;
	cursor: col-resize;
	background-color: var(--border-color);
	transition: background-color 0.15s ease;
	position: relative;
	z-index: 10;
}

.panel-divider::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: var(--panel-header-height);
	background-color: var(--bg-secondary);
	border-bottom: 1px solid var(--border-color);
	pointer-events: none;
}

.panel-divider:hover,
.panel-divider.dragging {
	background-color: var(--accent-green);
}

.panel-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 12px 16px;
	height: var(--panel-header-height);
	background-color: var(--bg-secondary);
	border-bottom: 1px solid var(--border-color);
}

.panel-header h3 {
	font-size: 13px;
	font-weight: 600;
	text-transform: uppercase;
	color: var(--text-secondary);
	letter-spacing: 0.5px;
}

.panel-header-actions {
	display: flex;
	align-items: center;
	gap: 4px;
}

/* ── Empty state ────────────────────────────────────────────────────────── */

.empty-state {
	padding: 40px 20px;
	text-align: center;
	color: var(--text-secondary);
	font-size: 13px;
}

/* Error modal flavor */

.error-icon {
	width: 24px;
	height: 24px;
	stroke: var(--danger-red);
	flex-shrink: 0;
}

.error-message {
	background-color: var(--bg-primary);
	border: 1px solid var(--border-color);
	border-radius: 4px;
	padding: 16px;
	font-family: var(--font-family-mono, "Consolas", "Monaco", "Courier New", monospace);
	font-size: 13px;
	color: var(--text-primary);
	line-height: 1.6;
	white-space: pre-wrap;
	word-break: break-word;
	max-height: 400px;
	overflow-y: auto;
	user-select: text;
	cursor: text;
}

/* ── Toggle switches ────────────────────────────────────────────────────── */

.toggle-group {
	margin-bottom: 20px;
}

.toggle-label {
	display: flex;
	align-items: center;
	justify-content: space-between;
	cursor: pointer;
	padding: 12px 0;
	transition: opacity 0.2s ease;
}

.toggle-label:hover {
	opacity: 0.9;
}

.toggle-text {
	font-size: 13px;
	color: var(--text-primary);
	flex: 1;
	user-select: none;
	margin-right: 16px;
}

.danger-zone {
	margin-top: 32px;
	padding-top: 24px;
	padding-bottom: 8px;
	border-top: 1px solid var(--border-color);
}

.danger-zone h3 {
	font-size: 14px;
	color: var(--danger-red);
	margin-bottom: 12px;
}

/* Settings modal — give it a stable minimum size so switching tabs
   doesn't make the dialog jump around. */

#settingsModal .modal-content {
	min-height: 480px;
	max-height: 80vh;
	height: 480px;
}

#settingsModal .modal-body {
	overflow: hidden;
	display: flex;
	flex-direction: column;
}

/* ── Progress + spinner + log pane ──────────────────────────────────────── */

.progress-container {
	padding: 12px 0;
}

.progress-label {
	font-size: 11px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--text-secondary);
	margin-bottom: 6px;
}

.progress-fill {
	height: 100%;
	background-color: var(--accent-green);
	transition: width 0.3s ease;
	width: 0%;
}

.progress-text {
	text-align: center;
	font-size: 13px;
	color: var(--text-secondary);
}

.process-spinner {
	width: 15px;
	height: 15px;
	border: 2px solid var(--bg-hover);
	border-top-color: var(--accent-green);
	border-radius: 50%;
	display: inline-block;
	flex-shrink: 0;
	animation: process-spin 0.7s linear infinite;
}

.log-section {
	margin-top: 12px;
	border-top: 1px solid var(--border-color);
	padding-top: 8px;
}

.log-toggle {
	background: none;
	border: none;
	color: var(--text-secondary);
	font-size: 12px;
	cursor: pointer;
	display: flex;
	align-items: center;
	gap: 5px;
	padding: 2px 0;
}

.log-toggle:hover {
	color: var(--text-primary);
}

.log-arrow {
	font-size: 9px;
}

.log-pane {
	display: none;
	margin-top: 8px;
	height: 200px;
	overflow-y: auto;
	background: var(--bg-primary);
	border: 1px solid var(--border-color);
	border-radius: 3px;
	font-family: var(--font-family-mono, "Consolas", "Courier New", monospace);
	font-size: 11px;
	padding: 6px 8px;
}

.log-row {
	display: flex;
	gap: 8px;
	line-height: 1.7;
}

.log-ts {
	color: var(--text-disabled);
	flex-shrink: 0;
	user-select: none;
}

.log-msg {
	color: var(--text-secondary);
	word-break: break-all;
}

.log--warn .log-msg {
	color: var(--warning-yellow);
}

.log--error .log-msg {
	color: var(--danger-red);
}

/* Custom number stepper (wrap an <input type="number"> in .num-stepper) */

.num-stepper {
	display: inline-flex;
	align-items: stretch;
	border: 1px solid var(--border-color);
	border-radius: 4px;
	background-color: var(--bg-primary);
	overflow: hidden;
}

.num-stepper input[type="number"] {
	border: none;
	background: transparent;
	color: var(--text-primary);
	font-size: 13px;
	padding: 8px 10px;
	width: 80px;
	text-align: center;
	outline: none;
}

.num-stepper input[type="number"]:focus {
	outline: 2px solid var(--accent-green);
	outline-offset: -2px;
}

.num-stepper-btn {
	width: 24px;
	background: var(--bg-tertiary);
	border: none;
	border-left: 1px solid var(--border-color);
	color: var(--text-secondary);
	cursor: pointer;
	font-size: 12px;
	display: flex;
	align-items: center;
	justify-content: center;
	user-select: none;
}

.num-stepper-btn:first-child {
	border-left: none;
	border-right: 1px solid var(--border-color);
}

.num-stepper-btn:hover {
	background-color: var(--selection-accent-bg);
	color: var(--selection-accent-text);
}

.slider-row {
	display: flex;
	align-items: center;
	gap: 12px;
}

.slider-row input[type="range"] {
	flex: 1;
}

.slider-value {
	min-width: 48px;
	text-align: right;
	color: var(--text-secondary);
	font-variant-numeric: tabular-nums;
	font-size: 12px;
}


/* ╭─ source: api/ui/app-bar/app-bar.css ─╮ */
/*
 * app-bar primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ── Titlebar (40px, drag region, menubar, window controls) ─────────────── */

#app-titlebar,
.app-titlebar,
.hui-app-bar {
	height: 40px;
	flex-shrink: 0;
	/* Three-cluster grid: [left: 1fr][centre: auto][right: 1fr]. Side
	 * columns get equal-fr shares so the centre column ends up TRULY
	 * centred on the bar regardless of left/right content widths.
	 * Earlier version used flex `justify-content: space-between` which
	 * centred the search between left/right's INNER edges — fine when
	 * those clusters were similar widths, drifted noticeably off-centre
	 * once the i18n fix made left short (brand+menus shrunk to the
	 * resolved translations instead of long raw keys). */
	display: grid;
	grid-template-columns: 1fr auto 1fr;
	align-items: center;
	background-color: var(--bg-secondary);
	border-bottom: 1px solid var(--border-color);
	-webkit-app-region: drag;
	user-select: none;
	flex: 1 1 auto;
	min-width: 0;
	/* Clip side-cluster overflow at the bar edge — never let the right
	 * cluster (incl. the close button) get pushed past the bar's right
	 * edge by a too-wide left cluster or a long search input. */
	overflow: hidden;
}

/* Left + right clusters group their children so each cluster reads as a
 * single right-pinned / left-pinned unit. Cluster wrappers are draggable
 * (inherit from .hui-app-bar) — their interactive children
 * (.app-titlebar-brand--clickable, .app-menu-btn, .app-titlebar-right's
 * widgets, windowControls' buttons) opt out via -webkit-app-region: no-drag. */

.app-titlebar-section {
	display: flex;
	align-items: stretch;
	height: 100%;
	min-width: 0;
}

.app-titlebar-section--left {
	grid-column: 1;
	justify-self: start;
	min-width: 0; /* allow the brand to shrink/ellipsize instead of stacking the bar */
}

.app-titlebar-section--right {
	grid-column: 3;
	/* justify-self: end pins the cluster's right edge to the bar's right
	 * edge regardless of cell width. If the cluster is wider than its
	 * 1fr cell, the cluster extends LEFT (overflowing into the centre
	 * column visually) — but the close × stays anchored to bar right
	 * where it belongs. The bar's overflow:hidden clips anything that
	 * extends past the LEFT edge of the bar (left cluster overgrowth). */
	justify-self: end;
}

/* Legacy: pre-migration host apps that drop content directly under
 * `<div id="app-titlebar">` (no `.app-titlebar-brand` wrapper) relied on
 * the bar itself supplying the 12px gutter. Preserved for those callers. */

#app-titlebar {
	padding-left: 12px;
}

#app-titlebar.menubar-open,
.app-titlebar.menubar-open,
.hui-app-bar.menubar-open,
[data-slot="app-bar"].menubar-open {
	-webkit-app-region: no-drag;
}

.app-titlebar-brand {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0 12px 0 7px;
	min-width: 0; /* let the brand shrink so the name can ellipsize instead of stacking */
	overflow: hidden;
	/* Default: brand is part of the drag region — clicking + dragging the
	 * logo or app name moves the window (matches the OS title-bar UX where
	 * the app icon area is draggable chrome). Apps that wire `brandOnClick`
	 * get `--clickable` added by the primitive so the click handler fires
	 * (drag regions are non-client at the OS level — they don't deliver
	 * click events). */
}

.app-titlebar-brand--clickable {
	-webkit-app-region: no-drag;
	cursor: pointer;
}

.app-titlebar-icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	color: var(--accent-green);
	flex: 0 0 auto;
}

.app-titlebar-icon svg {
	width: 36px;
	height: 36px;
}

/* Search sits as the middle direct child of the appBar wrap; flex:0 1
 * clamp(...) lets it grow up to its preferred width and shrink down to
 * zero when narrow bars need the room. justify-content: space-between on
 * the wrap places it visually centred between the left + right clusters. */

.app-titlebar-search {
	-webkit-app-region: no-drag;
	/* `.hui-app-bar` is `display: grid` now (1fr/auto/1fr) — flex sizing
	 * doesn't apply, so `flex: 0 1 clamp(...)` was inert. Use explicit
	 * width with a clamp so search is comfortably readable but doesn't
	 * dominate the bar at common window widths. */
	width: clamp(210px, 28vw, 345px);
	max-width: 100%;
	min-width: 0;
	display: flex;
	align-items: center;
	gap: 6px;
	height: 26px;
	padding: 0 8px 0 8px;
	background: var(--bg-primary);
	border: 1px solid var(--border-color);
	border-radius: 4px;
	color: var(--text-secondary);
	transition: border-color 80ms ease;
}

.app-titlebar-search:focus-within {
	border-color: var(--accent-green);
}

.app-titlebar-search-icon {
	display: inline-flex;
	align-items: center;
}

.app-titlebar-search-icon svg {
	width: 14px;
	height: 14px;
}

.app-titlebar-search input {
	flex: 1 1 auto;
	background: transparent;
	border: none;
	outline: none;
	color: var(--text-primary);
	font-size: 12px;
	min-width: 0;
}

.app-titlebar-search input::placeholder {
	color: var(--text-secondary);
}

.app-titlebar-name {
	font-size: 13px;
	font-weight: 600;
	color: var(--accent-green);
	letter-spacing: 0.3px;
	/* Single line — never wrap/stack the app-bar; truncate with an ellipsis when
	 * the bar is narrow (e.g. once the menus collapse to a hamburger). */
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	min-width: 0;
}

/* Optional right-slot widgets (locale picker, profile chip, status
 * indicator…) — appended inside .app-titlebar-section--right, immediately
 * before windowControls. No margin: the parent cluster is already pinned
 * right by the space-between layout. */

.app-titlebar-right {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0 8px;
	-webkit-app-region: no-drag;
	font-size: 12px;
	color: var(--text-secondary);
}


/* ╭─ source: api/ui/badge/badge.css ─╮ */
/*
 * badge primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── badge ──────────────────────────────────────────────────────────────── */

.hui-badge {
	display: inline-flex;
	align-items: center;
	font-size: 11px;
	font-weight: 500;
	padding: 1px 6px;
	border-radius: 9px;
	line-height: 1.6;
}

.hui-badge--neutral {
	background: var(--bg-tertiary);
	color: var(--text-secondary);
}

.hui-badge--success {
	background: rgba(152, 195, 121, 0.2);
	color: var(--accent-green);
}

.hui-badge--warn {
	background: rgba(204, 167, 0, 0.2);
	color: var(--warning-yellow);
}

.hui-badge--error {
	background: rgba(244, 67, 54, 0.2);
	color: var(--danger-red);
}

.hui-badge--info {
	background: rgba(80, 160, 220, 0.2);
	color: #5fb3eb;
}


/* ╭─ source: api/ui/button/button.css ─╮ */
/*
 * button primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── button ─────────────────────────────────────────────────────────────── */

.hui-button {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	border: 1px solid transparent;
	border-radius: var(--radius-sm);
	padding: 6px 12px;
	font: inherit;
	font-size: 12px;
	cursor: pointer;
	user-select: none;
	background: transparent;
	color: var(--text-primary);
	transition:
		background-color 0.12s ease,
		border-color 0.12s ease,
		color 0.12s ease;
}

.hui-button[disabled] {
	opacity: 0.45;
	cursor: not-allowed;
}

.hui-button--primary {
	background: var(--btn-primary-bg);
	color: var(--bg-primary);
	border-color: var(--btn-primary-bg);
}

.hui-button--primary:hover:not([disabled]) {
	background: var(--btn-primary-bg-hover);
	border-color: var(--btn-primary-bg-hover);
}

.hui-button--secondary {
	background: var(--bg-tertiary);
	border-color: var(--border-color);
}

.hui-button--secondary:hover:not([disabled]) {
	background: var(--bg-hover);
}

.hui-button--ghost {
	background: transparent;
	border-color: transparent;
	color: var(--text-secondary);
}

.hui-button--ghost:hover:not([disabled]) {
	background: var(--bg-hover);
	color: var(--text-primary);
}

.hui-button--danger {
	background: var(--danger-red);
	color: #fff;
	border-color: var(--danger-red);
}

.hui-button--danger:hover:not([disabled]) {
	background: var(--danger-red-hover);
	border-color: var(--danger-red-hover);
}

/* Success + warning round out the standard severity set so every demo /
 * consumer reach for the right semantic colour without falling back to
 * the legacy .btn-success / .btn-warning classes. */

.hui-button--success {
	background: var(--accent-green);
	color: var(--bg-primary);
	border-color: var(--accent-green);
}

.hui-button--success:hover:not([disabled]) {
	filter: brightness(1.08);
}

.hui-button--warning {
	background: #d59c11;
	color: var(--bg-primary);
	border-color: #d59c11;
}

.hui-button--warning:hover:not([disabled]) {
	filter: brightness(1.08);
}

/* Size modifiers — md is the default (no class). sm trims the padding
 * + font; lg bumps both. Useful for action toolbars (sm) and primary
 * call-to-action buttons (lg). */

.hui-button--sm {
	padding: 4px 10px;
	font-size: 11px;
}

.hui-button--lg {
	padding: 9px 18px;
	font-size: 13px;
}

/* Icon-only variant — the label span is omitted by the primitive when
 * iconOnly:true is passed; this rule trims horizontal padding so the
 * icon alone gets a square hit-target. aria-label is required on the
 * primitive side; the primitive forwards it for accessibility. */

.hui-button--icon-only {
	padding: 6px 8px;
}

.hui-button__icon {
	display: inline-flex;
	width: 14px;
	height: 14px;
	align-items: center;
	justify-content: center;
}

.hui-button__icon svg {
	width: 100%;
	height: 100%;
}

/* Disabled-by-`requires` state. Two visual modes:
   • `[disabled]` (no requiresHelp) — fully inert; relies on the browser's
     default disabled styling.
   • `[aria-disabled="true"]` (with requiresHelp) — looks disabled but is
     still clickable so the user can be guided to install the missing dep. */

.hui-button--requires-missing {
	opacity: 0.55;
	cursor: not-allowed;
}

.hui-button--requires-missing[aria-disabled="true"]:not([disabled]) {
	cursor: help;
}

.hui-button--requires-missing[aria-disabled="true"]:not([disabled]):hover {
	opacity: 0.75;
}


/* ╭─ source: api/ui/card/card.css ─╮ */
/*
 * card primitive — styles (co-located; concatenated by `rummage styles`).
 */

/*
 * @cldmv/rummage — UI primitive styles
 *
 * Styles for the built-in primitives in /src/ui/primitives.mjs. BEM-style
 * classes prefixed with `hui-` ("host-ui-") so they're easy to distinguish
 * from extension and app CSS. All colors come from /src/styles/tokens.css —
 * an app or theme extension can re-skin by overriding tokens upstream.
 *
 * Class index:
 *   .hui-card / .hui-card__title / .hui-card__body / .hui-card__actions
 *   .hui-card--default / --info / --warn / --error
 *   .hui-button / .hui-button__icon / .hui-button__label
 *   .hui-button--primary / --secondary / --ghost / --danger
 *   .hui-form-row / .hui-form-row__label / .hui-form-row__required
 *   .hui-form-row__control / .hui-form-row__hint
 *   .hui-badge / .hui-badge--success / --warn / --error / --info / --neutral
 *   .hui-list / .hui-list--default / --compact / --rows
 *   .hui-list-item / .hui-list-item__icon / .hui-list-item__text
 *   .hui-list-item__primary / .hui-list-item__secondary / .hui-list-item__actions
 *   .hui-link
 *   .hui-divider
 *   .hui-empty-state / .hui-empty-state__icon / .hui-empty-state__title
 *   .hui-empty-state__body / .hui-empty-state__action
 *   .hui-spinner / .hui-spinner__ring / .hui-spinner__label
 *   .hui-spinner--sm / --md / --lg
 *   .hui-dialog-header / __text / __title / __subtitle / __close
 *   .hui-segmented / .hui-segmented__option / --selected
 *   .hui-tabs / .hui-tabs__bar / .hui-tabs__tab / --selected / .hui-tabs__panel
 *   .hui-kbd / .hui-kbd__key / .hui-kbd__plus
 *   .hui-code
 *   .hui-stat-row / __label / __value / __hint
 *   .hui-status-dot / --ok / --warn / --error / --unknown / --info
 *   .hui-progress / __row / __label / __percent / __track / __fill
 *   .hui-chip-group / .hui-chip / .hui-chip__label / .hui-chip__remove
 *   .hui-chip--neutral / --success / --warn / --error / --info
 *   .hui-icon / .hui-icon--text
 */

/* ─── card ───────────────────────────────────────────────────────────────── */

.hui-card {
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: var(--radius-md);
	padding: 12px 14px;
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.hui-card--info {
	border-left: 3px solid var(--accent-green);
}

.hui-card--warn {
	border-left: 3px solid var(--warning-yellow);
}

.hui-card--error {
	border-left: 3px solid var(--danger-red);
}

.hui-card__title {
	font-weight: 600;
	color: var(--text-primary);
	font-size: 13px;
}

.hui-card__body {
	color: var(--text-secondary);
	font-size: 12px;
}

.hui-card__actions {
	display: flex;
	gap: 8px;
	justify-content: flex-end;
	margin-top: 4px;
}


/* ╭─ source: api/ui/chip-group/chip-group.css ─╮ */
/*
 * chip-group primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── chips ──────────────────────────────────────────────────────────────── */

.hui-chip-group {
	display: flex;
	flex-wrap: wrap;
	gap: 4px;
}

.hui-chip {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	font-size: 11px;
	padding: 2px 6px;
	border-radius: 10px;
	background: var(--bg-tertiary);
	color: var(--text-secondary);
	border: 1px solid var(--border-color);
}

.hui-chip--success {
	background: rgba(152, 195, 121, 0.15);
	color: var(--accent-green);
	border-color: rgba(152, 195, 121, 0.3);
}

.hui-chip--warn {
	background: rgba(204, 167, 0, 0.15);
	color: var(--warning-yellow);
	border-color: rgba(204, 167, 0, 0.3);
}

.hui-chip--error {
	background: rgba(244, 67, 54, 0.15);
	color: var(--danger-red);
	border-color: rgba(244, 67, 54, 0.3);
}

.hui-chip--info {
	background: rgba(80, 160, 220, 0.15);
	color: #5fb3eb;
	border-color: rgba(80, 160, 220, 0.3);
}

.hui-chip__remove {
	background: transparent;
	border: 0;
	color: inherit;
	cursor: pointer;
	font-size: 10px;
	padding: 0 2px;
	border-radius: 8px;
	opacity: 0.7;
}

.hui-chip__remove:hover {
	opacity: 1;
}


/* ╭─ source: api/ui/circular-fill/circular-fill.css ─╮ */
/* Circular fill / ring gauge (ui.circularFill). A conic-gradient fills to --hui-ring-pct; a radial mask
   punches the center to a donut; the label sits on top (unmasked). @property makes the % animate. */
@property --hui-ring-pct {
	syntax: "<number>";
	initial-value: 0;
	inherits: true;
}

.hui-ring {
	--hui-ring-size: 44px;
	--hui-ring-thick: 5px;
	--hui-ring-pct: 0;
	--hui-ring-color: var(--accent, #98c379);
	/* Track = the EMPTY portion. Use a visible divider tone (not bg-tertiary, which is too low-contrast on
	   a dark toolbar) so an empty (0%) ring still reads as present before it's filled. */
	--hui-ring-track: var(--border-color, rgba(255, 255, 255, 0.22));
	position: relative;
	flex: 0 0 auto;
	width: var(--hui-ring-size);
	height: var(--hui-ring-size);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	transition: --hui-ring-pct 0.4s ease;
}

.hui-ring__arc {
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background: conic-gradient(var(--hui-ring-color) calc(var(--hui-ring-pct) * 1%), var(--hui-ring-track) 0);
	-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--hui-ring-thick)), #000 calc(100% - var(--hui-ring-thick)));
	mask: radial-gradient(farthest-side, transparent calc(100% - var(--hui-ring-thick)), #000 calc(100% - var(--hui-ring-thick)));
}

.hui-ring__label {
	position: relative;
	z-index: 1;
	font-size: calc(var(--hui-ring-size) * 0.3);
	font-weight: 600;
	line-height: 1;
	color: var(--text-primary, #d4d4d4);
	font-variant-numeric: tabular-nums;
	pointer-events: none;
}


/* ╭─ source: api/ui/code/code.css ─╮ */
/*
 * code primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── code ───────────────────────────────────────────────────────────────── */

.hui-code {
	font-family: var(--font-family-mono);
	font-size: 12px;
	background: var(--bg-tertiary);
	padding: 1px 5px;
	border-radius: 3px;
	color: var(--text-primary);
	border: 1px solid var(--border-color);
}


/* ╭─ source: api/ui/dialog-header/dialog-header.css ─╮ */
/*
 * dialog-header primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── dialog header ──────────────────────────────────────────────────────── */

.hui-dialog-header {
	display: flex;
	align-items: flex-start;
	gap: 12px;
	padding: 12px 14px;
	border-bottom: 1px solid var(--divider-color);
}

.hui-dialog-header__text {
	flex: 1 1 auto;
}

.hui-dialog-header__title {
	font-weight: 600;
	color: var(--text-primary);
}

.hui-dialog-header__subtitle {
	font-size: 12px;
	color: var(--text-muted);
	margin-top: 2px;
}

.hui-dialog-header__close {
	background: transparent;
	border: 0;
	color: var(--text-secondary);
	cursor: pointer;
	font-size: 14px;
	padding: 2px 6px;
	border-radius: var(--radius-sm);
}

.hui-dialog-header__close:hover {
	background: var(--bg-hover);
	color: var(--text-primary);
}


/* ╭─ source: api/ui/divider/divider.css ─╮ */
/*
 * divider primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── divider ────────────────────────────────────────────────────────────── */

.hui-divider {
	border: 0;
	border-top: 1px solid var(--divider-color);
	margin: 8px 0;
}


/* ╭─ source: api/ui/editable-list/editable-list.css ─╮ */
/*
 * editable-list primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── editable list ─────────────────────────────────────────────────────── */

.hui-editable-list {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.hui-editable-list__input-row {
	display: flex;
	gap: 6px;
	align-items: stretch;
}

.hui-editable-list__input {
	flex: 1 1 auto;
	min-width: 0;
	background: var(--bg-tertiary);
	color: var(--text-primary);
	border: 1px solid var(--border-color);
	border-radius: var(--radius-sm);
	padding: 6px 10px;
	font: inherit;
	font-size: 12px;
}

.hui-editable-list__input:focus {
	outline: none;
	border-color: var(--accent-green);
}

.hui-editable-list__input--invalid {
	border-color: var(--danger-red);
	animation: hui-shake 0.4s ease;
}

.hui-editable-list__list {
	list-style: none;
	margin: 0;
	padding: 0;
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: var(--radius-sm);
	max-height: 320px;
	overflow: auto;
}

.hui-editable-list__row {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 5px 10px;
	border-bottom: 1px solid var(--divider-color);
	font-size: 12px;
	color: var(--text-primary);
}

.hui-editable-list__row:last-child {
	border-bottom: 0;
}

.hui-editable-list__row-body {
	flex: 1 1 auto;
	min-width: 0;
	word-break: break-all;
}

.hui-editable-list__remove {
	flex: 0 0 auto;
	background: transparent;
	border: 0;
	color: var(--text-muted);
	cursor: pointer;
	font-size: 12px;
	padding: 2px 6px;
	border-radius: var(--radius-sm);
	transition:
		background-color 0.12s ease,
		color 0.12s ease;
}

.hui-editable-list__remove:hover {
	background: var(--bg-hover);
	color: var(--danger-red);
}

.hui-editable-list__empty {
	padding: 12px;
	color: var(--text-muted);
	font-size: 12px;
	font-style: italic;
	text-align: center;
}


/* ╭─ source: api/ui/empty-state/empty-state.css ─╮ */
/*
 * empty-state primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── empty state ────────────────────────────────────────────────────────── */

.hui-empty-state {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 8px;
	text-align: center;
	padding: 24px 20px;
	color: var(--text-muted);
}

.hui-empty-state__icon {
	width: 32px;
	height: 32px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: var(--text-disabled);
}

.hui-empty-state__icon svg {
	width: 100%;
	height: 100%;
}

.hui-empty-state__title {
	font-size: 13px;
	color: var(--text-secondary);
	font-weight: 500;
}

.hui-empty-state__body {
	font-size: 12px;
	color: var(--text-muted);
}

.hui-empty-state__action {
	margin-top: 4px;
}


/* ╭─ source: api/ui/file-picker/file-picker.css ─╮ */
/*
 * file-picker primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── file picker (input + Browse button) ───────────────────────────────── */

.hui-file-picker {
	display: flex;
	gap: 6px;
	align-items: stretch;
	min-width: 0;
}

.hui-file-picker__input {
	flex: 1 1 auto;
	min-width: 0;
	background: var(--bg-tertiary);
	color: var(--text-primary);
	border: 1px solid var(--border-color);
	border-radius: var(--radius-sm);
	padding: 6px 10px;
	font: inherit;
	font-size: 12px;
}

.hui-file-picker__input:focus {
	outline: none;
	border-color: var(--accent-green);
}


/* ╭─ source: api/ui/form-grid/form-grid.css ─╮ */
/*
 * form-grid primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── form grid (multiple labeled controls per row) ─────────────────────── */

.hui-form-grid {
	display: grid;
	gap: 10px;
	margin: 0 0 8px 0;
}

.hui-form-grid__cell {
	display: flex;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
}

.hui-form-grid__label {
	font-size: 12px;
	color: var(--text-secondary);
}

.hui-form-grid__required {
	color: var(--danger-red);
}

.hui-form-grid__control {
	display: flex;
	flex-direction: column;
	min-width: 0;
}

.hui-form-grid__hint {
	font-size: 11px;
	color: var(--text-muted);
}


/* ╭─ source: api/ui/form-row/form-row.css ─╮ */
/*
 * form-row primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── form row ───────────────────────────────────────────────────────────── */

.hui-form-row {
	display: flex;
	flex-direction: column;
	gap: 4px;
	margin: 0 0 8px 0;
}

.hui-form-row__label {
	font-size: 12px;
	color: var(--text-secondary);
}

.hui-form-row__required {
	color: var(--danger-red);
}

.hui-form-row__control {
	display: flex;
	flex-direction: column;
}

.hui-form-row__hint {
	font-size: 11px;
	color: var(--text-muted);
}

/* Baseline input/select/textarea styling inside form rows + grids — apps that
 * pass raw <input> nodes shouldn't have to remember a packrat-specific class
 * to get the host's themed control look. Mirrors the .form-group rules in
 * base.css but scoped to anything the host's form primitives wrap. */

.hui-form-row__control input[type="text"],
.hui-form-row__control input[type="number"],
.hui-form-row__control input[type="password"],
.hui-form-row__control input[type="email"],
.hui-form-row__control input[type="search"],
.hui-form-row__control input[type="url"],
.hui-form-row__control input:not([type]),
.hui-form-row__control select,
.hui-form-row__control textarea,
.hui-form-grid__control input[type="text"],
.hui-form-grid__control input[type="number"],
.hui-form-grid__control input[type="password"],
.hui-form-grid__control input[type="email"],
.hui-form-grid__control input[type="search"],
.hui-form-grid__control input[type="url"],
.hui-form-grid__control input:not([type]),
.hui-form-grid__control select,
.hui-form-grid__control textarea {
	width: 100%;
	padding: 5px 8px;
	background: var(--bg-tertiary, var(--bg-secondary));
	border: 1px solid var(--border-color);
	border-radius: 3px;
	color: var(--text-primary);
	font: inherit;
	font-size: 12px;
	box-sizing: border-box;
}

/* Hover hint — same green-border affordance as focus so the user sees the
 * field is interactive before they click. Focus then doubles down with an
 * outline-feeling glow. */

.hui-form-row__control input:hover:not(:disabled),
.hui-form-row__control select:hover:not(:disabled),
.hui-form-row__control textarea:hover:not(:disabled),
.hui-form-grid__control input:hover:not(:disabled),
.hui-form-grid__control select:hover:not(:disabled),
.hui-form-grid__control textarea:hover:not(:disabled) {
	border-color: color-mix(in srgb, var(--accent-green) 50%, var(--border-color));
}

.hui-form-row__control input:focus,
.hui-form-row__control select:focus,
.hui-form-row__control textarea:focus,
.hui-form-grid__control input:focus,
.hui-form-grid__control select:focus,
.hui-form-grid__control textarea:focus {
	outline: none;
	border-color: var(--accent-green);
	box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-green) 25%, transparent);
}

.hui-form-row__control input::placeholder,
.hui-form-grid__control input::placeholder,
.hui-form-row__control textarea::placeholder,
.hui-form-grid__control textarea::placeholder {
	color: var(--text-muted);
}

/* Inline-intrinsic controls inside a form-row / form-grid control cell.
 * The control cell is a stretch flex column, which forces a lone child to
 * full width — wrong for controls that have a natural compact size. Pin
 * them to flex-start so a numStepper keeps its ~128px intrinsic width, a
 * segmented keeps its inline pill width, and a position-picker keeps its
 * grid size. Sliders/toggles/raw inputs are intentionally NOT listed here:
 * they want the full row width. Generalizes the demo-only
 * `.splash-preview-field > .hui-*` rescue into the host primitive so
 * consumers don't need an app-level override. */

.hui-form-row__control > .hui-num-stepper,
.hui-form-row__control > .hui-segmented,
.hui-form-row__control > .hui-position-picker,
.hui-form-grid__control > .hui-num-stepper,
.hui-form-grid__control > .hui-segmented,
.hui-form-grid__control > .hui-position-picker {
	align-self: flex-start;
}


/* ╭─ source: api/ui/kbd/kbd.css ─╮ */
/*
 * kbd primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── kbd ────────────────────────────────────────────────────────────────── */

.hui-kbd {
	display: inline-flex;
	align-items: center;
	gap: 2px;
}

.hui-kbd__key {
	display: inline-block;
	background: var(--bg-tertiary);
	border: 1px solid var(--border-color);
	border-bottom-width: 2px;
	border-radius: 3px;
	padding: 0 4px;
	font-family: var(--font-family-mono);
	font-size: 11px;
	line-height: 1.5;
	color: var(--text-primary);
}

.hui-kbd__plus {
	color: var(--text-muted);
	font-size: 11px;
	padding: 0 1px;
}


/* ╭─ source: api/ui/link/link.css ─╮ */
/*
 * link primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── link ───────────────────────────────────────────────────────────────── */

.hui-link {
	color: var(--accent-small);
	text-decoration: none;
	cursor: pointer;
}

.hui-link:hover {
	color: var(--accent-small-hover);
	text-decoration: underline;
}


/* ╭─ source: api/ui/list/list.css ─╮ */
/*
 * list primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── list / list item ───────────────────────────────────────────────────── */

.hui-list {
	display: flex;
	flex-direction: column;
}

.hui-list--default {
	gap: 6px;
}

.hui-list--compact {
	gap: 2px;
}

.hui-list--rows {
	gap: 0;
}

.hui-list--rows .hui-list-item {
	border-bottom: 1px solid var(--divider-color);
	border-radius: 0;
}


/* ╭─ source: api/ui/list-item/list-item.css ─╮ */
/*
 * list-item primitive — styles (co-located; concatenated by `rummage styles`).
 */

.hui-list-item {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 6px 10px;
	border-radius: var(--radius-sm);
	background: transparent;
	color: var(--text-primary);
	font: inherit;
	font-size: 12px;
	text-align: left;
	border: 0;
	width: 100%;
}
button.hui-list-item {
	cursor: pointer;
}
button.hui-list-item:hover {
	background: var(--bg-hover);
}
.hui-list-item__icon {
	flex: 0 0 auto;
	display: inline-flex;
	width: 18px;
	height: 18px;
	align-items: center;
	justify-content: center;
	color: var(--text-secondary);
}
.hui-list-item__text {
	flex: 1 1 auto;
	min-width: 0;
}
.hui-list-item__primary {
	font-size: 12px;
	color: var(--text-primary);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.hui-list-item__secondary {
	font-size: 11px;
	color: var(--text-muted);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.hui-list-item__actions {
	flex: 0 0 auto;
	display: flex;
	gap: 4px;
}


/* ╭─ source: api/ui/num-stepper/num-stepper.css ─╮ */
/*
 * num-stepper primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── numStepper ─────────────────────────────────────────────────────────── */

/* Numeric input flanked by - / + buttons. Same visual treatment as the
 * legacy .num-stepper / .num-stepper-btn pair (now quarantined in legacy.css) but
 * scoped to the primitive's class so consumers don't depend on the
 * legacy markup. */

.hui-num-stepper {
	display: inline-flex;
	align-items: stretch;
	border: 1px solid var(--border-color);
	border-radius: 4px;
	background-color: var(--bg-primary);
	overflow: hidden;
}

/* Specificity bump (input.hui-num-stepper__input[type="number"] = 0,3,1)
 * needed to beat `.form-group input[type="number"]` (0,2,1) which lives
 * in base.css and sets the wrong width/padding/border for our
 * stepper. The legacy .num-stepper used `.num-stepper input[type="number"]`
 * (0,2,1) + later-source-order to win; we get the same outcome via
 * higher specificity so context (in or out of a .form-group) doesn't
 * matter. */

.hui-num-stepper > input.hui-num-stepper__input[type="number"] {
	border: none;
	background: transparent;
	color: var(--text-primary);
	font-size: 13px;
	padding: 8px 10px;
	width: 80px;
	text-align: center;
	outline: none;
}

.hui-num-stepper > input.hui-num-stepper__input[type="number"]:focus {
	outline: 2px solid var(--accent-green);
	outline-offset: -2px;
	border-color: transparent;
}

.hui-num-stepper > input.hui-num-stepper__input[type="number"]::-webkit-outer-spin-button,
.hui-num-stepper > input.hui-num-stepper__input[type="number"]::-webkit-inner-spin-button {
	-webkit-appearance: none;
	margin: 0;
}

.hui-num-stepper__btn {
	width: 24px;
	background: var(--bg-tertiary);
	border: none;
	border-left: 1px solid var(--border-color);
	color: var(--text-secondary);
	cursor: pointer;
	font-size: 12px;
	display: flex;
	align-items: center;
	justify-content: center;
	user-select: none;
}

.hui-num-stepper__btn:first-child {
	border-left: none;
	border-right: 1px solid var(--border-color);
}

.hui-num-stepper__btn:hover:not([disabled]) {
	background-color: var(--selection-accent-bg);
	color: var(--selection-accent-text);
}

.hui-num-stepper__btn[disabled] {
	opacity: 0.45;
	cursor: not-allowed;
}


/* ╭─ source: api/ui/popover/popover.css ─╮ */
/*
 * popover primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── popover ────────────────────────────────────────────────────────────── */

/* Anchored popover surface — used for "click to expand" details that need
 * richer content than a tooltip but don't warrant a full modal. Visual
 * matches the notification-center vibe: rounded card, slight shadow,
 * dark surface, scrollable body if content overflows. */

.hui-popover {
	position: fixed;
	z-index: 8000;
	min-width: 240px;
	max-width: 420px;
	max-height: 60vh;
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: var(--radius-md, 8px);
	box-shadow:
		0 12px 28px rgba(0, 0, 0, 0.45),
		0 2px 4px rgba(0, 0, 0, 0.35);
	overflow: hidden;
	display: flex;
	flex-direction: column;
}

.hui-popover__content {
	display: flex;
	flex-direction: column;
	min-height: 0;
	overflow: auto;
	scrollbar-gutter: stable;
}


/* ╭─ source: api/ui/position-picker/position-picker.css ─╮ */
/*
 * position-picker primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── position picker (3×N cell grid) ────────────────────────────────────── */

/* Inline grid of small square buttons for picking a [col, row] cell.
 * Cell-grid dimensions are set inline by the primitive based on cols/rows. */

.hui-position-picker {
	display: inline-grid;
	gap: 3px;
}

.hui-position-picker > button {
	background: var(--bg-tertiary, #222);
	border: 1px solid var(--border-color);
	cursor: pointer;
	padding: 0;
	border-radius: 2px;
}

.hui-position-picker > button.is-active {
	background: var(--accent-green);
}

.hui-position-picker > button:disabled {
	cursor: not-allowed;
	opacity: 0.35;
}


/* ╭─ source: api/ui/progress-bar/progress-bar.css ─╮ */
/*
 * progress-bar primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── progress bar ───────────────────────────────────────────────────────── */

.hui-progress {
	display: flex;
	flex-direction: column;
	gap: 4px;
}

.hui-progress__row {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	font-size: 11px;
	color: var(--text-secondary);
}

.hui-progress__label {
}

.hui-progress__percent {
	color: var(--text-muted);
	font-family: var(--font-family-mono);
}

.hui-progress__track {
	background: var(--bg-tertiary);
	border-radius: 4px;
	height: 6px;
	overflow: hidden;
	border: 1px solid var(--border-color);
}

.hui-progress__fill {
	background: var(--accent-green);
	height: 100%;
	transition:
		width 0.2s ease,
		height 0.2s ease;
}

/* Vertical bars: track grows tall, fill keys to height; default anchors
   the fill to the bottom of the track (rising meter), invert anchors to
   the top (draining meter). */

.hui-progress--vertical {
	display: inline-flex;
	flex-direction: column;
	gap: 4px;
}

.hui-progress--vertical .hui-progress__track {
	position: relative;
	height: 100%;
	width: 6px;
	min-height: 40px;
}

.hui-progress--vertical .hui-progress__fill {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 0;
	width: auto;
}

.hui-progress--vertical.hui-progress--invert .hui-progress__fill {
	bottom: auto;
	top: 0;
}

/* Inverted horizontal bars grow right-to-left. The fill anchors to the
   right edge of the track instead of the left. */

.hui-progress:not(.hui-progress--vertical) .hui-progress__track {
	position: relative;
}

.hui-progress:not(.hui-progress--vertical) .hui-progress__fill {
	position: absolute;
	left: 0;
	top: 0;
	bottom: 0;
}

.hui-progress--invert:not(.hui-progress--vertical) .hui-progress__fill {
	left: auto;
	right: 0;
}


/* ╭─ source: api/ui/segmented/segmented.css ─╮ */
/*
 * segmented primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── segmented ──────────────────────────────────────────────────────────── */

.hui-segmented {
	display: inline-flex;
	border: 1px solid var(--border-color);
	border-radius: var(--radius-sm);
	overflow: hidden;
	background: var(--bg-tertiary);
}

.hui-segmented__option {
	background: transparent;
	color: var(--text-secondary);
	border: 0;
	padding: 4px 12px;
	font: inherit;
	font-size: 12px;
	cursor: pointer;
	border-right: 1px solid var(--border-color);
}

.hui-segmented__option:last-child {
	border-right: 0;
}

.hui-segmented__option:hover {
	background: var(--bg-hover);
	color: var(--text-primary);
}

.hui-segmented__option--selected {
	background: var(--accent-green);
	color: var(--bg-primary);
}

/* Explicitly re-state the dark text colour on hover too — without this, the
 * earlier `.hui-segmented__option:hover` rule's `color: var(--text-primary)`
 * wins (same specificity, defined first → loses), but for SELECTED options the
 * background stays green-on-green-hover and the light text becomes invisible.
 * The fix: pin `color` to bg-primary so the contrast holds in every state. */

.hui-segmented__option--selected:hover {
	background: var(--accent-green-hover);
	color: var(--bg-primary);
}


/* ╭─ source: api/ui/simple-accordion/simple-accordion.css ─╮ */
/*
 * simple-accordion primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── simpleAccordion ───────────────────────────────────────────────────────
 * Stacked collapsible sections with optional drag-to-reorder. NOT the
 * accordion extension (which is the rich split-view system the shell uses
 * for side-panel panes — keep these separate). */

/* Standard accordion visual treatment: panes are FLUSH (no gap), share a
 * single outer border + only the first/last get rounded corners, and a
 * 1px divider sits between adjacent panes. Matches the look users
 * expect from "an accordion" (vs. the previous spaced + individually-
 * rounded cards which looked like a list, not a collapsible group). */

.hui-simple-accordion {
	display: flex;
	flex-direction: column;
	min-height: 0;
	/* Fill the parent slot in both flex- and block-layout parents so the
	 * last expanded pane reaches the bottom edge instead of leaving a gap. */
	flex: 1 1 auto;
	height: 100%;
	border: 1px solid var(--border-color);
	border-radius: var(--radius-sm);
	overflow: hidden;
	background: var(--bg-secondary);
}

.hui-simple-accordion__pane {
	display: flex;
	flex-direction: column;
	min-height: 0;
	background: var(--bg-secondary);
	position: relative; /* drop-indicator pseudo-elements anchor here */
}

/* Divider between adjacent panes — match the outer border color so the
 * line reads as a single continuous frame edge, not a thinner inner rule. */

.hui-simple-accordion__pane + .hui-simple-accordion__pane {
	border-top: 1px solid var(--border-color);
}

.hui-simple-accordion__header {
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 6px 10px;
	cursor: pointer;
	user-select: none;
	color: var(--text-primary);
	font-size: 12px;
	font-weight: 600;
	background: var(--bg-tertiary); /* distinct from pane body so the header reads as a header */
	border-bottom: 1px solid var(--border-color);
}

.hui-simple-accordion__pane--collapsed > .hui-simple-accordion__header {
	border-bottom: 0;
}

.hui-simple-accordion__header:hover {
	background: color-mix(in srgb, var(--accent-green) 10%, var(--bg-tertiary));
}

.hui-simple-accordion__header:focus-visible {
	outline: 2px solid var(--accent-green);
	outline-offset: -2px;
}

/* `collapsible: false` — headers are inert section titles, not toggle
   buttons. Drop the click affordance (cursor + hover) so they don't
   mislead the user into thinking they can click to collapse. */

.hui-simple-accordion__header--static {
	cursor: default;
}

.hui-simple-accordion__header--static:hover {
	background: var(--bg-tertiary);
}

/* When the accordion isn't collapsible, panes auto-size to content (no
   _layout pool to distribute). Give bodies some breathing room so the
   form controls inside don't sit flush against the header divider. Also
   collapse the accordion's own height/overflow constraints — without
   collapsibility the user can't shrink long content to fit a fixed
   pool, so the natural answer is to let the outer container scroll. */

.hui-simple-accordion__pane:has(.hui-simple-accordion__header--static) > .hui-simple-accordion__body {
	padding: 12px 14px;
	overflow: visible;
	flex: 0 0 auto;
}

.hui-simple-accordion:has(.hui-simple-accordion__header--static) {
	height: auto;
	overflow: visible;
	flex: 0 0 auto;
}

.hui-simple-accordion__grip {
	color: var(--text-muted);
	font-size: 14px;
	line-height: 1;
	cursor: grab;
	padding: 0 2px;
	user-select: none;
}

.hui-simple-accordion__grip:active {
	cursor: grabbing;
}

.hui-simple-accordion__chevron {
	color: var(--text-muted);
	font-size: 11px;
	width: 12px;
	text-align: center;
	flex: 0 0 auto;
}

.hui-simple-accordion__title {
	flex: 1 1 auto;
	min-width: 0;
}

.hui-simple-accordion__body {
	padding: 0; /* lists/grids inside provide their own padding; outer body 0 keeps the expanded pane flush */
	overflow: auto;
	min-height: 0;
	/* JS owns the body's height — set by simpleAccordion's _layout() in px.
	 * Until that runs (first rAF after paint), the body sizes to its content. */
	flex: 0 0 auto;
	/* Reserve the scrollbar gutter even when content doesn't overflow, so
	 * the body chrome doesn't shift when content grows past the visible
	 * height. Without this, a near-fitting content list jumps horizontally
	 * the moment one more row appears and the scrollbar takes up width.
	 * Also smooths the user's "scroll background looks like a UI error"
	 * report — the empty-when-no-thumb track is now always present and
	 * reads as intentional chrome instead of a sudden gap. */
	scrollbar-gutter: stable;
}

/* Drag-handle between adjacent open panes. Positioned absolutely on the
 * pane below the gap (matches the rich `accordion` extension's trick) so a
 * thin visible line gets a fatter clickable strip without a layout slot. */

.hui-simple-accordion__sash {
	position: absolute;
	top: -3px;
	left: 0;
	right: 0;
	height: 6px;
	z-index: 4;
	cursor: row-resize;
	background: transparent;
	transition: background-color 100ms ease;
}

.hui-simple-accordion__sash:hover,
.hui-simple-accordion__sash.dragging {
	background: var(--accent-green);
}

/* Disable text selection while resizing — pointermove handlers fire over
 * every element they cross and a selection would flicker as the user drags. */

.hui-simple-accordion--resizing,
.hui-simple-accordion--resizing * {
	user-select: none !important;
}

.hui-simple-accordion__pane--dragging {
	opacity: 0.5;
}

/* Drop indicator — thin accent bar above or below the hovered pane
 * showing exactly where the dragged pane will land. Replaces the
 * earlier "highlight the entire target section" pattern which read
 * as "drop INTO" (wrong intent — accordion panes don't nest). */

.hui-simple-accordion__pane--drop-above::before,
.hui-simple-accordion__pane--drop-below::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	height: 2px;
	background: var(--accent-green);
	pointer-events: none;
	z-index: 1;
}

.hui-simple-accordion__pane--drop-above::before {
	top: -1px;
}

.hui-simple-accordion__pane--drop-below::after {
	bottom: -1px;
}


/* ╭─ source: api/ui/slider/slider.css ─╮ */
/*
 * slider primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── slider ─────────────────────────────────────────────────────────────── */

/* Range slider with optional live value display. The input itself is the
 * native range; styling of the track + thumb comes from the global
 * `input[type="range"]` rules in base.css (those drive the --fill
 * custom-property gradient too). The primitive provides the layout +
 * value display + --fill sync. */

.hui-slider {
	display: flex;
	align-items: center;
	gap: 12px;
}

.hui-slider__input {
	flex: 1;
}

.hui-slider__value {
	min-width: 48px;
	text-align: right;
	color: var(--text-secondary);
	font-variant-numeric: tabular-nums;
	font-size: 12px;
}


/* ╭─ source: api/ui/spinner/spinner.css ─╮ */
/*
 * spinner primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── spinner ────────────────────────────────────────────────────────────── */

.hui-spinner {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	color: var(--text-secondary);
}

.hui-spinner__ring {
	display: inline-block;
	border: 2px solid var(--bg-tertiary);
	border-top-color: var(--accent-green);
	border-radius: 50%;
	animation: hui-spin 0.7s linear infinite;
}

.hui-spinner--sm .hui-spinner__ring {
	width: 10px;
	height: 10px;
	border-width: 2px;
}

.hui-spinner--md .hui-spinner__ring {
	width: 14px;
	height: 14px;
	border-width: 2px;
}

.hui-spinner--lg .hui-spinner__ring {
	width: 28px;
	height: 28px;
	border-width: 3px;
}

.hui-spinner__label {
	font-size: 12px;
}

/* Overlay spinner — fills the parent (`overlay: "fill"`, parent must be
 * position:relative) or the whole window (`overlay: "window"`). Centers
 * the spinner + label, dims everything underneath with a subtle backdrop.
 * Used by views that want a "the body is loading" surface (e.g. repo-mgmt
 * during a Rescan) instead of cramming a 14px ring into a corner. */

.hui-spinner-overlay {
	display: flex;
	align-items: center;
	justify-content: center;
	background: color-mix(in srgb, var(--bg-primary) 60%, transparent);
	z-index: 1;
}

.hui-spinner-overlay--fill {
	position: absolute;
	inset: 0;
}

.hui-spinner-overlay--window {
	position: fixed;
	inset: 0;
	z-index: 9999;
}

.hui-spinner--center {
	flex-direction: column;
	gap: 10px;
	color: var(--text-primary);
}

.hui-spinner--center .hui-spinner__label {
	font-size: 13px;
}


/* ╭─ source: api/ui/stat-row/stat-row.css ─╮ */
/*
 * stat-row primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── stat row ───────────────────────────────────────────────────────────── */

.hui-stat-row {
	display: grid;
	grid-template-columns: minmax(120px, max-content) 1fr auto;
	gap: 12px;
	align-items: baseline;
	padding: 4px 0;
	font-size: 12px;
}

.hui-stat-row__label {
	color: var(--text-muted);
}

.hui-stat-row__value {
	color: var(--text-primary);
}

.hui-stat-row__hint {
	color: var(--text-muted);
	font-size: 11px;
}


/* ╭─ source: api/ui/status-dot/status-dot.css ─╮ */
/*
 * status-dot primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── status dot ─────────────────────────────────────────────────────────── */

.hui-status-dot {
	display: inline-block;
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: var(--text-disabled);
	vertical-align: middle;
}

.hui-status-dot--ok {
	background: var(--accent-green);
}

.hui-status-dot--warn {
	background: var(--warning-yellow);
}

.hui-status-dot--error {
	background: var(--danger-red);
}

.hui-status-dot--unknown {
	background: var(--text-disabled);
}

.hui-status-dot--info {
	background: #5fb3eb;
}


/* ╭─ source: api/ui/table/table.css ─╮ */
/*
 * table primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── table ──────────────────────────────────────────────────────────────── */

/* Data table with sortable columns + optional row click. The .hui-table wrapper
 * adds layout; the .data-table rules below style the cells / header / sort /
 * hover. (These used to live ONLY in the demo's CSS, so the table primitive
 * shipped unstyled to every other consumer — co-located here to fix that.) */

.hui-table {
	display: flex;
	flex-direction: column;
	min-height: 0;
	height: 100%;
}

.hui-table__scroll {
	overflow: auto;
	flex: 1 1 auto;
	min-height: 0;
}

.hui-table__table {
	width: 100%;
}

.hui-table__empty {
	text-align: center;
	color: var(--text-secondary);
	padding: 18px 12px;
	font-size: 12px;
}

/* The cell/header/sort/hover styling the primitive's <table class="…data-table">
 * emits — ship it with the primitive so it's styled for every consumer. */
.data-table {
	width: 100%;
	border-collapse: collapse;
	font-size: 12px;
}
.data-table th,
.data-table td {
	padding: 6px 14px;
	text-align: left;
	border-bottom: 1px solid var(--border-color);
	white-space: nowrap;
}
.data-table th {
	background: var(--bg-secondary);
	color: var(--text-secondary);
	font-weight: 600;
	font-size: 11px;
	text-transform: uppercase;
	letter-spacing: 0.4px;
	cursor: pointer;
	user-select: none;
	position: sticky;
	top: 0;
	z-index: 1;
}
.data-table th:hover {
	color: var(--accent-green);
}
.data-table th.sorted-asc::after {
	content: " ▲";
	color: var(--accent-green);
}
.data-table th.sorted-desc::after {
	content: " ▼";
	color: var(--accent-green);
}
.data-table tbody tr:hover {
	background: var(--bg-secondary);
}

/* ─── inline editing (opt-in — added only on .hui-table--editable) ────────── */
.hui-table--editable .hui-table__scroll {
	outline: none;
}
.hui-table--editable td.is-editable {
	cursor: cell;
}
/* rectangular selection */
.hui-table--editable td.is-selected {
	background: var(--tree-selection-bg, rgba(152, 195, 121, 0.18));
}
/* the single active / focused cell */
.hui-table--editable td.is-active {
	box-shadow: inset 0 0 0 2px var(--accent-green);
}
/* a cell whose editor.validate() rejected the value */
.hui-table--editable td.has-error {
	box-shadow: inset 0 0 0 2px var(--accent-red, #e35);
}
/* the inline editor (text / number input or select) */
.hui-table__editor {
	width: 100%;
	box-sizing: border-box;
	padding: 3px 6px;
	border: 1px solid var(--accent-green);
	border-radius: 3px;
	font: inherit;
	color: var(--text-primary);
	background: var(--bg-primary);
	outline: none;
}


/* ╭─ source: api/ui/tabs/tabs.css ─╮ */
/*
 * tabs primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── tabs ───────────────────────────────────────────────────────────────── */

.hui-tabs {
	display: flex;
	flex-direction: column;
	gap: 8px;
	min-width: 0;
}

/* Outer header row hosts the bar + chevron/dropdown overflow controls (added
 * by installTabOverflow). Keeping it `display:flex` lets the controls sit
 * inline with the bar without the bar's own buttons getting squeezed. */

.hui-tabs__header {
	display: flex;
	align-items: stretch;
	border-bottom: 1px solid var(--divider-color);
	min-width: 0;
}

.hui-tabs__bar {
	display: flex;
	gap: 0;
	flex: 1 1 auto;
	min-width: 0;
}

.hui-tabs__tab {
	background: transparent;
	border: 0;
	padding: 6px 12px;
	color: var(--text-secondary);
	font: inherit;
	font-size: 12px;
	cursor: pointer;
	border-bottom: 2px solid transparent;
	margin-bottom: -1px;
}

.hui-tabs__tab:hover {
	color: var(--text-primary);
}

.hui-tabs__tab--selected {
	color: var(--text-primary);
	border-bottom-color: var(--accent-green);
}

.hui-tabs__panel {
	flex: 1 1 auto;
	min-height: 0;
}


/* ╭─ source: api/ui/text-input/text-input.css ─╮ */
/*
 * text-input primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── text input ─────────────────────────────────────────────────────────── */

/* Themed wrapper for plain `<input type="text">` (and password/email/etc.).
 * Mirrors the file-picker / editable-list input look so every text field
 * across the host UI uses the same chrome. */

.hui-text-input {
	background: var(--bg-tertiary);
	color: var(--text-primary);
	border: 1px solid var(--border-color);
	border-radius: var(--radius-sm);
	padding: 6px 10px;
	font: inherit;
	font-size: 12px;
	min-width: 0;
}

.hui-text-input:focus {
	outline: none;
	border-color: var(--accent-green);
}

.hui-text-input:disabled,
.hui-text-input[readonly] {
	opacity: 0.6;
	cursor: not-allowed;
}


/* ╭─ source: api/ui/toggle/toggle.css ─╮ */
/*
 * toggle primitive — styles (co-located; concatenated by `rummage styles`).
 */

/* ─── toggle ─────────────────────────────────────────────────────────────── */

/* iOS-style switch — sliding pill that flips between off (grey) and on
 * (accent green). Wraps the label + the switch in one <label> so the
 * whole row is the click target. Same visual treatment as the legacy
 * .toggle-* set (switch in shared.css; label/group quarantined in legacy.css). */

.hui-toggle {
	display: flex;
	align-items: center;
	justify-content: space-between;
	cursor: pointer;
	padding: 12px 0;
	transition: opacity 0.2s ease;
}

.hui-toggle:hover {
	opacity: 0.9;
}

.hui-toggle--disabled {
	cursor: not-allowed;
}

.hui-toggle__label {
	font-size: 13px;
	color: var(--text-primary);
	flex: 1;
	user-select: none;
	margin-right: 16px;
}

.hui-toggle__switch {
	position: relative;
	width: 48px;
	height: 26px;
	flex-shrink: 0;
	margin-left: auto;
}

.hui-toggle__switch input[type="checkbox"] {
	opacity: 0;
	width: 0;
	height: 0;
	position: absolute;
}

.hui-toggle__slider {
	position: absolute;
	inset: 0;
	background-color: var(--bg-hover);
	border-radius: 26px;
	transition:
		background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
		box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
	cursor: pointer;
	box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
	overflow: hidden;
}

.hui-toggle__knob {
	position: absolute;
	height: 20px;
	width: 20px;
	left: 3px;
	bottom: 3px;
	background-color: var(--text-secondary);
	border-radius: 50%;
	transition:
		transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
		background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
		left 0.3s cubic-bezier(0.4, 0, 0.2, 1),
		width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
	box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 2;
}

.hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider {
	background-color: var(--accent-small);
	box-shadow:
		inset 0 1px 3px rgba(0, 0, 0, 0.2),
		0 0 8px rgba(151, 202, 97, 0.3);
}

.hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__knob {
	transform: translateX(22px);
	background-color: #fff;
}

.hui-toggle__switch input[type="checkbox"]:focus + .hui-toggle__slider {
	outline: none;
}

.hui-toggle__switch input[type="checkbox"]:disabled + .hui-toggle__slider {
	opacity: 0.5;
	cursor: not-allowed;
}

.hui-toggle__switch input[type="checkbox"]:disabled + .hui-toggle__slider .hui-toggle__knob {
	background-color: var(--text-disabled);
}

/* ── text: "checkmark" — glyph in the knob ───────────────────────────────
 * Knob carries a ✕ glyph when off, ✓ when on. Composable with any
 * `variant` / `vertical` combination. */

.hui-toggle__knob-icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 12px;
	line-height: 1;
	color: var(--bg-primary);
	font-weight: 700;
}

.hui-toggle__knob-icon::before {
	content: "\2715"; /* ✕ */
}

.hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__knob-icon::before {
	content: "\2713"; /* ✓ */
}

/* ── track text — used by text: "on/off" and text: [off, on] ─────────────
 * Two labels live inside the slider at fixed left / right positions.
 * Knob slides between/over them. Opacity flips based on :checked state
 * so only the relevant label reads at any moment. */

.hui-toggle__track-text {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	font-size: 10px;
	font-weight: 700;
	color: var(--text-primary);
	letter-spacing: 0.04em;
	user-select: none;
	pointer-events: none;
	z-index: 1;
	white-space: nowrap;
}

/* Position convention: label appears OPPOSITE the knob. When unchecked
 * the knob is on the LEFT, so the "off" label reads on the RIGHT. When
 * checked the knob is on the RIGHT, so the "on" label reads on the LEFT.
 * Opacity transitions hide the inactive label so only one reads at a
 * time. */

.hui-toggle__track-text--off {
	right: 8px;
	color: var(--text-secondary);
	opacity: 0.8;
}

.hui-toggle__track-text--on {
	left: 8px;
	color: #fff;
	opacity: 0;
}

.hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__track-text--off {
	opacity: 0;
}

.hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__track-text--on {
	opacity: 1;
}

/* text: "on/off" preset (short fixed labels) — wider pill so OFF/ON +
 * 20px knob all fit. */

.hui-toggle--pill.hui-toggle--text-text .hui-toggle__switch {
	width: 60px;
}

.hui-toggle--pill.hui-toggle--text-text .hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__knob {
	transform: translateX(34px);
}

/* text: [on, off] custom — track expands to fit the wider of the two
 * labels. CSS Grid with two equal 1fr columns gives each label its own
 * half; `justify-items: center` centers each label inside its half so
 * the visible label is equidistant from the knob's edges regardless
 * of which label is wider. The grid is sized by its intrinsic content
 * (both labels are present, just one is opacity 0), so the wider label
 * sets the column width and both columns inherit that width.
 *
 * The knob is absolute behind the grid (z-index 1) so it slides UNDER
 * the labels — `order` is unused (grid-column places each label in
 * its intended half regardless of DOM order). */

.hui-toggle--pill.hui-toggle--text-label .hui-toggle__switch {
	width: auto;
	display: inline-block;
	height: 26px;
	min-width: 64px;
}

/* The base `.hui-toggle__slider` is `position: absolute; inset: 0` so
 * it stretches to fill the switch — that pins the slider to whatever
 * width the switch chose, which kept the grid columns too narrow and
 * clipped longer labels via overflow: hidden. Override to in-flow so
 * the grid drives the slider's intrinsic width. */

.hui-toggle--pill.hui-toggle--text-label .hui-toggle__slider {
	position: relative;
	inset: auto;
	display: inline-grid;
	grid-template-columns: 1fr 1fr;
	align-items: center;
	justify-items: center;
	height: 26px;
	min-width: 64px;
}

.hui-toggle--pill.hui-toggle--text-label .hui-toggle__track-text {
	position: relative;
	top: auto;
	bottom: auto;
	left: auto;
	right: auto;
	transform: none;
	padding: 0 12px; /* matched left/right gutter inside each half */
	line-height: 20px; /* explicit so both labels share a baseline */
	font-size: 11px;
	z-index: 3;
	color: var(--text-primary);
	pointer-events: none;
	white-space: nowrap;
}

/* grid-row: 1 on BOTH labels is non-optional. Without it, the auto-flow
 * algorithm processes the DOM-first label (off, grid-column: 2) at row
 * 1 col 2, then advances the placement cursor past col 2 — so the
 * DOM-second label (on, grid-column: 1) gets wrapped to row 2 col 1.
 * That puts "Yes" below the track (clipped by overflow) and at a
 * different vertical position from "No". */

.hui-toggle--pill.hui-toggle--text-label .hui-toggle__track-text--on {
	grid-column: 1;
	grid-row: 1;
	opacity: 0;
	color: #fff;
}

.hui-toggle--pill.hui-toggle--text-label .hui-toggle__track-text--off {
	grid-column: 2;
	grid-row: 1;
	opacity: 1;
}

.hui-toggle--pill.hui-toggle--text-label
	.hui-toggle__switch
	input[type="checkbox"]:checked
	+ .hui-toggle__slider
	.hui-toggle__track-text--on {
	opacity: 1;
}

.hui-toggle--pill.hui-toggle--text-label
	.hui-toggle__switch
	input[type="checkbox"]:checked
	+ .hui-toggle__slider
	.hui-toggle__track-text--off {
	opacity: 0;
}

/* Knob: absolute, behind labels (lower z-index). Fills exactly half the
 * track width; translateX(100%) of knob-width = half slider, so the
 * knob ends with a 3px margin from the right edge. */

.hui-toggle--pill.hui-toggle--text-label .hui-toggle__knob {
	z-index: 1;
	left: 3px;
	top: 3px;
	bottom: 3px;
	width: calc(50% - 3px);
	height: auto;
	border-radius: 22px;
}

.hui-toggle--pill.hui-toggle--text-label .hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__knob {
	transform: translateX(100%);
}

/* ── vertical orientation ────────────────────────────────────────────────
 * Track rotated 90°: 26px wide × 48px tall. Knob slides top↔bottom.
 * "Checked" = knob at top. Composes with any text mode (typically
 * paired with no text or "checkmark"). */

.hui-toggle--vertical .hui-toggle__switch {
	width: 26px;
	height: 48px;
}

.hui-toggle--vertical .hui-toggle__knob {
	left: 3px;
	bottom: auto;
	top: 25px;
	transform: none;
}

.hui-toggle--vertical .hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__knob {
	transform: translateY(-22px);
}

.hui-toggle--vertical .hui-toggle__track-text {
	writing-mode: vertical-rl;
	transform: translateX(-50%);
	left: 50%;
	top: auto;
}

.hui-toggle--vertical .hui-toggle__track-text--off {
	top: 6px;
	bottom: auto;
}

.hui-toggle--vertical .hui-toggle__track-text--on {
	bottom: 6px;
	top: auto;
	right: auto;
}

/* ── variant: rectangle ──────────────────────────────────────────────────
 * Rectangular track, knob ≈ 1/4 width. On/off labels (if supplied)
 * fill the 3/4 area the knob isn't covering. Layered: text always
 * present; opacity flips so the relevant one shows.  */

.hui-toggle--rectangle .hui-toggle__switch {
	width: 88px;
	height: 26px;
}

.hui-toggle--rectangle .hui-toggle__slider {
	border-radius: 4px;
}

.hui-toggle--rectangle .hui-toggle__knob {
	border-radius: 3px;
	width: 22px; /* ≈ 1/4 of 88px */
	height: 20px;
	left: 3px;
	bottom: 3px;
}

.hui-toggle--rectangle .hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__knob {
	transform: translateX(60px);
}

.hui-toggle--rectangle .hui-toggle__track-text {
	font-size: 11px;
	letter-spacing: 0.03em;
	transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Labels OPPOSITE the knob (same convention as the pill text variants).
 * Off state: knob LEFT, "off" label reads on the RIGHT. On state: knob
 * RIGHT, "on" label reads on the LEFT. */

.hui-toggle--rectangle .hui-toggle__track-text--off {
	right: 12px;
	left: auto;
	opacity: 1;
	color: var(--text-primary);
}

.hui-toggle--rectangle .hui-toggle__track-text--on {
	left: 12px;
	right: auto;
	opacity: 0;
	color: var(--text-primary);
}

.hui-toggle--rectangle .hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__track-text--off {
	opacity: 0;
}

.hui-toggle--rectangle .hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__track-text--on {
	opacity: 1;
	color: #fff;
}

/* rectangle + custom-text labels — same grid-1fr-1fr layout as the
 * pill text-label so each label is centered in its half. Knob width
 * is proportional (≈ 1/4 of the track) and slides between the left
 * and right quarters via `left`. */

.hui-toggle--rectangle.hui-toggle--text-label .hui-toggle__switch {
	width: auto;
	display: inline-block;
	height: 26px;
	min-width: 88px;
}

/* Same in-flow override as pill text-label so the grid drives width
 * instead of being pinned by the base `position: absolute; inset: 0`. */

.hui-toggle--rectangle.hui-toggle--text-label .hui-toggle__slider {
	position: relative;
	inset: auto;
	display: inline-grid;
	grid-template-columns: 1fr 1fr;
	align-items: center;
	justify-items: center;
	height: 26px;
	min-width: 88px;
}

.hui-toggle--rectangle.hui-toggle--text-label .hui-toggle__track-text {
	position: relative;
	top: auto;
	bottom: auto;
	left: auto;
	right: auto;
	transform: none;
	padding: 0 12px;
	line-height: 20px;
	font-size: 11px;
	z-index: 3;
	color: var(--text-primary);
	pointer-events: none;
	white-space: nowrap;
}

.hui-toggle--rectangle.hui-toggle--text-label .hui-toggle__track-text--on {
	grid-column: 1;
	grid-row: 1;
	opacity: 0;
	color: #fff;
}

.hui-toggle--rectangle.hui-toggle--text-label .hui-toggle__track-text--off {
	grid-column: 2;
	grid-row: 1;
	opacity: 1;
}

.hui-toggle--rectangle.hui-toggle--text-label
	.hui-toggle__switch
	input[type="checkbox"]:checked
	+ .hui-toggle__slider
	.hui-toggle__track-text--on {
	opacity: 1;
}

.hui-toggle--rectangle.hui-toggle--text-label
	.hui-toggle__switch
	input[type="checkbox"]:checked
	+ .hui-toggle__slider
	.hui-toggle__track-text--off {
	opacity: 0;
}

/* Knob ≈ 25% width with 3px margins on each side; slides via `left`
 * so it spans whatever width the labels chose. */

.hui-toggle--rectangle.hui-toggle--text-label .hui-toggle__knob {
	z-index: 1;
	left: 3px;
	bottom: 3px;
	top: 3px;
	width: calc(25% - 6px);
	height: auto;
}

.hui-toggle--rectangle.hui-toggle--text-label .hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__knob {
	left: calc(75% + 3px);
	transform: none; /* clear the inherited translateX(60px) from .hui-toggle--rectangle */
}

/* rectangle + vertical — 26 wide × 88 tall, knob slides top↔bottom.
 * Knob is short-fat (20 × 22) instead of tall-thin so the 1/4 ratio
 * makes sense for a vertical rectangle. */

.hui-toggle--rectangle.hui-toggle--vertical .hui-toggle__switch {
	width: 26px;
	height: 88px;
}

.hui-toggle--rectangle.hui-toggle--vertical .hui-toggle__knob {
	width: 20px;
	height: 22px;
	left: 3px;
	bottom: auto;
	top: 63px;
	transform: none;
}

.hui-toggle--rectangle.hui-toggle--vertical .hui-toggle__switch input[type="checkbox"]:checked + .hui-toggle__slider .hui-toggle__knob {
	transform: translateY(-60px);
}

/* Rectangle vertical text stays horizontal-tb (wide enough to fit). */

.hui-toggle--rectangle.hui-toggle--vertical .hui-toggle__track-text {
	writing-mode: horizontal-tb;
	left: 50%;
	transform: translateX(-50%);
}

.hui-toggle--rectangle.hui-toggle--vertical .hui-toggle__track-text--off {
	top: 8px;
	bottom: auto;
	right: auto;
}

.hui-toggle--rectangle.hui-toggle--vertical .hui-toggle__track-text--on {
	bottom: 8px;
	top: auto;
	right: auto;
}


/* ╭─ source: api/ui/window-controls/window-controls.css ─╮ */
/*
 * window-controls primitive — styles (co-located; concatenated by `rummage styles`).
 */

.win-ctrl-btn:focus {
	outline: none;
}

.app-titlebar-controls {
	display: flex;
	align-items: stretch;
	height: 100%;
	margin-left: auto;
	-webkit-app-region: no-drag;
}

.win-ctrl-btn {
	width: 46px;
	height: 100%;
	/* Fallback when parent has no explicit height (e.g. a primitive showcase
	   or any host other than the title bar itself). In the real title bar
	   `height: 100%` resolves to 40px; outside it, we want a sensible
	   intrinsic size instead of collapsing to the SVG glyph. */
	min-height: 32px;
	background: none;
	border: none;
	color: var(--text-secondary);
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: center;
	-webkit-app-region: no-drag;
	transition:
		background-color 0.1s ease,
		color 0.1s ease;
}

.win-ctrl-btn:hover {
	background-color: var(--selection-accent-bg);
	color: var(--selection-accent-text);
}

.win-ctrl-btn--close:hover {
	background-color: var(--danger-red);
	color: #fff;
}


/* ╭─ source: styles/ui-primitives.css ─╮ */
/*
 * ui-primitives.css — shared/un-owned primitive rules (base + cross-cutting).
 * Per-primitive rules live in src/api/ui/<name>/<name>.css; this is the remainder.
 */

@keyframes hui-spin {
	to {
		transform: rotate(360deg);
	}
}
@keyframes hui-shake {
	0%,
	100% {
		transform: translateX(0);
	}
	25% {
		transform: translateX(-3px);
	}
	50% {
		transform: translateX(3px);
	}
	75% {
		transform: translateX(-2px);
	}
}
/* ─── shared icon wrapper ───────────────────────────────────────────────── */
.hui-icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
.hui-icon svg {
	width: 100%;
	height: 100%;
}
.hui-icon--text {
	font-size: inherit;
}


/* ╭─ source: api/shell/shell.css ─╮ */
/**
 *	@Project: @cldmv/rummage
 *	@Filename: /src/api/shell/shell.css
 *	@Date: 2026-05-21
 *	@Author: Shinrai <Shinrai@users.noreply.github.com>
 *	@Email: <Shinrai@users.noreply.github.com>
 *	@Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
 */

/*
 * Shell — the standard host layout. Slots are addressable via CSS classes and
 * data-slot attributes. Extensions contribute views into named slots via
 * manifest.contributes.views[].where.
 *
 * Modes (set via [data-mode] on .shell):
 *   "default"            — every slot visible (subject to per-slot empty hides)
 *   "settings-middle"    — middle.top takes over the middle column; middle.lower
 *                          stays present only if it has any active tab.
 *   "settings-full"      — left-panel, right-panel, middle.lower are hidden;
 *                          middle.top owns the entire workspace. Icon bar,
 *                          app bar, bottom bar, and bottom-content (if loaded)
 *                          remain visible.
 */

.shell {
	display: grid;
	grid-template-columns: auto 1fr;
	grid-template-rows: auto 1fr var(--bottom-content-height, 260px) auto;
	grid-template-areas:
		"app-bar     app-bar"
		"icon-bar    workspace"
		"icon-bar    bottom-content"
		"bottom-bar  bottom-bar";
	/* Fill height, most-reliable-last: 100vh (fallback for no-dvh engines) → 100dvh
	 * (tracks mobile URL bars) → --app-vh (JS-published window.innerHeight, the ground
	 * truth). --app-vh is the one that matters on iOS PWA / add-to-home-screen views,
	 * where 100dvh can under-report and leave a strip below the bottom bar; shell.mjs'
	 * _applyViewport keeps it live. See --app-vh publishing there. */
	height: 100vh;
	height: 100dvh;
	height: var(--app-vh, 100dvh);
	width: 100vw;
	overflow: hidden;
	background: var(--bg-primary);
	color: var(--text-primary);
}
/* Collapse the bottom-content row when it has no contributions OR is
 * explicitly toggled off via the chevron in the icon-bar. */
.shell:has(.shell__bottom-content[data-empty="true"]),
.shell:has(.shell__bottom-content[data-collapsed="true"]),
.shell.shell--bottom-collapsed {
	grid-template-rows: auto 1fr 0 auto;
}

.shell__app-bar {
	grid-area: app-bar;
	display: flex;
	align-items: center;
	background: var(--bg-secondary);
	border-bottom: 1px solid var(--border-color);
	min-height: 40px;
	-webkit-app-region: drag;
}
/* The app-bar slot anchors absolutely-positioned children (search overlay,
 * progress strip) — its own flex row; same centering anchor. */
[data-slot="app-bar"] {
	position: relative;
}
/* Only the interactive controls disable the drag region — everything else in
 * the app-bar (brand text, padding, empty space) stays draggable. */
.shell__app-bar button,
.shell__app-bar input,
.shell__app-bar select,
.shell__app-bar a,
.shell__app-bar [role="button"] {
	-webkit-app-region: no-drag;
}

.shell__icon-bar {
	grid-area: icon-bar;
	display: flex;
	flex-direction: column;
	width: 48px;
	background: var(--bg-tertiary, var(--bg-secondary));
	border-right: 1px solid var(--border-color);
	overflow: hidden;
	/* Widen smoothly when expanded (Feature 2). The grid's `auto` icon-bar track
	 * follows the element width, so the workspace reflows without overlap. */
	transition: width 160ms ease;
}
.shell__icon-bar-top {
	display: flex;
	flex-direction: column;
	flex: 1 1 auto;
	/* Scrollable so every contributed icon is reachable when the column can't fit
	 * them all — the scrollbar only appears while overflowing, so a short list is
	 * unchanged. overflow-x stays hidden (icons never overflow horizontally). */
	overflow-x: hidden;
	overflow-y: auto;
	min-height: 0;
	/* Thin, subtle, theme-aware scrollbar (Firefox / standard). Colours come from
	 * theme tokens so it tracks light + dark automatically. */
	scrollbar-width: thin;
	scrollbar-color: var(--border-color) transparent;
}
/* WebKit / Chromium (Electron + most browsers): a slim, unobtrusive scrollbar
 * that only takes a few px and uses the same token colours as the standard one. */
.shell__icon-bar-top::-webkit-scrollbar {
	width: 6px;
}
.shell__icon-bar-top::-webkit-scrollbar-track {
	background: transparent;
}
.shell__icon-bar-top::-webkit-scrollbar-thumb {
	background: var(--border-color);
	border-radius: 3px;
}
@media (hover: hover) {
	.shell__icon-bar-top::-webkit-scrollbar-thumb:hover {
		background: var(--text-muted, var(--border-color));
	}
}
/* Keep the overflow "more" fallback pinned to the foot of the scrolling column so
 * it stays reachable even when scrolled; the bar background masks icons sliding
 * under it. Only appears while the column overflows. */
.shell__icon-bar-top > .shell-icon-btn--more {
	position: sticky;
	bottom: 0;
	background: var(--bg-tertiary, var(--bg-secondary));
	border-top: 1px solid var(--border-color);
}
/* ─── Overflow mode (configurable; default "both") ─────────────────────────
 * The shell publishes the chosen mode on the icon-bar via data-icon-overflow.
 * "both" (default / unset) and "scroll" keep the column scrollable — the base
 * overflow-y: auto above applies, byte-for-byte the original behaviour. "menu"
 * turns scroll OFF so the tail that doesn't fit is clipped and reached through
 * the sticky "…" more button (classic toolbar overflow). The more button itself
 * is suppressed by the module in "scroll" mode; this rule is a belt-and-braces
 * guard so a stray button (e.g. mid-flip) never lingers when scrolling is the
 * only affordance. */
.shell__icon-bar[data-icon-overflow="menu"] .shell__icon-bar-top {
	overflow-y: hidden;
}
.shell__icon-bar[data-icon-overflow="scroll"] .shell__icon-bar-top > .shell-icon-btn--more {
	display: none;
}
.shell__icon-bar-bottom {
	display: flex;
	flex-direction: column;
	flex: 0 0 auto;
}

/* ─── Expandable left bar (Feature 2) ──────────────────────────────────────
 * NARROW is the default (icons only — the two columns above). A pinned toggle
 * at the foot widens the bar to a WIDE menu of icon+title rows, grouped into
 * collapsible accordion sections (Feature 3 populates real groups). Expansion
 * only applies in the wide viewport — narrow keeps its off-canvas drawer. */

/* The wide menu — hidden until expanded so narrow is byte-for-byte unchanged. */
.shell__icon-bar-wide {
	display: none;
	flex-direction: column;
	flex: 1 1 auto;
	min-height: 0;
}
.shell__icon-bar-wide-top {
	flex: 1 1 auto;
	min-height: 0;
	overflow-x: hidden;
	overflow-y: auto;
	scrollbar-width: thin;
	scrollbar-color: var(--border-color) transparent;
}
.shell__icon-bar-wide-top::-webkit-scrollbar {
	width: 6px;
}
.shell__icon-bar-wide-top::-webkit-scrollbar-track {
	background: transparent;
}
.shell__icon-bar-wide-top::-webkit-scrollbar-thumb {
	background: var(--border-color);
	border-radius: 3px;
}
@media (hover: hover) {
	.shell__icon-bar-wide-top::-webkit-scrollbar-thumb:hover {
		background: var(--text-muted, var(--border-color));
	}
}
.shell__icon-bar-wide-bottom {
	flex: 0 0 auto;
	border-top: 1px solid var(--border-color);
}

/* The expand/collapse toggle — pinned at the foot of the bar in both states.
 * Hidden by default (also covers the narrow drawer, which has its own toggle);
 * shown in the wide viewport. */
.shell__icon-bar-toggle {
	display: none;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 100%;
	height: 40px;
	background: transparent;
	border: none;
	border-top: 1px solid var(--border-color);
	color: var(--text-secondary);
	cursor: pointer;
}
.shell[data-viewport="wide"] .shell__icon-bar-toggle {
	display: flex;
}
@media (hover: hover) {
	.shell__icon-bar-toggle:hover {
		color: var(--accent-green);
		background: var(--bg-primary);
	}
}
.shell__icon-bar-toggle svg {
	width: 18px;
	height: 18px;
}

/* Expanded state — wide viewport only (narrow keeps its 48px drawer). Swap the
 * icons-only columns for the wide menu and widen the bar. */
.shell[data-viewport="wide"] .shell__icon-bar[data-expanded="true"] {
	width: var(--icon-bar-wide-width, 232px);
}
.shell[data-viewport="wide"] .shell__icon-bar[data-expanded="true"] .shell__icon-bar-top,
.shell[data-viewport="wide"] .shell__icon-bar[data-expanded="true"] .shell__icon-bar-bottom {
	display: none;
}
.shell[data-viewport="wide"] .shell__icon-bar[data-expanded="true"] .shell__icon-bar-wide {
	display: flex;
}

/* A wide-menu row: [icon][title]. Mirrors the icon-btn hover/active treatment so
 * an active row paints exactly like the narrow icon it stands in for. */
.shell-bar-row {
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	height: 40px;
	padding: 0 12px;
	background: transparent;
	border: none;
	color: var(--text-secondary);
	cursor: pointer;
	text-align: left;
	position: relative;
}
@media (hover: hover) {
	.shell-bar-row:hover {
		color: var(--accent-green);
		background: var(--bg-primary);
	}
}
.shell-bar-row.active {
	color: var(--accent-green);
	background: var(--bg-primary);
	box-shadow: inset 2px 0 0 var(--accent-green);
}
.shell-bar-row__icon {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 22px;
	height: 22px;
}
.shell-bar-row__icon svg {
	width: 20px;
	height: 20px;
}
.shell-bar-row__label {
	flex: 1 1 auto;
	min-width: 0;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	font-size: 13px;
}
.shell-bar-row__badge {
	flex: 0 0 auto;
	min-width: 16px;
	height: 16px;
	padding: 0 5px;
	border-radius: 8px;
	background: var(--accent-red, #e35);
	color: #fff;
	font-size: 10px;
	font-weight: 600;
	display: flex;
	align-items: center;
	justify-content: center;
}

.shell__workspace {
	grid-area: workspace;
	display: grid;
	grid-template-columns: auto 1fr auto;
	grid-template-areas: "left-panel middle right-panel";
	overflow: hidden;
	min-height: 0;
}

.shell__left-panel {
	grid-area: left-panel;
	position: relative;
	display: flex;
	flex-direction: column;
	width: var(--left-panel-width, 250px);
	background: var(--bg-secondary);
	border-right: 1px solid var(--border-color);
	/* The panel itself no longer scrolls — the accordion inside owns scrolling,
	   one independent scroller per open sub-section. */
	overflow: hidden;
}

.shell__middle {
	grid-area: middle;
	display: grid;
	grid-template-rows: 1fr var(--middle-lower-height, 220px);
	grid-template-areas: "top" "lower";
	overflow: hidden;
	min-height: 0;
}
.shell__middle:has(.shell__middle-lower[data-empty="true"]) {
	grid-template-rows: 1fr 0;
}
.shell__middle-top {
	grid-area: top;
	overflow: hidden;
	min-height: 0;
	display: flex;
	flex-direction: column;
	position: relative;
}
.shell__middle-lower {
	grid-area: lower;
	position: relative;
	overflow: hidden;
	min-height: 0;
	display: flex;
	flex-direction: column;
	border-top: 1px solid var(--border-color);
}

.shell__right-panel {
	grid-area: right-panel;
	position: relative;
	display: flex;
	flex-direction: column;
	width: var(--right-panel-width, 280px);
	background: var(--bg-secondary);
	border-left: 1px solid var(--border-color);
	overflow: hidden;
}

.shell__bottom-content {
	grid-area: bottom-content;
	position: relative;
	display: flex;
	flex-direction: column;
	border-top: 1px solid var(--border-color);
	background: var(--bg-secondary);
	overflow: hidden;
	min-height: 0;
}

/* ─── Region splitters — draggable dividers between resizable shell areas ── */
.shell-splitter {
	position: absolute;
	z-index: 6;
	background: transparent;
	transition: background-color 100ms ease;
}
.shell-splitter:hover,
.shell-splitter.dragging {
	background: var(--accent-green);
}
.shell-splitter--col {
	top: 0;
	bottom: 0;
	width: 6px;
	cursor: col-resize;
}
.shell-splitter--row {
	left: 0;
	right: 0;
	height: 6px;
	cursor: row-resize;
}
.shell-splitter--right-edge {
	right: -3px;
}
.shell-splitter--left-edge {
	left: -3px;
}
.shell-splitter--top-edge {
	top: -3px;
}
/* While dragging, suppress text/elt selection + pointer flicker app-wide. */
.shell--resizing,
.shell--resizing * {
	user-select: none !important;
}
.shell--resizing iframe,
.shell--resizing webview {
	pointer-events: none;
}

/* While the OS window is actively resizing, kill TRANSITIONS so panels track
 * the window edge 1:1 instead of easing into place. Keyframe animations
 * (spinners, progress bars) are intentionally left alone. Applied to <body> so
 * it also covers overlays rendered outside the shell. */
body.window-resizing *,
body.window-resizing *::before,
body.window-resizing *::after {
	transition: none !important;
}

.shell__bottom-bar {
	/* Structural only — grid placement + height. The bottom-bar extension's
	 * .app-bottom-bar (added to this same node) owns the visual (padding,
	 * background, font-size, border). Setting them here too would win the
	 * cascade (later in the concatenated sheet) and override the extension. */
	grid-area: bottom-bar;
	display: flex;
	align-items: center;
	min-height: 24px;
}

/* Side-panel accordion mount — fills the panel; the accordion inside owns all
 * scrolling (one scroller per open pane). Sibling of the region splitter. */
.shell__panel-views {
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	flex-direction: column;
	overflow: hidden;
}

/* ─── Empty-slot hides — slots with zero contributions collapse to nothing. ── */

.shell__left-panel:empty,
.shell__left-panel[data-empty="true"] {
	display: none;
}
.shell__right-panel:empty,
.shell__right-panel[data-empty="true"] {
	display: none;
}
.shell__bottom-content[data-empty="true"],
.shell__bottom-content[data-collapsed="true"] {
	display: none;
}
.shell__middle-lower[data-empty="true"] {
	display: none;
}

/* ─── Slot internals (panel sections + tabbed regions) ─────────────────── */

.shell-section {
	display: flex;
	flex-direction: column;
	min-height: 0;
	border-bottom: 1px solid var(--border-color);
}
.shell-section:last-child {
	border-bottom: none;
}
.shell-section__header {
	display: flex;
	align-items: center;
	gap: 6px;
	width: 100%;
	/* Left padding matches .tree-item's 8px so the section-header chevron lines
	   up with the tree disclosure chevrons in the body below it. */
	padding: 6px 8px;
	font-size: 11px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.4px;
	color: var(--text-secondary);
	background: var(--bg-primary);
	border: none;
	border-bottom: 1px solid var(--border-color);
	text-align: left;
	cursor: pointer;
	font-family: inherit;
}
.shell-section__header:hover {
	color: var(--text-primary);
}
.shell-section__caret {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 16px;
	height: 16px;
	flex: 0 0 16px;
	color: var(--text-secondary);
}
.shell-section__caret svg {
	width: 16px;
	height: 16px;
}
.shell-section__title {
	flex: 1 1 auto;
}
.shell-section__body {
	flex: 1 1 auto;
	overflow: auto;
	min-height: 0;
}
.shell-section[data-collapsed="true"] {
	flex: 0 0 auto;
}
.shell-section[data-collapsed="true"] .shell-section__body {
	display: none;
}

.shell-tabs {
	display: flex;
	flex-direction: column;
	min-height: 0;
	flex: 1 1 auto;
	height: 100%;
}
/* Flex-row that hosts the (overflow-wrapped) tab bar + optional collapse caret
 * as siblings. Keeps the caret pinned to the right edge no matter how the bar
 * scrolls underneath. The row, not the bar, owns the bottom border. */
.shell-tabs__bar-row {
	display: flex;
	align-items: stretch;
	flex: 0 0 auto;
	background: var(--bg-secondary);
	border-bottom: 1px solid var(--border-color);
	min-width: 0;
}
.shell-tabs__bar {
	display: flex;
	gap: 0;
	padding: 0;
	margin: 0;
	flex: 1 1 auto;
	min-width: 0;
	/* Overflow is handled by installTabOverflow — no native scrollbar here. */
}
.shell-tabs__btn {
	padding: 8px 16px;
	background: transparent;
	border: none;
	border-bottom: 2px solid transparent;
	color: var(--text-secondary);
	font-size: 12px;
	cursor: pointer;
	white-space: nowrap;
	flex: 0 0 auto; /* tabs themselves keep their width */
}
/* Touch devices (phone / tablet): bigger tabs so they're comfortable tap targets. Coarse-pointer
 * only, so a desktop mouse keeps the compact tabs. */
@media (pointer: coarse) {
	.shell-tabs__btn {
		padding: 13px 22px;
		font-size: 15px;
	}
}
/* Hover affordances gated to real hover-capable pointers — on touch there is no
 * hover, so an un-gated :hover "sticks" to a tapped tab/icon and lights up on the
 * next tap (e.g. tapping a tab, then tapping it again). @media (hover: hover)
 * keeps desktop hover working while touch/PWA gets none. In-place so it stays
 * ordered before the matching .active rule (same specificity — source order wins). */
@media (hover: hover) {
	.shell-tabs__btn:hover {
		color: var(--selection-accent-text);
		background: var(--selection-accent-bg);
	}
}
.shell-tabs__btn.active {
	color: var(--text-primary);
	background: var(--bg-primary);
	border-bottom-color: var(--accent-green);
}
.shell-tabs__btn__label {
	flex: 0 1 auto;
	min-width: 0;
}
/* Tab badge — extension-controlled counter (e.g. Problems panel unread count).
 * Renders as a small pill to the right of the label. Hidden by setting
 * display:none from JS when the count is 0. */
.shell-tabs__btn__badge {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	margin-left: 6px;
	min-width: 16px;
	height: 14px;
	padding: 0 5px;
	border-radius: 8px;
	background: var(--danger-red, #e35);
	color: #fff;
	font-size: 10px;
	font-weight: 600;
	line-height: 14px;
}
/* Side-panel collapse chevron pinned to the far end of the tabs bar. */
.shell-tabs__caret {
	margin-left: auto;
	align-self: center;
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 24px;
	height: 24px;
	padding: 0;
	background: transparent;
	border: none;
	color: var(--text-secondary);
	cursor: pointer;
}
@media (hover: hover) {
	.shell-tabs__caret:hover {
		color: var(--accent-green);
	}
}
.shell-tabs__caret svg {
	width: 16px;
	height: 16px;
}
.shell-tabs[data-collapsed="true"] .shell-tabs__body {
	display: none;
}
.shell-tabs[data-collapsed="true"] {
	flex: 0 0 auto;
}
/* Headerless / icon-driven slot (all views `mode: "single"`): hide the tab
   strip so the active view fills the slot — navigation is the left-icon-bar's
   `linkTo`. The tab buttons stay in the DOM (just hidden) so switching +
   active-link painting keep working; see _renderTabsSlot. */
.shell-tabs[data-headerless="true"] .shell-tabs__bar-row {
	display: none;
}
/* View groups (Feature 3): when a group scopes the middle.top region
   (`data-active-group` on the wrap), tab buttons that aren't targets of the
   active group are marked `--group-hidden` and dropped from the strip so only
   the group's views show. Their panels are already hidden unless active, and
   the active tab is always in-scope. Nothing carries this class without groups,
   so the default strip is unchanged. */
.shell-tabs__btn--group-hidden {
	display: none;
}
.shell-tabs__body {
	flex: 1 1 auto;
	overflow: hidden; /* panels manage their own scroll */
	min-height: 0;
	background: var(--bg-primary);
	position: relative;
}
.shell-tabs__panel {
	display: none;
	height: 100%;
	min-height: 0;
	overflow: auto;
}
.shell-tabs__panel.active {
	display: flex;
	flex-direction: column;
}
/* Panels that mount a widget with its own internal scroller (terminal, table)
 * should not scroll themselves. The widget covers the whole panel. */
.shell-tabs__panel:has(> .terminal),
.shell-tabs__panel:has(> .data-table-bar) {
	overflow: hidden;
}

/* Right-panel section tabs mirror the middle-top tab strip's spacing, but stay
 * text-based: no background fill, no per-tab box. The active tab is marked the
 * same way the middle tabs are — an accent-green underline + green label — so
 * the strip still reads as tabs rather than a plain section header. The bar
 * itself sits flush to the panel edges (no outer padding). */
/* Right-panel bar-row uses a transparent strip (no fill). The border-bottom is
 * on the row, not the bar — overflow controls live in the row too. */
.shell__right-panel .shell-tabs__bar-row {
	background: transparent;
	padding: 0 3px 0 0;
	align-items: stretch;
}
.shell__right-panel .shell-tabs__bar {
	background: transparent;
	align-items: stretch;
}
.shell__right-panel .shell-tabs__btn {
	padding: 8px 16px;
	margin: 0;
	border-bottom: 2px solid transparent;
	font-size: 12px;
}
@media (hover: hover) {
	.shell__right-panel .shell-tabs__btn:hover {
		background: transparent;
		color: var(--text-primary);
	}
}
.shell__right-panel .shell-tabs__btn.active {
	background: transparent;
	color: var(--accent-green);
	border-bottom-color: var(--accent-green);
}

/* ─── iconTabs (used by bottom-content) — left icon column + right panel ── */

.shell-icontabs {
	display: flex;
	flex: 1 1 auto;
	min-height: 0;
	height: 100%;
}
.shell-icontabs__col {
	flex: 0 0 auto;
	width: 36px;
	background: var(--bg-tertiary, var(--bg-secondary));
	border-right: 1px solid var(--border-color);
	display: flex;
	flex-direction: column;
	overflow: hidden;
}
.shell-icontabs__col .shell-icon-btn {
	width: 36px;
	height: 36px;
}
.shell-icontabs__col .shell-icon-btn svg {
	width: 18px;
	height: 18px;
}
.shell-icontabs__body {
	flex: 1 1 auto;
	min-height: 0;
	overflow: hidden;
	position: relative;
	background: var(--bg-primary);
}
.shell-icontabs__panel {
	display: none;
	height: 100%;
	min-height: 0;
}
.shell-icontabs__panel.active {
	display: flex;
	flex-direction: column;
}

/* ─── Icon-bar buttons ─────────────────────────────────────────────────── */

.shell-icon-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 48px;
	height: 48px;
	background: transparent;
	border: none;
	color: var(--text-secondary);
	cursor: pointer;
	position: relative;
	flex: 0 0 auto;
}
@media (hover: hover) {
	.shell-icon-btn:hover {
		color: var(--accent-green);
		background: var(--bg-primary);
	}
}
.shell-icon-btn.active {
	color: var(--accent-green);
	background: var(--bg-primary);
	box-shadow: inset 2px 0 0 var(--accent-green);
}
.shell-icon-btn svg {
	width: 22px;
	height: 22px;
}
/* Overflow trigger used when more icons are contributed than fit in the bar.
 * Click opens a context menu listing the hidden views. */
.shell-icon-btn--more {
	color: var(--text-secondary);
}
@media (hover: hover) {
	.shell-icon-btn--more:hover {
		color: var(--accent-green);
		background: var(--bg-primary);
	}
}

.shell-icon-btn__badge {
	position: absolute;
	top: 6px;
	right: 6px;
	min-width: 14px;
	height: 14px;
	padding: 0 4px;
	border-radius: 7px;
	background: var(--accent-red, #e35);
	color: #fff;
	font-size: 10px;
	font-weight: 600;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* ─── Modes ────────────────────────────────────────────────────────────── */

.shell[data-mode="settings-full"] .shell__left-panel,
.shell[data-mode="settings-full"] .shell__right-panel,
.shell[data-mode="settings-full"] .shell__middle-lower {
	display: none;
}
.shell[data-mode="settings-full"] .shell__workspace {
	grid-template-columns: 1fr;
	grid-template-areas: "middle";
}
/* Reclaim the 220px middle.lower row when middle.lower is hidden, so the
 * settings page (or any full-middle view) gets the full middle column height. */
.shell[data-mode="settings-full"] .shell__middle,
.shell[data-mode="settings-middle"] .shell__middle {
	grid-template-rows: 1fr 0;
}

.shell[data-mode="settings-middle"] .shell__middle-lower {
	display: none;
}

/* ─── Dormant-extension placeholder — used by slot factories that fail
 *     to mount (extension disabled, missing api, etc.). Matches the same
 *     pattern used by mountable extensions (.ext-dormant-placeholder below). */
.shell-slot-empty {
	padding: 18px;
	color: var(--text-secondary);
	font-style: italic;
	font-size: 12px;
	text-align: center;
}

/* ── Dormant-extension placeholder ──────────────────────────────────────── */

/*
 * Shown inside a container whose mount-point extension is currently disabled.
 * The extension keeps a record of every mount it received, so toggling the
 * extension back on in Settings → Extensions auto-restores the widget — the
 * caller never has to re-invoke .create().
 */

.ext-dormant-placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 40px;
	padding: 12px 14px;
	background: repeating-linear-gradient(
		135deg,
		rgba(255, 255, 255, 0.025),
		rgba(255, 255, 255, 0.025) 8px,
		transparent 8px,
		transparent 16px
	);
	border: 1px dashed var(--border-color);
	border-radius: 4px;
	color: var(--text-muted);
	font-size: 12px;
	font-style: italic;
	text-align: center;
}

.ext-dormant {
	pointer-events: none;
	opacity: 0.85;
}

/* ═══ Responsive layout ═══════════════════════════════════════════════════
 * Driven by [data-viewport="wide|narrow"] on .shell, set by shell.mjs from the
 * viewport width (or forced by the `shell.responsive` config). Desktop is the
 * default; these rules add the narrow/compact treatment: the icon-bar +
 * left-panel become a slide-in drawer, the top menus collapse to a hamburger,
 * and the right panel folds away so the middle owns the width.
 */

/* Chrome that only exists in narrow mode — hidden by default. */
.shell__drawer-toggle,
.shell__scrim,
.app-menubar-toggle {
	display: none;
}

/* Collapse an icon-bar with no contributed icons (e.g. an app that disables the
 * icon-bar built-ins) so it never shows as an empty strip — desktop or narrow. */
.shell__icon-bar[data-empty="true"] {
	display: none;
}
.shell:has(.shell__icon-bar[data-empty="true"]) {
	grid-template-columns: 1fr;
	grid-template-areas:
		"app-bar"
		"workspace"
		"bottom-content"
		"bottom-bar";
}

/* ── Narrow viewport ────────────────────────────────────────────────────── */
.shell[data-viewport="narrow"] {
	grid-template-columns: 1fr;
	grid-template-areas:
		"app-bar"
		"workspace"
		"bottom-content"
		"bottom-bar";
}
.shell[data-viewport="narrow"] .shell__workspace {
	grid-template-columns: 1fr;
	grid-template-areas: "middle";
}
/* Right panel folds away on narrow — the middle owns the width. */
.shell[data-viewport="narrow"] .shell__right-panel {
	display: none;
}

/* Icon-bar + left-panel become an off-canvas drawer that slides in. */
.shell[data-viewport="narrow"] .shell__icon-bar,
.shell[data-viewport="narrow"] .shell__left-panel {
	position: fixed;
	top: var(--app-bar-h, 40px);
	bottom: 0;
	z-index: 40;
	transform: translateX(-100%);
	transition: transform 180ms ease;
	box-shadow: 2px 0 12px rgba(0, 0, 0, 0.35);
}
.shell[data-viewport="narrow"] .shell__icon-bar {
	left: 0;
}
.shell[data-viewport="narrow"] .shell__left-panel {
	left: 48px;
	width: min(78vw, var(--left-panel-width, 250px));
}
/* No icon-bar → the left-panel drawer starts flush at the edge. */
.shell[data-viewport="narrow"]:has(.shell__icon-bar[data-empty="true"]) .shell__left-panel {
	left: 0;
}
.shell[data-viewport="narrow"][data-drawer="open"] .shell__icon-bar,
.shell[data-viewport="narrow"][data-drawer="open"] .shell__left-panel {
	transform: translateX(0);
}

/* Scrim behind an open drawer. */
.shell[data-viewport="narrow"][data-drawer="open"] .shell__scrim {
	display: block;
	position: fixed;
	inset: var(--app-bar-h, 40px) 0 0 0;
	background: rgba(0, 0, 0, 0.45);
	z-index: 39;
}

/* Drawer toggle (hamburger) — only when narrow AND there's drawer content. */
.shell[data-viewport="narrow"]:not([data-drawer-empty="true"]) .shell__drawer-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	position: fixed;
	top: 0;
	left: 0;
	width: 44px;
	/* Self-contained height matching the app-bar (40px + notch strip) instead of the
	 * MEASURED --app-bar-h: the measurement can lag the safe-area padding, leaving the
	 * fixed toggle shorter than the bar and showing a step in the border just under it.
	 * With padding-top:safe-area (below) the glyph still centers in the 40px content. */
	height: calc(40px + env(safe-area-inset-top, 0px));
	background: transparent;
	border: none;
	color: var(--text-primary);
	font-size: 18px;
	line-height: 1;
	cursor: pointer;
	z-index: 45;
	-webkit-app-region: no-drag;
}
/* Make room in the app-bar so the toggle doesn't overlap the brand. */
.shell[data-viewport="narrow"]:not([data-drawer-empty="true"]) .shell__app-bar {
	padding-left: 44px;
}

/* ── Menu hamburger (top menus collapse to a dropdown) ──────────────────── */
/* The app-bar is the positioned reference for the narrow menu wrapper; the
 * wrapper is the positioned reference for the dropdown — so the dropdown anchors
 * to the hamburger, not a far ancestor (which left it detached + looking bg-less). */
.shell__app-bar {
	position: relative;
}
.app-menubar-wrap {
	position: relative;
	display: inline-flex;
	align-items: center;
}
.shell[data-viewport="narrow"] .app-menubar-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 32px;
	background: transparent;
	border: none;
	color: var(--text-primary);
	font-size: 17px;
	line-height: 1;
	cursor: pointer;
	-webkit-app-region: no-drag;
}
/* Relocate the whole menu wrapper (toggle + dropdown) to the FAR RIGHT of the
 * app-bar in narrow — the left hamburger owns the sidebar drawer, this one owns
 * the top menus. */
.shell[data-viewport="narrow"] .app-menubar-wrap {
	position: absolute;
	right: 6px;
	top: 0;
	bottom: 0;
}
/* The menu dropdown is a FULL-WIDTH sheet that drops from directly below the
 * app-bar — a real mobile menu, not a narrow column floating over the content.
 * `position: fixed` breaks it out of the right-anchored wrapper so left/right:0
 * span the viewport. It starts at --app-bar-h, which is the MEASURED app-bar
 * height and ALREADY includes the standalone safe-area strip — do NOT add
 * env(safe-area-inset-top) again or the sheet drops a whole notch-strip too low
 * (a gap opens between the app-bar and the menu). The fallback (before JS measures)
 * folds the inset in so it's still correct. */
.shell[data-viewport="narrow"] .app-menubar {
	display: none;
	position: fixed;
	top: var(--app-bar-h, calc(40px + env(safe-area-inset-top, 0px)));
	left: 0;
	right: 0;
	flex-direction: column;
	/* Override the base .app-menubar `height:100%` — with position:fixed that
	 * resolves to the full viewport height, making a solid full-screen sheet
	 * whose blank lower half can't be tapped-through to dismiss. Shrink to the
	 * sections/items; max-height + scroll only kick in if they get tall. */
	height: auto;
	background: var(--bg-secondary);
	border-top: 1px solid var(--border-color);
	border-bottom: 1px solid var(--border-color);
	box-shadow: 0 14px 30px rgba(0, 0, 0, 0.5);
	max-height: calc(100dvh - var(--app-bar-h, calc(40px + env(safe-area-inset-top, 0px))));
	overflow-y: auto;
	padding-left: env(safe-area-inset-left, 0px);
	padding-right: env(safe-area-inset-right, 0px);
	z-index: 70;
}
.shell[data-viewport="narrow"] .app-menubar.app-menubar--open {
	display: flex;
}
/* Section-name row (View / Help): full-width, left-aligned, with a chevron on
 * the right that flips down when the section is expanded. */
.shell[data-viewport="narrow"] .app-menubar .app-menu-btn {
	display: flex;
	align-items: center;
	width: 100%;
	height: auto;
	text-align: left;
	padding: 14px 16px;
	font-size: 15px;
	border-bottom: 1px solid var(--border-color);
}
.shell[data-viewport="narrow"] .app-menubar .app-menu-btn::after {
	content: "\203A"; /* › */
	margin-left: auto;
	font-size: 18px;
	line-height: 1;
	color: var(--text-secondary);
	transition: transform 150ms ease;
}
/* Expanded section header — a subtle lighter surface + a rotated chevron, NOT the
 * desktop green selection fill (which read as a giant highlighted blob on a phone).
 * Scoped to narrow so the desktop menubar keeps its accent-green active state. */
.shell[data-viewport="narrow"] .app-menubar .app-menu-btn.active {
	background: var(--bg-tertiary);
	color: var(--text-primary);
}
.shell[data-viewport="narrow"] .app-menubar .app-menu-btn.active::after {
	transform: rotate(90deg);
	color: var(--text-primary);
}
/* Inline accordion: a menu's items expand right under its name in the SAME
 * sheet (menubar._toggleInline) — no floating popup, one background. Items sit on
 * a darker surface + deeper indent so they read as nested under the section name. */
.shell[data-viewport="narrow"] .app-menubar-items {
	display: flex;
	flex-direction: column;
	background: var(--bg-primary);
	border-bottom: 1px solid var(--border-color);
}
.shell[data-viewport="narrow"] .app-menubar-item {
	display: flex;
	align-items: center;
	gap: 12px;
	width: 100%;
	text-align: left;
	padding: 13px 16px 13px 28px;
	background: transparent;
	border: 0;
	color: var(--text-primary);
	font-size: 14px;
	cursor: pointer;
}
.shell[data-viewport="narrow"] .app-menubar-item:active {
	background: var(--bg-hover);
}
.shell[data-viewport="narrow"] .app-menubar-item__icon {
	flex: 0 0 20px;
	text-align: center;
	color: var(--text-secondary);
}
.shell[data-viewport="narrow"] .app-menubar-item__label {
	flex: 1 1 auto;
}

/* ═══ Installed-PWA safe areas ═════════════════════════════════════════════
 * When running standalone (added to home screen), the app draws UNDER the device
 * status bar / notch / home indicator — otherwise the app-bar sits beneath the
 * status stuff. Pad the chrome into the safe areas; scoped to standalone (in a
 * browser tab the browser owns those regions). [data-display-mode] is set by
 * shell.mjs; the insets need viewport-fit=cover in the page meta (rummage build
 * sets it). env() resolves to 0 on devices without insets, so this is safe.
 */
/* The app-bar fills the top status-bar / notch strip with ITS OWN background, its
 * content padded below the strip. UNCONDITIONAL (not just standalone): with
 * viewport-fit=cover the web view also draws under the notch in some BROWSERS —
 * notably Chrome on iOS, whose URL bar sits at the bottom — where data-display-mode
 * is "browser", not "standalone". env() is 0 when there's no inset, so this is a
 * no-op on desktop / Safari-portrait and correct everywhere a notch actually
 * intrudes. The measured --app-bar-h picks up the taller bar in every mode. */
.shell__app-bar {
	padding-top: env(safe-area-inset-top, 0px);
	min-height: calc(40px + env(safe-area-inset-top, 0px));
}
/* The drawer toggle is position:fixed at top:0 and as tall as the whole app-bar
 * (--app-bar-h includes the safe-area strip), so without this its centered hamburger
 * glyph floats UP into the status-bar strip — misaligned with the brand and looking
 * oversized. Pad its content below the strip too, matching the app-bar. */
.shell__drawer-toggle {
	padding-top: env(safe-area-inset-top, 0px);
}
/* Same for the top-menu hamburger: its wrapper is position:absolute top:0 bottom:0
 * over the app-bar, so it spans the notch strip and its centered glyph rides up into
 * the status bar. Start it below the strip so it centers with the brand. Kept at the
 * narrow selector's specificity (which sets top:0) so this actually wins. */
.shell[data-viewport="narrow"] .app-menubar-wrap {
	top: env(safe-area-inset-top, 0px);
}
/* Inset the whole grid from the SIDES so nothing lands under the rounded corners
 * or the landscape notch; the shell's own background fills the strip. box-sizing
 * keeps 100vw/100vh honest with the pad. (The top strip is handled by the app-bar
 * above so its bg — not the body's — shows behind the status bar.) The BOTTOM
 * (home-indicator) strip is handled by the bottom bar below, NOT here — see why. */
.shell[data-display-mode="standalone"] {
	box-sizing: border-box;
	/* Reach the PHYSICAL bottom of the screen. --app-vh (window.innerHeight) is the base .shell height
	 * and is right in a browser tab (tracks the URL bar) and on iPad standalone — but on iPhone
	 * standalone innerHeight is SHORT (measured on-device: innerHeight 793 = 100dvh, but the physical
	 * screen / 100vh = 852), so the shell ends ~59px short and a band of background shows below the
	 * bottom bar. Use 100vh — in STANDALONE (no dynamic browser chrome) it's the full physical screen,
	 * the height the content actually paints with viewport-fit=cover. Take max() with --app-vh so a
	 * device where innerHeight is the correct/taller value still wins. NOTE: 100dvh is NOT enough here
	 * (it equals the short innerHeight on iPhone); 100vh is the large/full viewport. Scoped to
	 * standalone — in a browser TAB 100vh overshoots under the URL bar (that's why the base uses --app-vh). */
	height: max(var(--app-vh, 0px), 100vh);
	/* SIDE insets: in landscape the notch/camera intrudes from the side and would clip content.
	 * The TOP (notch / status bar) is handled by the .shell__app-bar rules. The BOTTOM bar's
	 * background reaches the physical edge via the height above; its corner-hugging CONTENT is
	 * inset horizontally (not padded vertically) to clear the rounded corners — see the rule below. */
	padding-left: env(safe-area-inset-left);
	padding-right: env(safe-area-inset-right);
}
/* The bottom bar's background reaches the physical bottom (the shell is full height). Its content —
 * the extension counters (left) + the bell (right) — hug the corners, where the phone's rounded
 * screen corners would clip them. Do NOT grow the bar vertically for this (that just reads as dead
 * padding); instead inset the content HORIZONTALLY so it clears the corners. env-left/right handles
 * the landscape notch; the fixed floor clears the portrait bottom-corner radius (there's no env() for
 * the corner radius). Scoped to standalone, so the browser/desktop keep the bar's normal 8px padding. */
.shell[data-display-mode="standalone"] .shell__bottom-bar {
	padding-left: max(28px, env(safe-area-inset-left));
	padding-right: max(28px, env(safe-area-inset-right));
}
