:root {
	/* Authoritative over any <meta name="color-scheme">: it flips with the theme,
	   and it is load-bearing, not cosmetic — ::placeholder is nowhere styled in
	   this sheet, <select> popups and the <audio> player are UA-drawn, and
	   scrollbars follow it too. */
	color-scheme: dark;
	--bg: #131318;
	--bg-panel: #1b1b22;
	--bg-elevated: #23232c;
	--border: #32323e;
	--text: #e6e6ea;
	--text-muted: #8f8f9c;
	--accent: #4a8fe7;
	--accent-soft: #2b4568;
	--cyan: #39c5cf;
	--green: #4caf7d;
	--red: #e0605e;
	--yellow: #d9a842;
	/* Text that sits on an --accent fill. Same in both themes: --accent stays
	   saturated enough for white either way. */
	--on-accent: #fff;
	--shadow-color: rgba(0, 0, 0, 0.4);
	--scrim: rgba(0, 0, 0, 0.55);
	--scrim-soft: rgba(0, 0, 0, 0.45);
}

/* Light theme. Written pre-paint by the inline boot script in index.html — a
   deferred module lands after the first frame, and #login-screen ships visible.
   Absent the attribute (JS off, storage throwing) the dark palette above stands,
   so dark is the default in the strict sense: it needs nothing to happen.

   The tiers invert *meaning*, not just lightness. Dark reads elevation as
   "lighter"; here --bg is the white canvas, --bg-panel the chrome tint and
   --bg-elevated the controls tier (composer, buttons, selects, popovers) — grey
   on white. Two constraints pin the spacing: inline <code> is --bg-panel with no
   border, so it must separate visibly from --bg; and the sidebar's row hover is
   --bg-elevated over a --bg-panel sidebar, so it must go darker still. */
:root[data-theme="light"] {
	color-scheme: light;
	--bg: #ffffff;
	--bg-panel: #f0f1f5;
	--bg-elevated: #e3e5ec;
	--border: #c9cdd6;
	--text: #1b1c20;
	/* Not pale: --text-muted carries the whole tool log and every timestamp, i.e.
	   real content. 6.4:1 on --bg, 5.0:1 on --bg-elevated. */
	--text-muted: #5c5f6b;
	/* Doubles as the link colour and as a fill under --on-accent: 5.5:1 both ways. */
	--accent: #1668c8;
	/* Always a fill under --text (user bubble, active row, search mark, menu hover). */
	--accent-soft: #dbe7f9;
	/* The four semantic colours are text and borders only (the sole fill is
	   #recorder-dot). The dark values are mid-bright and fail on white, so these
	   are darkened same-hue variants, each ≥4.5:1 on the surface it lands on. */
	--cyan: #0a6d7d;
	--green: #0f7a43;
	--red: #c62828;
	--yellow: #8f6200;
	--on-accent: #fff;
	/* 0.4 black at a 24px blur over a white app reads as dirt, and a 0.55 scrim
	   behind a white modal box looks like a rendering bug. */
	--shadow-color: rgba(15, 23, 42, 0.14);
	--scrim: rgba(15, 23, 42, 0.32);
	--scrim-soft: rgba(15, 23, 42, 0.22);
}

* {
	box-sizing: border-box;
}

html, body {
	height: 100%;
	margin: 0;
}

body {
	height: 100vh;
	height: 100dvh;
	/* --app-height mirrors visualViewport.height (app.js): Chrome-Android
	   standalone PWAs sometimes launch with a stale, too-tall layout viewport
	   that 100dvh inherits until the app process dies — a refresh reuses the
	   same window. The compositor-tracked visual viewport stays correct. */
	height: var(--app-height, 100dvh);
	display: flex;
	background: var(--bg);
	color: var(--text);
	font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
	font-size: 15px;
}

.hidden {
	display: none !important;
}

/* ---- sidebar ---- */

#sidebar {
	width: 280px;
	min-width: 280px;
	display: flex;
	flex-direction: column;
	background: var(--bg-panel);
	border-right: 1px solid var(--border);
}

#sidebar-backdrop {
	display: none;
}

#sidebar-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 12px 14px;
	border-bottom: 1px solid var(--border);
}

#sidebar-title {
	font-weight: 700;
	font-size: 17px;
}

#new-conversation {
	background: var(--accent-soft);
	color: var(--text);
	border: 1px solid var(--accent);
	border-radius: 8px;
	width: 28px;
	height: 28px;
	padding: 0;
	cursor: pointer;
	font-size: 17px;
	line-height: 1;
}

/* The base state keeps var(--text) over the pale --accent-soft fill; once the
   hover swaps in the saturated --accent, the text has to swap with it. */
#new-conversation:hover {
	background: var(--accent);
	color: var(--on-accent);
}

#sidebar-search {
	position: relative;
	padding: 8px 8px 0;
}

#search-input {
	width: 100%;
	background: var(--bg);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 6px 28px 6px 10px;
	font-size: 14px;
	font-family: inherit;
}

#search-input:focus {
	outline: none;
	border-color: var(--accent);
}

#search-clear {
	position: absolute;
	right: 12px;
	top: 50%;
	/* re-center on the input, whose box starts below the 8px top padding */
	transform: translateY(calc(-50% + 4px));
	background: none;
	border: none;
	color: var(--text-muted);
	cursor: pointer;
	font-size: 13px;
	padding: 2px 4px;
}

#search-clear:hover {
	color: var(--text);
}

#conversation-list {
	list-style: none;
	margin: 0;
	padding: 8px;
	overflow-y: auto;
	overscroll-behavior: contain;
	flex: 1;
}

.conv-snippet {
	font-size: 12px;
	color: var(--text-muted);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.conv-snippet mark {
	background: var(--accent-soft);
	color: var(--text);
	border-radius: 2px;
}

.sidebar-empty {
	padding: 10px;
	color: var(--text-muted);
	font-size: 13px;
	cursor: default;
}

.conversation-item,
.librarian-item,
.jobs-item {
	padding: 8px 10px;
	border-radius: 8px;
	cursor: pointer;
	display: flex;
	flex-direction: column;
	gap: 2px;
	position: relative;
}

.conversation-item:hover,
.librarian-item:hover,
.jobs-item:hover {
	background: var(--bg-elevated);
}

.conversation-item.active,
.librarian-item.active {
	background: var(--accent-soft);
}

/* The two collapsible sections pinned under the conversations — Attività (the
   background jobs, js/jobs.js) and Agenti (the run history): one set of rules
   on shared classes, the ids stay for the scripts. */
.sidebar-section {
	border-top: 1px solid var(--border);
	display: flex;
	flex-direction: column;
	max-height: 40%;
}

.sidebar-section-title {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
	padding: 10px 14px 4px;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--text-muted);
}

/* The title doubles as the collapse toggle: a button rather than a click handler
   on the row. */
.sidebar-section-toggle {
	flex: 1;
	min-width: 0;
	display: flex;
	align-items: center;
	gap: 6px;
	background: none;
	border: none;
	padding: 4px 0;
	margin: -4px 0;
	cursor: pointer;
	font: inherit;
	letter-spacing: inherit;
	text-transform: inherit;
	color: inherit;
	text-align: left;
}

.sidebar-section-toggle:hover {
	color: var(--text);
}

.sidebar-section-caret {
	display: inline-block;
	font-size: 9px;
	transition: transform 0.15s ease;
}

.sidebar-section:not(.collapsed) .sidebar-section-caret {
	transform: rotate(90deg);
}

/* Collapsed (the boot state): only the title row survives, so the list stops
   claiming its 40% of the sidebar. */
.sidebar-section.collapsed .sidebar-section-body {
	display: none;
}

.sidebar-section-body {
	display: flex;
	flex-direction: column;
	/* Lets the body shrink inside the section's max-height so the list scrolls. */
	overflow: hidden;
}

#librarian-list,
#jobs-list {
	list-style: none;
	margin: 0;
	padding: 4px 8px 8px;
	overflow-y: auto;
	overscroll-behavior: contain;
}

.librarian-item .conv-title,
.jobs-item .conv-title {
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.librarian-item .conv-when,
.jobs-item .conv-when {
	font-size: 11px;
	color: var(--text-muted);
}

/* ---- attività section (background jobs) ---- */

/* The live count, in the title row even while collapsed. */
#jobs-badge {
	display: inline-block;
	min-width: 18px;
	padding: 1px 6px;
	border-radius: 9px;
	background: var(--accent);
	color: var(--on-accent);
	font-size: 10px;
	line-height: 16px;
	text-align: center;
	letter-spacing: 0;
}

.jobs-item.failed .conv-title {
	color: var(--red);
}

.jobs-item.cancelled .conv-title,
.jobs-item.queued .conv-title {
	color: var(--text-muted);
}

/* A thin progress bar under a live row (and in the modal): determinate when
   the job reports a fraction, the upload card's shimmer otherwise. */
.job-bar {
	height: 3px;
	border-radius: 2px;
	background: var(--border);
	overflow: hidden;
	margin: 3px 0 1px;
}

.job-fill {
	height: 100%;
	width: 0;
	border-radius: 2px;
	background: var(--accent);
	transition: width 0.2s ease;
}

.job-fill.indeterminate {
	animation: upload-shimmer 1.2s ease-in-out infinite;
}

/* Read-only Librarian run view: no composer, the transcript is a replay. */
body.run-view #composer {
	display: none;
}

/* A run is not a conversation: nothing meaningful to copy. */
body.run-view #copy-conv-btn {
	display: none;
}

/* A run is not a conversation to call into either. */
body.run-view #call-btn {
	display: none;
}

/* Read-only wiki viewer: it takes over the main pane like the run view, but
   nothing in the header means anything on a wiki page — not the conversation
   controls, not the pickers (no turn to configure), not even the title, since
   #wiki-bar's first breadcrumb already reads "wiki". So the whole header goes,
   which on a phone gives the page back a full row; the ← back to Dobby sits in
   #wiki-bar instead. The conversation sidebar goes too: the wiki tree takes its
   place as *the* sidebar, and two nested navigation columns would leave the
   page no room. */
body.wiki-view #header,
body.wiki-view #sidebar,
body.wiki-view #sidebar-backdrop,
body.wiki-view #transcript,
body.wiki-view #jump-to-bottom-wrap,
body.wiki-view #composer {
	display: none;
}

/* Full-row hit target as a real link, so middle-click / "open in new tab"
   work. The rename/delete buttons follow it in the DOM and stay clickable. */
.conversation-item .conv-link,
.librarian-item .conv-link {
	position: absolute;
	inset: 0;
	border-radius: inherit;
}

.conversation-item .conv-title {
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	/* Room for the three hover icons (pin + rename + delete). */
	padding-right: 70px;
}

.conversation-item .conv-when {
	font-size: 11px;
	color: var(--text-muted);
}

.conversation-item .conv-pin,
.conversation-item .conv-rename,
.conversation-item .conv-delete {
	position: absolute;
	top: 8px;
	background: none;
	border: none;
	color: var(--text-muted);
	cursor: pointer;
	font-size: 13px;
	visibility: hidden;
}

.conversation-item .conv-delete {
	right: 6px;
}

.conversation-item .conv-rename {
	right: 28px;
}

.conversation-item .conv-pin {
	right: 50px;
	/* Faded while the row is merely hovered: the button is also the state
	   indicator, so "unpinned" has to read differently from "pinned". */
	opacity: 0.4;
}

.conversation-item:hover .conv-pin,
.conversation-item:hover .conv-rename,
.conversation-item:hover .conv-delete {
	visibility: visible;
}

/* A pin is a standing choice, not a hover action: it stays lit, and marks its
   row (the pinned block leads the list). */
.conversation-item.pinned .conv-pin {
	visibility: visible;
	opacity: 1;
}

/* Inset shadow rather than a border: no 2px reflow against the unpinned rows. */
.conversation-item.pinned {
	box-shadow: inset 2px 0 0 var(--accent);
}

.conversation-item .conv-delete:hover {
	color: var(--red);
}

.conversation-item input.conv-rename-input {
	background: var(--bg);
	color: var(--text);
	border: 1px solid var(--accent);
	border-radius: 6px;
	padding: 3px 6px;
	font-size: 14px;
	width: 100%;
}

/* ---- main ---- */

#main {
	flex: 1;
	display: flex;
	flex-direction: column;
	min-width: 0;
}

#header {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 10px 16px;
	border-bottom: 1px solid var(--border);
	background: var(--bg-panel);
}

#thread-name {
	font-weight: 600;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 30%;
}

#busy-spinner::after {
	content: '';
	display: inline-block;
	width: 14px;
	height: 14px;
	border: 2px solid var(--yellow);
	border-top-color: transparent;
	border-radius: 50%;
	animation: spin 0.8s linear infinite;
	vertical-align: middle;
}

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

#header-controls {
	margin-left: auto;
	display: flex;
	align-items: center;
	gap: 8px;
}

#header-controls select {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 5px 8px;
	font-size: 13px;
	max-width: 180px;
}

/* Context occupancy badge (share of the model window the conversation uses). */
#context-badge {
	color: var(--green);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 4px 7px;
	font-size: 12px;
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
	cursor: default;
}

/* Copies the whole conversation as markdown. */
#copy-conv-btn {
	background: none;
	color: var(--text-muted);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 4px 7px;
	font-size: 12px;
	white-space: nowrap;
	cursor: pointer;
}

#copy-conv-btn:hover {
	color: var(--text);
	border-color: var(--cyan);
}

/* Live voice call (call.js). Same chip family as #copy-conv-btn; while a call
   is on it pulses green as a standing "mic is live" caution. */
#call-btn {
	background: none;
	color: var(--text-muted);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 4px 7px;
	font-size: 12px;
	white-space: nowrap;
	cursor: pointer;
}

#call-btn:hover {
	color: var(--text);
	border-color: var(--green);
}

#call-btn.on-call {
	color: var(--green);
	border-color: var(--green);
	animation: call-pulse 1.6s ease-in-out infinite;
}

@keyframes call-pulse {
	50% { opacity: 0.45; }
}

/* Session auto-approve toggle: always visible — muted 🔒 while off, yellow 🔓
   while armed as a standing caution that prompts are being answered unattended. */
#auth-all-badge {
	background: none;
	color: var(--text-muted);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 4px 7px;
	font-size: 12px;
	white-space: nowrap;
	cursor: pointer;
}

#auth-all-badge:hover {
	color: var(--yellow);
	border-color: var(--yellow);
}

#auth-all-badge.on {
	color: var(--yellow);
	border-color: var(--yellow);
}

#auth-all-badge.on:hover {
	color: var(--red);
	border-color: var(--red);
}

#context-badge.warn {
	color: var(--yellow);
}

#context-badge.danger {
	color: var(--red);
}

/* Engine+model pickers. On desktop both wrappers are display: contents, so the
   selects stay direct flex items of #header-controls; below 720px they collapse
   into the ⚙️ button's popover — see the mobile media block. */
#picker-wrap,
#picker-panel {
	display: contents;
}

#picker-btn {
	display: none;
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 5px 10px;
	cursor: pointer;
	font-size: 13px;
	/* The label is the active model's name — don't let a long id reconquer the
	   width the collapse just freed. */
	max-width: 140px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

#picker-btn:hover,
#picker-btn[aria-expanded="true"] {
	border-color: var(--cyan);
}

/* ---- transcript ---- */

#transcript {
	flex: 1;
	overflow-y: auto;
	overscroll-behavior: contain;
	padding: 20px 0 30px;
}

#transcript > * {
	max-width: 820px;
	margin-left: auto;
	margin-right: auto;
	padding-left: 20px;
	padding-right: 20px;
}

/* Zero-height anchor between transcript and composer: the button floats over
   the transcript's bottom edge without taking flex space, and stays put when
   the composer grows (or disappears, in the run view). */
#jump-to-bottom-wrap {
	position: relative;
	height: 0;
	z-index: 5;
}

#jump-to-bottom {
	position: absolute;
	bottom: 14px;
	left: 50%;
	transform: translateX(-50%);
	width: 36px;
	height: 36px;
	border-radius: 50%;
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	box-shadow: 0 2px 10px var(--shadow-color);
	font-size: 16px;
	line-height: 1;
	cursor: pointer;
}

#jump-to-bottom:hover {
	border-color: var(--accent);
}

.msg-user {
	display: flex;
	justify-content: flex-end;
	margin: 14px auto 6px;
}

.msg-user .bubble {
	background: var(--accent-soft);
	border-radius: 14px 14px 4px 14px;
	padding: 9px 14px;
	max-width: 75%;
	white-space: pre-wrap;
	overflow-wrap: break-word;
}

.msg-user .bubble img {
	display: block;
	max-width: 100%;
	max-height: 260px;
	border-radius: 8px;
	margin: 4px 0;
}

.msg-user .bubble a.attachment-link,
.msg-attachment a.attachment-link {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	background: var(--bg-elevated);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 6px 10px;
	margin: 4px 0;
	font-size: 13px;
	color: var(--text);
	text-decoration: none;
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.msg-user .bubble a.attachment-link:hover,
.msg-attachment a.attachment-link:hover {
	border-color: var(--accent);
}

/* Voice notes and other audio uploads (the /media/ mount, served inline). */
.attachment-audio {
	display: block;
	width: 260px;
	max-width: 100%;
	height: 36px;
	margin: 4px 0;
}

.msg-user .bubble.command {
	font-family: ui-monospace, Consolas, monospace;
	font-size: 13px;
	color: var(--cyan);
	background: var(--bg-elevated);
}

/* Optimistic placeholder while the send waits — staged uploads still in
   flight, then the POST round-trip (app.js#send): dimmed bubble, "Invio in
   corso" caption with a small spinner. */
.msg-user.pending .bubble {
	opacity: 0.6;
}

.msg-time.sending::after {
	content: '';
	display: inline-block;
	width: 9px;
	height: 9px;
	margin-left: 6px;
	border: 1.5px solid var(--text-muted);
	border-top-color: transparent;
	border-radius: 50%;
	animation: spin 0.8s linear infinite;
	vertical-align: -1px;
}

.msg-time {
	font-size: 11px;
	color: var(--text-muted);
	opacity: 0.7;
	margin: 2px auto 8px;
	user-select: none;
}

.msg-time.right {
	text-align: right;
}

.msg-time.left {
	text-align: left;
}

/* Model that actually served an assistant message (SDK engine only): rides
   inside the timestamp caption, one notch smaller and dimmer. */
.msg-model {
	font-size: 10px;
	opacity: 0.8;
	margin-left: 6px;
}

/* Utterance spoken during a live voice call (call: true on the event): same
   muted caption slot as .msg-model. */
.msg-call {
	font-size: 10px;
	opacity: 0.8;
	margin-left: 6px;
}

/* Copy-message icon inside the timestamp caption: revealed by hovering the
   message (the caption's previous sibling) or the caption itself; always
   visible on touch (see the hover:none block). */
.msg-copy {
	background: none;
	border: none;
	padding: 0;
	margin: 0 6px;
	font-size: 11px;
	line-height: 1;
	cursor: pointer;
	visibility: hidden;
	opacity: 0.8;
}

.msg-copy:hover {
	opacity: 1;
}

.msg-user:hover + .msg-time .msg-copy,
.msg-assistant:hover + .msg-time .msg-copy,
.msg-time:hover .msg-copy {
	visibility: visible;
}

.msg-assistant {
	margin: 6px auto;
	line-height: 1.55;
	overflow-wrap: break-word;
}

.msg-assistant + .msg-assistant {
	margin-top: 0;
}

/* The rendered-markdown rules below are shared with the wiki viewer's page pane
   (#wiki-doc), which is markdown from the same renderer. It deliberately does
   not borrow the .msg-assistant *class*: that one carries chat-bubble semantics
   and the :hover + .msg-time .msg-copy selectors, neither of which applies to a
   wiki page. */
.msg-assistant pre,
#wiki-doc pre {
	background: var(--bg-panel);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 10px 12px;
	overflow-x: auto;
	font-size: 13px;
}

.msg-assistant code,
#wiki-doc code {
	font-family: ui-monospace, Consolas, monospace;
	font-size: 0.92em;
	background: var(--bg-panel);
	border-radius: 4px;
	padding: 1px 4px;
}

.msg-assistant pre code,
#wiki-doc pre code {
	background: none;
	padding: 0;
}

.msg-assistant .table-wrap,
#wiki-doc .table-wrap {
	overflow-x: auto;
	max-width: 100%;
	margin: 8px 0;
}

.msg-assistant table,
#wiki-doc table {
	border-collapse: collapse;
}

.msg-assistant th, .msg-assistant td,
#wiki-doc th, #wiki-doc td {
	border: 1px solid var(--border);
	padding: 5px 10px;
}

.msg-assistant blockquote,
#wiki-doc blockquote {
	border-left: 3px solid var(--border);
	margin: 8px 0;
	padding-left: 12px;
	color: var(--text-muted);
}

.msg-assistant a,
#wiki-doc a {
	color: var(--accent);
}

.msg-image img {
	max-width: 100%;
	border-radius: 10px;
	margin: 6px 0;
}

.msg-attachment {
	margin: 6px auto;
}

.msg-attachment img {
	max-width: 100%;
	max-height: 320px;
	border-radius: 10px;
	margin: 6px 0;
}

.msg-system {
	font-size: 13px;
	margin: 4px auto;
	color: var(--text-muted);
}

.msg-system.success {
	color: var(--green);
}

.msg-system.info {
	color: var(--text);
}

.msg-reminder {
	font-size: 14px;
	font-weight: 600;
	margin: 6px auto;
	color: var(--text);
}

.msg-error {
	font-size: 13px;
	margin: 4px auto;
	color: var(--red);
}

/* ---- wiki viewer (the ⋮ › 📚 Wiki › 📖 entry) ---- */

/* Sits beside #transcript as the other main-pane takeover; app.js toggles both
   the .hidden here and body.wiki-view above, so the two views never overlap. */
#wiki-view {
	flex: 1;
	display: flex;
	/* Without this the flex children refuse to shrink and both panes scroll the
	   page instead of themselves. */
	min-height: 0;
}

/* 280px is #sidebar's width, deliberately: the tree replaces it while the viewer
   is open, and matching means the left column doesn't jump when ← swaps them. */
#wiki-tree-pane {
	flex: none;
	width: 280px;
	border-right: 1px solid var(--border);
	background: var(--bg-panel);
	overflow-y: auto;
	overscroll-behavior: contain;
	padding: 8px 0;
}

/* Only ever a hit target on mobile, where the tree is an overlay. */
#wiki-tree-backdrop {
	display: none;
}

#wiki-tree {
	list-style: none;
	margin: 0;
	padding: 0;
}

.wiki-node-children {
	list-style: none;
	margin: 0;
	padding: 0;
}

.wiki-node-row {
	display: flex;
	align-items: center;
	gap: 4px;
	padding: 3px 8px;
	font-size: 13px;
	color: var(--text);
	text-decoration: none;
	cursor: pointer;
	border-radius: 5px;
}

.wiki-node-row:hover {
	background: var(--bg-elevated);
}

.wiki-node-row.selected {
	background: var(--accent-soft);
}

.wiki-node-caret {
	flex: none;
	font-size: 9px;
	color: var(--text-muted);
	transition: transform 0.15s ease;
}

.wiki-node.open > .wiki-node-row .wiki-node-caret {
	transform: rotate(90deg);
}

.wiki-node-label {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

#wiki-main {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
}

#wiki-bar {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 8px 16px;
	border-bottom: 1px solid var(--border);
	background: var(--bg-panel);
}

#wiki-tree-toggle {
	/* The tree is permanently visible on desktop; the button only exists for the
	   mobile overlay. */
	display: none;
}

/* The one exit from the viewer (the header with ☰ is hidden): a bare arrow
   rather than another boxed button, so it reads as navigation, not a tool. */
#wiki-bar #wiki-back {
	background: none;
	border: none;
	font-size: 18px;
	padding: 0 4px;
}

#wiki-bar #wiki-back:hover {
	color: var(--accent);
}

#wiki-bar button {
	flex: none;
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 4px 8px;
	font-size: 12px;
	line-height: 1.4;
	cursor: pointer;
}

#wiki-bar button:hover:not(:disabled) {
	border-color: var(--accent);
}

#wiki-bar button:disabled {
	opacity: 0.35;
	cursor: default;
}

#wiki-raw-toggle.active,
#wiki-graph-toggle.active {
	border-color: var(--accent);
	color: var(--accent);
}

#wiki-breadcrumb {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 4px;
	font-size: 13px;
	font-family: ui-monospace, Consolas, monospace;
}

.wiki-crumb {
	color: var(--accent);
	text-decoration: none;
	cursor: pointer;
}

.wiki-crumb:hover {
	text-decoration: underline;
}

.wiki-crumb.current {
	color: var(--text);
	cursor: default;
}

.wiki-crumb-sep {
	color: var(--text-muted);
}

#wiki-body {
	flex: 1;
	overflow-y: auto;
	overscroll-behavior: contain;
	padding: 20px 0 40px;
}

#wiki-doc, #wiki-raw {
	max-width: 820px;
	margin: 0 auto;
	padding: 0 20px;
	line-height: 1.55;
	overflow-wrap: break-word;
}

#wiki-doc h1 {
	font-size: 22px;
	margin: 0 0 12px;
}

/* ---- wiki graph view (web/js/wikiGraph.js) ----
   A mode of #wiki-body: the WebGL canvas fills the pane, the label overlay and
   the tools panel float over it. The document is hidden underneath, not
   removed — leaving the graph restores it as it was. */

#wiki-body.graph-mode {
	padding: 0;
	overflow: hidden;
	position: relative;
}

#wiki-body.graph-mode #wiki-doc,
#wiki-body.graph-mode #wiki-raw {
	display: none;
}

#wiki-graph {
	position: absolute;
	inset: 0;
	overflow: hidden;
}

#wiki-graph-canvas {
	position: absolute;
	inset: 0;
}

/* The library injects a .scene-tooltip we don't use (nodeLabel is empty) and
   nav hints we turn off; nothing of ours must intercept the canvas pointer. */
#wiki-graph-labels {
	position: absolute;
	inset: 0;
	pointer-events: none;
	overflow: hidden;
}

.wiki-graph-label {
	position: absolute;
	left: 0;
	top: 0;
	/* Anchored a little under the node's center; the transform carries the
	   screen position. */
	margin: 8px 0 0 -60px;
	width: 120px;
	text-align: center;
	font-size: 11px;
	line-height: 1.2;
	color: var(--text);
	text-shadow: 0 0 3px var(--bg), 0 0 3px var(--bg), 0 1px 2px var(--bg);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	will-change: transform, opacity;
}

.wiki-graph-label.focus {
	color: var(--accent);
	font-weight: 600;
	font-size: 12px;
}

.wiki-graph-label.anchor {
	font-weight: 600;
	font-size: 12px;
}

.wiki-graph-label.tag {
	color: var(--text-muted);
	font-style: italic;
}

#wiki-graph-status {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--text-muted);
	font-size: 14px;
	pointer-events: none;
}

#wiki-graph-tools {
	position: absolute;
	top: 12px;
	left: 12px;
	z-index: 2;
	max-height: calc(100% - 24px);
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 6px;
}

#wiki-graph-tools-toggle {
	/* Desktop: the panel is always open; the button exists for the mobile fold. */
	display: none;
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 4px 8px;
	font-size: 14px;
	cursor: pointer;
}

#wiki-graph-panel {
	width: 220px;
	max-height: 100%;
	overflow-y: auto;
	display: flex;
	flex-direction: column;
	gap: 8px;
	padding: 10px;
	background: color-mix(in srgb, var(--bg-panel) 88%, transparent);
	backdrop-filter: blur(6px);
	border: 1px solid var(--border);
	border-radius: 8px;
	box-shadow: 0 4px 16px var(--shadow-color);
	font-size: 12px;
}

#wiki-graph-search {
	width: 100%;
	box-sizing: border-box;
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 5px 8px;
	font: inherit;
	font-size: 12px;
}

#wiki-graph-search:focus {
	outline: none;
	border-color: var(--accent);
}

.wiki-graph-opts {
	display: flex;
	flex-wrap: wrap;
	gap: 4px 10px;
}

.wiki-graph-opts label {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	cursor: pointer;
	white-space: nowrap;
}

.wiki-graph-opts input[type="checkbox"] {
	margin: 0;
	accent-color: var(--accent);
}

.wiki-graph-opts input[type="number"] {
	width: 34px;
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 4px;
	padding: 1px 2px;
	font: inherit;
	font-size: 11px;
}

#wiki-graph-legend {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.wiki-graph-legend-row {
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 2px 4px;
	border-radius: 4px;
	cursor: pointer;
	user-select: none;
}

.wiki-graph-legend-row:hover {
	background: var(--bg-elevated);
}

.wiki-graph-legend-row.off {
	opacity: 0.4;
}

.wiki-graph-legend-row.off .wiki-graph-swatch {
	background: transparent !important;
	box-shadow: inset 0 0 0 2px var(--text-muted);
}

.wiki-graph-swatch {
	flex: none;
	width: 10px;
	height: 10px;
	border-radius: 50%;
}

.wiki-graph-legend-name {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.wiki-graph-legend-count {
	color: var(--text-muted);
	font-variant-numeric: tabular-nums;
}

.wiki-graph-foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 6px;
	color: var(--text-muted);
	font-size: 11px;
}

#wiki-graph-fit {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 2px 7px;
	font-size: 12px;
	cursor: pointer;
}

#wiki-graph-fit:hover {
	border-color: var(--accent);
}

#wiki-doc h2 {
	font-size: 18px;
	margin: 22px 0 8px;
	padding-bottom: 4px;
	border-bottom: 1px solid var(--border);
}

#wiki-doc h3 {
	font-size: 15px;
	margin: 18px 0 6px;
}

#wiki-doc hr {
	border: none;
	border-top: 1px solid var(--border);
	margin: 18px 0;
}

/* A link the viewer could not resolve inside the wiki root (a `../` that climbs
   out). Rendered as text so it can't lead anywhere misleading. */
#wiki-doc a.wiki-link-broken {
	color: var(--text-muted);
	text-decoration: line-through;
	cursor: default;
}

/* Frontmatter, kept but folded away: rendered inline it would put an <hr> and a
   stray YAML paragraph at the top of every page. */
details.wiki-frontmatter {
	margin: 0 0 14px;
	font-size: 12px;
}

details.wiki-frontmatter > summary {
	cursor: pointer;
	color: var(--text-muted);
	list-style: none;
}

details.wiki-frontmatter > summary::before {
	content: '▸ ';
	font-size: 9px;
}

details.wiki-frontmatter[open] > summary::before {
	content: '▾ ';
}

details.wiki-frontmatter > pre {
	margin-top: 6px;
	white-space: pre-wrap;
}

/* Non-markdown files under raw/ (chat dumps, JSON) — read verbatim; running
   them through the markdown renderer would mangle them, not help. */
pre.wiki-plain, #wiki-raw {
	white-space: pre-wrap;
	font-family: ui-monospace, Consolas, monospace;
	font-size: 12.5px;
	color: var(--text);
}

/* Directory listing, in the visual family of the forget picker's rows. */
ul.wiki-entries {
	list-style: none;
	margin: 0;
	padding: 0;
}

.wiki-entry {
	display: flex;
	align-items: baseline;
	gap: 10px;
	padding: 5px 8px;
	border-radius: 6px;
}

.wiki-entry:hover {
	background: var(--bg-elevated);
}

.wiki-entry-link {
	flex: 1;
	min-width: 0;
	color: var(--text);
	text-decoration: none;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.wiki-entry-link:hover {
	color: var(--accent);
}

.wiki-entry-when {
	flex: none;
	font-size: 11px;
	color: var(--text-muted);
}

.wiki-empty {
	font-size: 13px;
	color: var(--text-muted);
}

/* ---- reasoning ---- */

details.reasoning {
	margin: 6px auto;
	font-size: 13px;
	color: var(--text-muted);
}

details.reasoning summary {
	cursor: pointer;
	user-select: none;
	list-style: none;
}

details.reasoning summary::-webkit-details-marker {
	display: none;
}

details.reasoning .reasoning-body {
	white-space: pre-wrap;
	border-left: 2px solid var(--border);
	margin-top: 6px;
	padding-left: 12px;
	overflow-wrap: break-word;
}

details.reasoning.active summary {
	color: var(--yellow);
}

/* ---- tool rows ---- */

.tool-row {
	font-family: ui-monospace, Consolas, monospace;
	font-size: 13px;
	margin: 3px auto;
	color: var(--text-muted);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.tool-row .glyph.pending::after {
	content: '';
	display: inline-block;
	width: 9px;
	height: 9px;
	border: 2px solid var(--yellow);
	border-top-color: transparent;
	border-radius: 50%;
	animation: spin 0.8s linear infinite;
}

.tool-row .glyph.ok {
	color: var(--green);
}

.tool-row .glyph.fail {
	color: var(--red);
}

.tool-row .tool-name {
	color: var(--text);
	font-weight: 600;
}

.tool-error {
	font-family: ui-monospace, Consolas, monospace;
	font-size: 12px;
	margin: 0 auto 3px;
	padding-left: 40px;
	color: var(--red);
	opacity: 0.8;
	white-space: pre-wrap;
}

/* Collapsible tool input/output (debug view) — same disclosure idiom as reasoning. */
details.tool-input,
details.tool-output {
	font-family: ui-monospace, Consolas, monospace;
	font-size: 12px;
	margin: 0 auto 3px;
	padding-left: 40px;
	color: var(--text-muted);
}

details.tool-input summary,
details.tool-output summary {
	cursor: pointer;
	user-select: none;
	opacity: 0.7;
}

details.tool-input pre,
details.tool-output pre {
	margin: 4px 0 6px;
	padding: 6px 10px;
	border-left: 2px solid var(--border);
	max-height: 300px;
	overflow: auto;
	white-space: pre-wrap;
	overflow-wrap: break-word;
}

/* ---- sub-agent box ---- */

.subagent {
	border: 1px solid var(--cyan);
	border-radius: 10px;
	margin: 10px auto;
	padding: 10px 14px;
}

.subagent .subagent-header {
	font-weight: 600;
	color: var(--cyan);
	margin-bottom: 6px;
}

.subagent .subagent-header .glyph.pending::after {
	content: '';
	display: inline-block;
	width: 10px;
	height: 10px;
	border: 2px solid var(--cyan);
	border-top-color: transparent;
	border-radius: 50%;
	animation: spin 0.8s linear infinite;
	margin-left: 6px;
}

.subagent .subagent-header .glyph.ok {
	color: var(--green);
	margin-left: 6px;
}

.subagent .subagent-header .glyph.fail {
	color: var(--red);
	margin-left: 6px;
}

.subagent .msg-assistant,
.subagent .tool-row,
.subagent .msg-error {
	padding-left: 0;
	padding-right: 0;
	font-size: 13px;
}

.subagent details.tool-input,
.subagent details.tool-output {
	padding-left: 18px;
}

/* ---- composer ---- */

#composer {
	display: flex;
	flex-direction: column;
	gap: 8px;
	max-width: 820px;
	width: 100%;
	margin: 0 auto;
	padding: 0 20px calc(18px + env(safe-area-inset-bottom, 0px));
}

#composer-row {
	display: flex;
	align-items: flex-end;
	gap: 8px;
}

#composer.dragover #composer-input {
	border-color: var(--accent);
	border-style: dashed;
}

#attachment-strip {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}

.attachment-thumb {
	position: relative;
}

.attachment-thumb img {
	display: block;
	width: 56px;
	height: 56px;
	object-fit: cover;
	border-radius: 8px;
	border: 1px solid var(--border);
}

.attachment-file {
	position: relative;
	display: flex;
	align-items: center;
	gap: 6px;
	background: var(--bg-elevated);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 8px 26px 8px 10px;
	font-size: 13px;
	max-width: 220px;
}

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

/* Upload progress: a thin bottom bar that vanishes at 100%, so a resting
   strip looks exactly like it always did. */
.attachment-progress {
	position: absolute;
	left: 3px;
	right: 3px;
	bottom: 3px;
	height: 3px;
	border-radius: 2px;
	background: var(--border);
	overflow: hidden;
	display: none;
}

.attachment-thumb.uploading .attachment-progress,
.attachment-file.uploading .attachment-progress {
	display: block;
}

.attachment-progress-fill {
	height: 100%;
	width: 0;
	border-radius: 2px;
	background: var(--accent);
	transition: width 0.2s ease;
}

.attachment-thumb.uploading img,
.attachment-file.uploading .attachment-name {
	opacity: 0.7;
}

.attachment-thumb.upload-error img,
.attachment-file.upload-error {
	border-color: var(--red);
}

.attachment-retry {
	position: absolute;
	top: -6px;
	right: 14px;
	width: 18px;
	height: 18px;
	display: none;
	align-items: center;
	justify-content: center;
	background: var(--bg-elevated);
	border: 1px solid var(--red);
	border-radius: 50%;
	color: var(--red);
	font-size: 10px;
	line-height: 1;
	cursor: pointer;
	padding: 0;
}

.attachment-thumb.upload-error .attachment-retry,
.attachment-file.upload-error .attachment-retry {
	display: flex;
}

.attachment-remove {
	position: absolute;
	top: -6px;
	right: -6px;
	width: 18px;
	height: 18px;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--bg-elevated);
	border: 1px solid var(--border);
	border-radius: 50%;
	color: var(--text);
	font-size: 10px;
	line-height: 1;
	cursor: pointer;
	padding: 0;
}

.attachment-remove:hover {
	border-color: var(--red);
	color: var(--red);
}

#attach-button {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 10px;
	width: 42px;
	height: 42px;
	flex: none;
	font-size: 16px;
	cursor: pointer;
}

#attach-button:hover {
	border-color: var(--accent);
}

#attach-button:disabled {
	opacity: 0.4;
	cursor: default;
}

#composer-input {
	flex: 1;
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 12px;
	padding: 11px 14px;
	font-family: inherit;
	font-size: 15px;
	resize: none;
	max-height: 220px;
	line-height: 1.4;
}

#composer-input:focus {
	outline: none;
	border-color: var(--accent);
}

#composer-input:disabled {
	opacity: 0.55;
}

#send-button {
	background: var(--accent);
	color: var(--on-accent);
	border: none;
	border-radius: 10px;
	width: 42px;
	height: 42px;
	font-size: 16px;
	cursor: pointer;
}

#send-button:disabled {
	opacity: 0.4;
	cursor: default;
}

/* Stop state: while a turn runs and the input is empty, the ➤ slot is the ⏹
   interrupt button (autoGrow applies the class) — same danger idiom as
   #call-hangup. */
#send-button.stop {
	background: var(--bg-elevated);
	color: var(--red);
	border: 1px solid var(--red);
}

#mic-button {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 10px;
	width: 42px;
	height: 42px;
	flex: none;
	font-size: 16px;
	cursor: pointer;
}

#mic-button:hover {
	border-color: var(--accent);
}

#mic-button:disabled {
	opacity: 0.4;
	cursor: default;
}

/* Recording bar: swapped in for #composer-row while a voice message is being
   recorded or transcribed, so the layout never has to squeeze both. */
#recorder {
	display: flex;
	align-items: center;
	gap: 10px;
	height: 44px;
	background: var(--bg-elevated);
	border: 1px solid var(--border);
	border-radius: 12px;
	padding: 0 8px 0 14px;
}

#recorder-dot {
	width: 10px;
	height: 10px;
	flex: none;
	border-radius: 50%;
	background: var(--red);
	animation: recorder-pulse 1.4s ease-in-out infinite;
}

#recorder.transcribing #recorder-dot {
	background: var(--accent);
}

@keyframes recorder-pulse {
	50% { opacity: 0.25; }
}

#recorder-time {
	font-variant-numeric: tabular-nums;
	font-size: 14px;
	color: var(--text);
}

#recorder.transcribing #recorder-time {
	display: none;
}

#recorder-label {
	flex: 1;
	font-size: 13px;
	color: var(--text-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

#recorder-cancel,
#recorder-stop {
	width: 34px;
	height: 34px;
	flex: none;
	border-radius: 8px;
	font-size: 14px;
	cursor: pointer;
}

#recorder-cancel {
	background: transparent;
	color: var(--text-muted);
	border: 1px solid var(--border);
}

#recorder-cancel:hover {
	border-color: var(--red);
	color: var(--red);
}

#recorder-stop {
	background: var(--accent);
	color: var(--on-accent);
	border: none;
}

#recorder-cancel:disabled,
#recorder-stop:disabled {
	opacity: 0.4;
	cursor: default;
}

/* ---- voice call overlay (call.js) ---- */

/* Same overlay family as #auth-modal (z-index 20, below #login-screen's 40).
   var(--app-height) rather than inset alone: the Android-PWA stale-viewport
   quirk the rest of the layout works around (see #main). */
#call-modal {
	position: fixed;
	inset: 0;
	height: var(--app-height, 100dvh);
	background: var(--scrim);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 20;
}

#call-modal-box {
	background: var(--bg-panel);
	border: 1px solid var(--green);
	border-radius: 12px;
	padding: 24px 28px;
	max-width: 360px;
	width: 90%;
	text-align: center;
}

#call-title {
	font-weight: 600;
	font-size: 18px;
	margin-bottom: 6px;
}

#call-status {
	color: var(--text-muted);
	font-size: 13px;
	margin-bottom: 10px;
}

#call-timer {
	font-size: 26px;
	font-variant-numeric: tabular-nums;
	margin-bottom: 10px;
}

#call-working {
	color: var(--yellow);
	font-size: 13px;
	margin-bottom: 10px;
}

#call-buttons {
	display: flex;
	gap: 10px;
	justify-content: center;
	margin-top: 8px;
}

#call-buttons button {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 10px 16px;
	min-height: 36px;
	cursor: pointer;
	font-size: 14px;
}

#call-buttons button:hover {
	border-color: var(--cyan);
}

#call-buttons #call-hangup {
	border-color: var(--red);
	color: var(--red);
}

/* ---- auth modal (tool permissions) + token modal (LLM credentials) ---- */

/* Both sit below #login-screen (z-index 40): an expired session must never leave
   a modal floating over the login form. */
#auth-modal,
#token-modal {
	position: fixed;
	inset: 0;
	background: var(--scrim);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 20;
}

#auth-modal-box,
#token-modal-box {
	background: var(--bg-panel);
	border: 1px solid var(--yellow);
	border-radius: 12px;
	padding: 20px 24px;
	max-width: 560px;
	width: 90%;
}

/* Wider than the token modal: it carries whole shell commands. */
#auth-modal-box {
	max-width: 760px;
}

#token-modal-box {
	border-color: var(--accent);
}

#auth-modal-title,
#token-modal-title {
	font-weight: 600;
	margin-bottom: 10px;
}

#token-provider,
#token-value {
	width: 100%;
	box-sizing: border-box;
	background: var(--bg);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 8px 10px;
	margin-bottom: 10px;
	font-size: 14px;
}

#token-value {
	font-family: ui-monospace, Consolas, monospace;
	resize: vertical;
}

#auth-modal-tools {
	list-style: none;
	margin: 0 0 16px;
	padding: 0;
	font-family: ui-monospace, Consolas, monospace;
	font-size: 13px;
	color: var(--text-muted);
	max-height: 50vh;
	overflow-y: auto;
}

#auth-modal-tools li {
	padding: 3px 0;
}

.auth-tool-name {
	color: var(--text);
	margin-bottom: 4px;
}

/* The call verbatim: never clipped, since this is what the user approves.
   `pre-wrap` keeps the argument's own newlines (a heredoc, a multi-line Write
   body) while folding an over-wide command back into the box: a sideways
   scrollbar would hide the tail again, which is the whole bug. Height is
   capped instead, so a long call scrolls vertically rather than pushing the
   decision buttons off screen. */
.auth-tool-args {
	margin: 0;
	padding: 8px 10px;
	background: var(--bg);
	border: 1px solid var(--border);
	border-radius: 8px;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
	max-height: 34vh;
	overflow-y: auto;
	tab-size: 4;
}

#auth-modal-buttons,
#token-status {
	font-size: 13px;
	color: var(--text-muted);
	margin-bottom: 12px;
	min-height: 16px;
}

#token-modal-buttons {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	justify-content: flex-end;
}

/* Pushed to the far left, away from Salva/Annulla: it destroys a credential. */
#token-delete:not(.hidden) {
	margin-right: auto;
}

#auth-modal-buttons button,
#token-modal-buttons button {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 8px 14px;
	cursor: pointer;
}

#auth-modal-buttons button:hover,
#token-modal-buttons button:hover {
	border-color: var(--accent);
}

#auth-modal-buttons button.danger:hover,
#token-modal-buttons button.danger:hover {
	border-color: var(--red);
	color: var(--red);
}

#token-modal-buttons button:disabled {
	opacity: 0.4;
	cursor: default;
}

/* ---- ingest sources modal (notification rulebooks) ---- */

#ingest-modal {
	position: fixed;
	inset: 0;
	background: var(--scrim);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 20;
}

#ingest-modal-box {
	position: relative;
	background: var(--bg-panel);
	border: 1px solid var(--accent);
	border-radius: 12px;
	padding: 20px 24px;
	max-width: 860px;
	width: 92%;
	max-height: 90vh;
	display: flex;
	flex-direction: column;
	overscroll-behavior: contain;
}

#ingest-modal-title {
	font-weight: 600;
	margin-bottom: 10px;
}

/* List + editor side by side; stacked on narrow screens (below). */
#ingest-modal-body {
	display: flex;
	gap: 16px;
	min-height: 0;
	flex: 1;
	margin-bottom: 12px;
}

#ingest-list {
	list-style: none;
	margin: 0;
	padding: 0;
	flex: 0 0 220px;
	max-height: 60vh;
	overflow-y: auto;
	overscroll-behavior: contain;
	border-right: 1px solid var(--border);
	padding-right: 8px;
}

.ingest-item {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: 6px 8px;
	border-radius: 6px;
	cursor: pointer;
	font-size: 13px;
}

.ingest-item:hover {
	background: var(--bg-elevated);
}

.ingest-item.selected {
	background: var(--bg-elevated);
	outline: 1px solid var(--accent);
}

.ingest-item-new {
	color: var(--text-muted);
	margin-top: 6px;
}

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

#ingest-form {
	flex: 1;
	min-width: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.ingest-row {
	display: flex;
	gap: 8px;
}

.ingest-row .ingest-field {
	flex: 1;
	min-width: 0;
}

.ingest-field {
	display: flex;
	flex-direction: column;
	gap: 3px;
	font-size: 12px;
	color: var(--text-muted);
}

.ingest-field input,
.ingest-field select,
.ingest-field textarea {
	width: 100%;
	box-sizing: border-box;
	background: var(--bg);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 7px 10px;
	font-size: 14px;
}

.ingest-field input:read-only {
	color: var(--text-muted);
}

.ingest-field textarea {
	font-family: ui-monospace, Consolas, monospace;
	font-size: 13px;
	resize: vertical;
	min-height: 120px;
}

/* The MacroDroid URL for this source, copy-ready. */
#ingest-hint {
	display: flex;
	align-items: center;
	gap: 8px;
	font-family: ui-monospace, Consolas, monospace;
	font-size: 12px;
	color: var(--text-muted);
}

#ingest-hint-url {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

#ingest-hint-copy {
	flex: none;
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 2px 8px;
	cursor: pointer;
}

#ingest-status {
	font-size: 13px;
	color: var(--text-muted);
	min-height: 16px;
}

#ingest-modal-buttons {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	justify-content: flex-end;
}

#ingest-delete:not(.hidden) {
	margin-right: auto;
}

#ingest-modal-buttons button {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 8px 14px;
	cursor: pointer;
}

#ingest-modal-buttons button:hover {
	border-color: var(--accent);
}

#ingest-modal-buttons button.danger:hover {
	border-color: var(--red);
	color: var(--red);
}

#ingest-modal-buttons button:disabled {
	opacity: 0.4;
	cursor: default;
}

@media (max-width: 700px) {
	#ingest-modal-body {
		flex-direction: column;
	}

	#ingest-list {
		flex: none;
		max-height: 24vh;
		border-right: none;
		border-bottom: 1px solid var(--border);
		padding-right: 0;
		padding-bottom: 6px;
	}
}

/* ---- forwarded apps: chips in the source editor, and the picker over it ---- */
/* Native shell only (docs/native-app.md phase 3.1): the package -> key map lives on the
   phone, so none of this renders in a browser. */

#ingest-apps {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
}

.app-chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 3px 6px 3px 4px;
	border: 1px solid var(--border);
	border-radius: 999px;
	background: var(--bg-elevated);
	font-size: 13px;
	color: var(--text);
	max-width: 100%;
}

.app-chip > span {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.app-chip-x {
	flex: none;
	background: none;
	border: none;
	color: var(--text-muted);
	cursor: pointer;
	padding: 0 2px;
	font-size: 12px;
}

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

.app-chip-add {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px dashed var(--border);
	border-radius: 999px;
	padding: 4px 10px;
	font-size: 13px;
	cursor: pointer;
}

.app-chip-add:hover {
	border-color: var(--accent);
}

.app-chip-empty {
	font-size: 13px;
	color: var(--text-muted);
}

.app-icon {
	flex: none;
	width: 22px;
	height: 22px;
	border-radius: 5px;
	object-fit: contain;
}

/* Over the whole modal box: the catalog is long, and the editor behind it is irrelevant
   while choosing. */
#ingest-apps-panel {
	position: absolute;
	inset: 0;
	background: var(--bg-panel);
	border-radius: 12px;
	padding: 20px 24px;
	display: flex;
	flex-direction: column;
	gap: 10px;
	z-index: 1;
}

#ingest-apps-panel-head {
	display: flex;
	align-items: center;
	gap: 10px;
	font-weight: 600;
}

#ingest-apps-filter {
	flex: 1;
	min-width: 0;
	background: var(--bg);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 6px 10px;
	font-size: 14px;
	font-weight: 400;
}

#ingest-apps-list {
	list-style: none;
	margin: 0;
	padding: 0;
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
}

.app-row {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 6px 4px;
	border-bottom: 1px solid var(--border);
}

.app-row.on {
	background: var(--bg-elevated);
}

.app-row-empty {
	padding: 12px 4px;
	color: var(--text-muted);
	font-size: 13px;
}

.app-text {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
}

.app-label {
	font-size: 14px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.app-sub {
	font-size: 11px;
	color: var(--text-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.app-key {
	/* Fixed share rather than auto: a ragged right edge over a few hundred rows reads as
	   noise, and the long keys ellipsize anyway. */
	flex: 0 0 40%;
	max-width: 40%;
	background: var(--bg);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 5px 8px;
	font-size: 13px;
}

#ingest-apps-panel-foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 10px;
	font-size: 12px;
	color: var(--text-muted);
}

#ingest-apps-done {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 8px 14px;
	cursor: pointer;
}

#ingest-apps-done:hover {
	border-color: var(--accent);
}

/* ---- phone modal (the native shell's own settings) ---- */

#phone-modal {
	position: fixed;
	inset: 0;
	background: var(--scrim);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 20;
}

#phone-modal-box {
	background: var(--bg-panel);
	border: 1px solid var(--accent);
	border-radius: 12px;
	padding: 20px 24px;
	max-width: 560px;
	width: 92%;
	max-height: 90vh;
	display: flex;
	flex-direction: column;
	overscroll-behavior: contain;
}

#phone-modal-title {
	font-weight: 600;
	margin-bottom: 10px;
}

#phone-modal-body {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	display: flex;
	flex-direction: column;
	gap: 12px;
	margin-bottom: 12px;
}

#phone-status {
	list-style: none;
	margin: 0;
	padding: 0;
	font-size: 13px;
	color: var(--text-muted);
}

.phone-row {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 7px 0;
	border-bottom: 1px solid var(--border);
}

.phone-dot {
	flex: none;
	width: 9px;
	height: 9px;
	border-radius: 50%;
	background: var(--red);
}

.phone-dot.ok {
	background: var(--green);
}

.phone-dot.warn {
	background: var(--yellow);
}

.phone-row-text {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
}

.phone-row-label {
	font-size: 14px;
	color: var(--text);
}

.phone-row-detail {
	font-size: 11px;
	color: var(--text-muted);
}

.phone-row-btn {
	flex: none;
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 5px 10px;
	font-size: 13px;
	cursor: pointer;
}

.phone-row-btn:hover {
	border-color: var(--accent);
}

/* ---- zones modal (web/js/zones.js — the one editor of the location plugin's zones) ---- */

#zones-modal {
	position: fixed;
	inset: 0;
	background: var(--scrim);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 20;
}

#zones-modal-box {
	background: var(--bg-panel);
	border: 1px solid var(--accent);
	border-radius: 12px;
	padding: 20px 24px;
	max-width: 860px;
	width: 94%;
	max-height: 92vh;
	max-height: 92dvh;
	display: flex;
	flex-direction: column;
	overscroll-behavior: contain;
}

#zones-modal-title {
	font-weight: 600;
	margin-bottom: 10px;
}

#zones-count {
	color: var(--text-faint, var(--text-muted));
	font-weight: 400;
}

/* Map on the left, list + form on the right; stacked on a phone (below). */
#zones-modal-body {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	display: flex;
	gap: 16px;
	margin-bottom: 12px;
}

#zones-map-pane {
	flex: 1 1 55%;
	min-width: 0;
	display: flex;
	flex-direction: column;
}

#zones-map {
	height: 360px;
	border-radius: 8px;
	border: 1px solid var(--border);
	background: var(--bg-elevated);
}

/* No key, or the script refused: the pane is just its hint, above the list. */
#zones-map-pane.no-map #zones-map {
	display: none;
}

#zones-modal-body:has(#zones-map-pane.no-map) {
	flex-direction: column;
	gap: 8px;
}

#zones-modal-box:has(#zones-map-pane.no-map) {
	max-width: 560px;
}

#zones-map-hint {
	font-size: 12px;
	color: var(--text-muted);
	padding: 6px 0;
}

#zones-map-hint:empty {
	display: none;
}

#zones-search-row {
	display: flex;
	gap: 6px;
	margin-top: 8px;
}

#zones-search-row input {
	flex: 1;
	min-width: 0;
}

#zones-side {
	flex: 1 1 45%;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 10px;
}

#zones-list {
	list-style: none;
	margin: 0;
	padding: 0;
	max-height: 200px;
	overflow-y: auto;
	overscroll-behavior: contain;
}

.zone-row {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 6px 0;
	border-bottom: 1px solid var(--border);
}

.zone-text {
	display: flex;
	flex-direction: column;
	flex: 1;
	min-width: 0;
	cursor: pointer;
}

.zone-name {
	font-size: 14px;
	color: var(--text);
}

.zone-row.selected .zone-name {
	color: var(--accent);
	font-weight: 600;
}

.zone-detail {
	font-size: 11px;
	color: var(--text-muted);
}

.zone-x {
	background: none;
	border: none;
	color: var(--text-muted);
	font-size: 14px;
	cursor: pointer;
	padding: 2px 6px;
}

.zone-x:hover {
	color: var(--red);
}

.zone-empty {
	font-size: 12px;
	color: var(--text-muted);
	padding: 6px 0;
}

#zones-form {
	display: flex;
	flex-direction: column;
	gap: 6px;
}

#zones-form-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
	font-size: 13px;
	color: var(--text-muted);
}

#zones-coords {
	display: flex;
	align-items: center;
	gap: 6px;
}

#zones-lat,
#zones-lon {
	flex: 1 1 90px;
	min-width: 0;
}

#zones-radius {
	width: 84px;
	flex: 0 0 auto;
}

#zones-radius-unit {
	font-size: 12px;
	color: var(--text-muted);
}

#zones-modal input {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 7px 10px;
	font-size: 13px;
	font-family: inherit;
}

#zones-form-actions {
	display: flex;
	gap: 6px;
	justify-content: flex-end;
	margin-top: 4px;
}

#zones-modal-body button,
#zones-modal-buttons button {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 7px 12px;
	font-size: 13px;
	font-family: inherit;
	cursor: pointer;
}

#zones-modal-body button:hover,
#zones-modal-buttons button:hover {
	border-color: var(--accent);
}

#zones-modal button.primary {
	border-color: var(--accent);
}

#zones-modal button.danger:hover {
	border-color: var(--red);
	color: var(--red);
}

#zones-modal button:disabled {
	opacity: 0.5;
	cursor: default;
}

#zones-hint,
#zones-sync-hint {
	font-size: 11px;
	color: var(--text-muted);
	min-height: 14px;
}

#zones-modal-buttons {
	display: flex;
	gap: 10px;
	justify-content: flex-end;
}

@media (max-width: 720px) {
	#zones-modal-box {
		padding: 14px 14px;
	}

	#zones-modal-body {
		flex-direction: column;
	}

	#zones-map {
		height: 40vh;
		height: 40dvh;
	}

	#zones-list {
		max-height: 140px;
	}
}

#phone-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}

#phone-actions button {
	flex: 1 1 auto;
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 8px 12px;
	font-size: 13px;
	cursor: pointer;
}

#phone-actions button:hover {
	border-color: var(--accent);
}

#phone-logs {
	font-size: 13px;
	color: var(--text-muted);
}

#phone-logs summary {
	cursor: pointer;
	padding: 4px 0;
}

#phone-logs-actions {
	display: flex;
	gap: 8px;
	margin: 6px 0;
}

#phone-logs-actions button {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 5px 10px;
	font-size: 13px;
	cursor: pointer;
}

#phone-logs-actions button.danger:hover {
	border-color: var(--red);
	color: var(--red);
}

#phone-logs pre {
	background: var(--bg);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 8px;
	margin: 0 0 8px;
	max-height: 30vh;
	overflow: auto;
	overscroll-behavior: contain;
	font-family: ui-monospace, Consolas, monospace;
	font-size: 11px;
	white-space: pre-wrap;
	word-break: break-word;
}

#phone-modal-buttons {
	display: flex;
	gap: 10px;
	justify-content: flex-end;
}

#phone-modal-buttons button {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 8px 14px;
	cursor: pointer;
}

#phone-modal-buttons button.danger:hover {
	border-color: var(--red);
	color: var(--red);
}

/* ---- agent launcher modal (every manual pass) ---- */

#agent-modal {
	position: fixed;
	inset: 0;
	background: var(--scrim);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 20;
}

#agent-modal-box {
	background: var(--bg-panel);
	border: 1px solid var(--cyan);
	border-radius: 12px;
	padding: 20px 24px;
	max-width: 460px;
	width: 90%;
}

#agent-modal-title {
	font-weight: 600;
	margin-bottom: 4px;
}

#agent-modal-mode {
	font-size: 13px;
	color: var(--text-muted);
	margin-bottom: 14px;
}

/* The forget pass's target, shown so a destructive run is never launched blind. */
#agent-modal-source {
	font-family: ui-monospace, Consolas, monospace;
	font-size: 12px;
	color: var(--red);
	word-break: break-all;
	margin-bottom: 14px;
}

.agent-modal-field {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-bottom: 10px;
}

.agent-modal-field span {
	font-size: 13px;
	color: var(--text-muted);
	width: 64px;
	flex: none;
}

.agent-modal-field select {
	flex: 1;
	min-width: 0;
	background: var(--bg);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 8px 10px;
	font-size: 14px;
}

#agent-modal-buttons {
	display: flex;
	gap: 10px;
	justify-content: flex-end;
	margin-top: 16px;
}

#agent-modal-buttons button {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 8px 14px;
	cursor: pointer;
}

#agent-modal-buttons button:hover {
	border-color: var(--accent);
}

#agent-modal-buttons button.danger:hover {
	border-color: var(--red);
	color: var(--red);
}

#agent-modal-buttons button:disabled {
	opacity: 0.4;
	cursor: default;
}

/* ---- job modal (a row of the Attività section) ---- */

#job-modal {
	position: fixed;
	inset: 0;
	background: var(--scrim);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 20;
}

#job-modal-box {
	background: var(--bg-panel);
	border: 1px solid var(--cyan);
	border-radius: 12px;
	padding: 20px 24px;
	max-width: 520px;
	width: 90%;
	max-height: 85vh;
	display: flex;
	flex-direction: column;
	gap: 8px;
	overflow: hidden;
}

#job-modal-title {
	font-weight: 600;
	overflow-wrap: anywhere;
}

#job-modal-status {
	font-size: 13px;
	color: var(--text-muted);
}

#job-modal-status[data-status="failed"],
#job-modal-outcome[data-status="failed"] {
	color: var(--red);
}

#job-modal-status[data-status="done"],
#job-modal-outcome[data-status="done"] {
	color: var(--green);
}

#job-modal-meta {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 2px 12px;
	margin: 4px 0;
	font-size: 13px;
}

#job-modal-meta dt {
	color: var(--text-muted);
}

#job-modal-meta dd {
	margin: 0;
	overflow-wrap: anywhere;
}

#job-modal .job-bar {
	height: 6px;
	border-radius: 3px;
	margin: 4px 0 0;
}

#job-modal-progress-text {
	font-size: 12px;
	color: var(--text-muted);
	min-height: 1em;
}

/* The job's own lines (the fetcher's output): a scrollable tail. */
#job-modal-log {
	margin: 4px 0 0;
	padding: 8px 10px;
	max-height: 200px;
	overflow: auto;
	background: var(--bg-elevated);
	border: 1px solid var(--border);
	border-radius: 8px;
	font-size: 11px;
	line-height: 1.45;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
}

#job-modal-outcome {
	font-size: 13px;
	overflow-wrap: anywhere;
}

#job-modal-buttons {
	display: flex;
	justify-content: flex-end;
	gap: 8px;
	margin-top: 8px;
}

#job-modal-buttons button {
	padding: 6px 14px;
	border-radius: 8px;
	border: 1px solid var(--border);
	background: var(--bg-elevated);
	color: var(--text);
	cursor: pointer;
	font: inherit;
}

#job-modal-buttons button:hover {
	border-color: var(--cyan);
}

#job-modal-buttons button.danger:hover {
	border-color: var(--red);
	color: var(--red);
}

#job-modal-buttons button:disabled {
	opacity: 0.5;
	cursor: default;
}

/* ---- forget-source modal (the 🧽 button's picker) ---- */

#forget-modal {
	position: fixed;
	inset: 0;
	background: var(--scrim);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 20;
}

#forget-modal-box {
	background: var(--bg-panel);
	border: 1px solid var(--red);
	border-radius: 12px;
	padding: 20px 24px;
	max-width: 560px;
	width: 90%;
	/* List (40vh) + preview (30vh) together can outgrow small screens. */
	max-height: 90vh;
	overflow-y: auto;
	overscroll-behavior: contain;
}

#forget-modal-title {
	font-weight: 600;
	margin-bottom: 10px;
}

#forget-modal-list {
	list-style: none;
	margin: 0 0 12px;
	padding: 0;
	max-height: 40vh;
	overflow-y: auto;
	overscroll-behavior: contain;
}

/* Group headers, styled like the sidebar's Librarian section title. */
.forget-group {
	font-size: 11px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--text-muted);
	padding: 8px 0 4px;
}

.forget-empty {
	font-size: 13px;
	color: var(--text-muted);
	padding: 2px 0 6px;
}

.forget-item {
	display: flex;
	align-items: baseline;
	gap: 10px;
	padding: 5px 8px;
	border-radius: 6px;
	cursor: pointer;
}

.forget-item:hover {
	background: var(--bg-elevated);
}

.forget-item.selected {
	background: var(--bg-elevated);
	outline: 1px solid var(--red);
}

.forget-item-path {
	flex: 1;
	min-width: 0;
	font-family: ui-monospace, Consolas, monospace;
	font-size: 13px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.forget-item-when {
	flex: none;
	font-size: 11px;
	color: var(--text-muted);
}

#forget-confirm {
	border-top: 1px solid var(--border);
	padding-top: 10px;
	margin-bottom: 12px;
}

#forget-confirm-path {
	font-family: ui-monospace, Consolas, monospace;
	font-size: 13px;
	margin-bottom: 6px;
	word-break: break-all;
}

/* Full-content preview of the selected source. */
#forget-preview {
	font-family: ui-monospace, Consolas, monospace;
	font-size: 12px;
	white-space: pre-wrap;
	word-break: break-word;
	background: var(--bg-elevated);
	border: 1px solid var(--border);
	border-radius: 6px;
	padding: 8px 10px;
	margin: 0 0 10px;
	max-height: 30vh;
	overflow-y: auto;
	overscroll-behavior: contain;
}

#forget-confirm-note {
	font-size: 13px;
	color: var(--text-muted);
	margin-bottom: 10px;
}

#forget-confirm-buttons,
#forget-modal-buttons {
	display: flex;
	gap: 10px;
	justify-content: flex-end;
}

#forget-confirm-buttons button,
#forget-modal-buttons button {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 8px 14px;
	cursor: pointer;
}

#forget-confirm-buttons button:hover,
#forget-modal-buttons button:hover {
	border-color: var(--accent);
}

#forget-confirm-buttons button.danger:hover {
	border-color: var(--red);
	color: var(--red);
}

#forget-confirm-buttons button:disabled {
	opacity: 0.4;
	cursor: default;
}

/* ---- share-target chooser ---- */

#share-modal {
	position: fixed;
	inset: 0;
	background: var(--scrim);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 20;
}

#share-modal-box {
	background: var(--bg-panel);
	border: 1px solid var(--accent);
	border-radius: 12px;
	padding: 20px 24px;
	max-width: 420px;
	width: 90%;
}

#share-modal-title {
	font-weight: 600;
	margin-bottom: 10px;
}

#share-modal-files {
	list-style: none;
	margin: 0 0 14px;
	padding: 0;
	max-height: 30vh;
	overflow-y: auto;
	font-size: 13px;
	color: var(--text-muted);
}

#share-modal-files li {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	padding: 2px 0;
}

#share-modal-actions {
	display: flex;
	flex-direction: column;
	gap: 8px;
	margin-bottom: 14px;
}

#share-modal-actions button,
#share-modal-buttons button {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 10px 14px;
	cursor: pointer;
	text-align: left;
	font-size: 14px;
}

#share-modal-actions button:hover {
	border-color: var(--accent);
}

/* The pre-highlighted likely action (by file type) — one tap to confirm. */
#share-modal-actions button.suggested {
	border-color: var(--accent);
	background: var(--bg);
}

#share-modal-actions button:disabled {
	opacity: 0.4;
	cursor: default;
}

#share-modal-buttons {
	display: flex;
	justify-content: flex-end;
}

#share-modal-buttons button.danger:hover {
	border-color: var(--red);
	color: var(--red);
}

/* ---- upload panel ---- */

/* One card per active utility upload (Timeline, chat import, wiki inbox,
   shared files). Bottom-right on desktop, out of the composer's way; the
   mobile block moves it above the composer full-width. */
#upload-panel {
	position: fixed;
	right: 18px;
	bottom: 18px;
	display: flex;
	flex-direction: column;
	gap: 8px;
	width: 300px;
	z-index: 30;
}

#upload-panel:empty {
	display: none;
}

.upload-card {
	background: var(--bg-elevated);
	border: 1px solid var(--border);
	border-radius: 10px;
	padding: 10px 12px;
	box-shadow: 0 4px 18px var(--shadow-color);
	font-size: 13px;
}

.upload-card.done {
	border-color: var(--green);
}

.upload-card.error {
	border-color: var(--red);
}

.upload-card-head {
	display: flex;
	align-items: center;
	gap: 6px;
}

.upload-card-title {
	flex: 1;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.upload-card-retry,
.upload-card-close {
	background: none;
	border: none;
	color: var(--text-muted);
	font-size: 13px;
	line-height: 1;
	cursor: pointer;
	padding: 2px;
}

.upload-card-retry:hover,
.upload-card-close:hover {
	color: var(--text);
}

.upload-card-bar {
	height: 6px;
	border-radius: 3px;
	background: var(--border);
	overflow: hidden;
	margin: 8px 0 6px;
}

.upload-card-fill {
	height: 100%;
	width: 0;
	border-radius: 3px;
	background: var(--accent);
	transition: width 0.2s ease;
}

.upload-card.done .upload-card-fill {
	background: var(--green);
}

.upload-card.error .upload-card-fill {
	background: var(--red);
}

/* Server-side work after the bytes landed (Timeline parse, normalizer spawn):
   the bar is full but the job isn't — shimmer instead of pretending. */
.upload-card.processing .upload-card-fill {
	animation: upload-shimmer 1.2s ease-in-out infinite;
}

/* Following a background job that reports a fraction: a real bar again. */
.upload-card.following .upload-card-fill {
	animation: none;
}

@keyframes upload-shimmer {
	0%, 100% { opacity: 1; }
	50% { opacity: 0.45; }
}

.upload-card-status {
	color: var(--text-muted);
	font-size: 12px;
}

/* ---- image lightbox ---- */

/* Above #toast and #upload-panel (30) but below #login-screen (40): an expired
   session must never leave an image floating over the login form. The backdrop
   is deliberately a fixed near-black rather than var(--scrim) — the light
   theme's scrim is barely a tint, and a photo viewer wants a dark surround in
   both themes. */
#lightbox {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.82);
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 24px;
	z-index: 35;
	cursor: zoom-out;
}

#lightbox-img {
	max-width: 100%;
	max-height: 100%;
	border-radius: 8px;
	box-shadow: 0 8px 40px rgba(0, 0, 0, 0.6);
}

#lightbox-close {
	position: absolute;
	top: 12px;
	right: 16px;
	background: var(--bg-elevated);
	border: 1px solid var(--border);
	border-radius: 8px;
	color: var(--text);
	font-size: 18px;
	line-height: 1;
	padding: 6px 11px;
	cursor: pointer;
}

#lightbox-close:hover {
	border-color: var(--accent);
}

/* The affordance, on exactly the images the delegated handler opens. */
.msg-user .bubble img,
.msg-image img,
.msg-attachment img,
.msg-assistant img,
#wiki-doc img {
	cursor: zoom-in;
}

/* ---- toast ---- */

#toast {
	position: fixed;
	bottom: 24px;
	left: 50%;
	transform: translateX(-50%);
	background: var(--bg-elevated);
	border: 1px solid var(--red);
	color: var(--text);
	border-radius: 10px;
	padding: 10px 18px;
	font-size: 14px;
	z-index: 30;
	box-shadow: 0 4px 18px var(--shadow-color);
}

/* ---- login ---- */

/* Opaque, not the translucent scrim of #auth-modal: this covers the app rather
   than layering over it. Above #toast (30) so nothing can peek through. */
#login-screen {
	position: fixed;
	inset: 0;
	background: var(--bg);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 40;
}

/* Pre-paint suppression for a returning user: the inline boot script in
   index.html stamps data-auth when a token is stored, since the deferred module
   that adds .hidden can land after the first paint. app.js removes the
   attribute at module scope (same task, no paint in between) so .hidden stays
   the single runtime source of truth — otherwise logout couldn't re-show it. */
:root[data-auth] #login-screen {
	display: none;
}

#login-form {
	display: flex;
	flex-direction: column;
	gap: 12px;
	background: var(--bg-panel);
	border: 1px solid var(--border);
	border-radius: 12px;
	padding: 24px;
	width: 90%;
	max-width: 320px;
}

#login-title {
	font-size: 20px;
	font-weight: 700;
	text-align: center;
	margin-bottom: 4px;
}

#login-form input {
	background: var(--bg-elevated);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: 8px;
	padding: 10px 12px;
	/* 16px: anything smaller makes iOS zoom in on focus. */
	font-size: 16px;
	font-family: inherit;
}

#login-form input:focus {
	outline: none;
	border-color: var(--accent);
}

#login-submit {
	background: var(--accent-soft);
	color: var(--text);
	border: 1px solid var(--accent);
	border-radius: 8px;
	padding: 10px 14px;
	font-size: 16px;
	font-family: inherit;
	cursor: pointer;
	margin-top: 4px;
}

#login-submit:hover {
	background: var(--accent);
	color: var(--on-accent);
}

#login-error {
	color: var(--red);
	font-size: 14px;
	text-align: center;
}

#sidebar-header-actions {
	display: flex;
	align-items: center;
	gap: 6px;
}

#sidebar-menu-wrap {
	position: relative;
}

#sidebar-menu-btn {
	background: none;
	border: none;
	color: var(--text-muted);
	font-size: 17px;
	cursor: pointer;
	width: 28px;
	height: 28px;
	padding: 0;
	border-radius: 6px;
	line-height: 1;
}

#sidebar-menu-btn:hover,
#sidebar-menu-btn[aria-expanded="true"] {
	background: var(--bg-elevated);
	color: var(--text);
}

#sidebar-menu {
	position: absolute;
	top: calc(100% + 4px);
	right: 0;
	z-index: 20;
	display: flex;
	flex-direction: column;
	min-width: 190px;
	padding: 4px;
	background: var(--bg-elevated);
	border: 1px solid var(--border);
	border-radius: 10px;
	box-shadow: 0 8px 24px var(--shadow-color);
	/* Three groups expanded at once would outgrow a phone; only one opens at a
	   time, but the tallest (Agenti, 7 entries) still needs the cap. Lifted on
	   desktop, where the groups fly out instead of unfolding — see below. */
	max-height: min(70vh, 520px);
	overflow-y: auto;
	overscroll-behavior: contain;
}

#sidebar-menu button {
	background: none;
	border: none;
	color: var(--text);
	font-size: 14px;
	text-align: left;
	white-space: nowrap;
	cursor: pointer;
	padding: 8px 10px;
	border-radius: 6px;
}

#sidebar-menu button:hover {
	background: var(--accent-soft);
}

/* ---- ⋮ menu groups (Agenti / Upload / Wiki) ---- */

/* Accordion is the touch shape: the sidebar is a drawer there, where a flyout
   has no hover to open it and nowhere to unfold into. Desktop overrides it into
   a side panel further down. */
.menu-group {
	display: flex;
	flex-direction: column;
}

.menu-group-btn {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
}

.menu-caret {
	display: inline-block;
	color: var(--text-muted);
	font-size: 11px;
	transition: transform 0.12s ease;
}

.menu-group-btn[aria-expanded="true"] {
	color: var(--accent);
}

.menu-group-btn[aria-expanded="true"] .menu-caret {
	transform: rotate(90deg);
}

.menu-sub {
	display: flex;
	flex-direction: column;
}

/* Indented and rule-marked, so a submenu entry never reads as a top-level one. */
#sidebar-menu .menu-sub button {
	padding-left: 22px;
	margin-left: 10px;
	border-left: 1px solid var(--border);
	border-radius: 0 6px 6px 0;
}

/* Desktop: the same groups as a real app menu — a panel beside the menu instead
   of an unfold inside it, so the entries below never shift under the cursor and
   the whole tree is one hover away. Same breakpoint as the drawer below; the
   hover-to-open half is gated on `(hover: hover)` in app.js, so a touch screen
   wide enough to land here still gets a tap-driven menu (that JS is the reason
   this block also has to keep working when a group is opened by click). */
@media (min-width: 721px) {
	#sidebar-menu {
		/* The flyout is an absolutely-positioned descendant: a scroll container
		   would clip it. Safe to drop — the cap only ever guarded the accordion's
		   stacked height, and one panel of 7 entries fits anywhere. */
		overflow: visible;
		max-height: none;
	}

	.menu-group {
		position: relative;
	}

	.menu-sub {
		position: absolute;
		/* -5px, not -4: the panel's own border sits outside the menu's padding box. */
		top: -5px;
		left: calc(100% + 5px);
		z-index: 21;
		min-width: 190px;
		padding: 4px;
		background: var(--bg-elevated);
		border: 1px solid var(--border);
		border-radius: 10px;
		box-shadow: 0 8px 24px var(--shadow-color);
	}

	/* The indent + rule marked depth *inside* the menu; in a panel of its own the
	   entries are top-level again. */
	#sidebar-menu .menu-sub button {
		padding-left: 10px;
		margin-left: 0;
		border-left: none;
		border-radius: 6px;
	}

	/* ▸ points at the panel it opens — no rotation, unlike the accordion's ▾. */
	.menu-group-btn[aria-expanded="true"] .menu-caret {
		transform: none;
	}

	/* The open group stays highlighted as the row the panel belongs to. */
	.menu-group-btn[aria-expanded="true"] {
		background: var(--accent-soft);
	}
}

#logout-btn:hover,
#wiki-forget-btn:hover {
	color: var(--red);
}

/* The plugins' entries (data-purpose) disable themselves while an action runs. */
#sidebar-menu button[data-purpose]:disabled,
#wiki-sync-btn:disabled {
	cursor: default;
	opacity: 0.4;
}

/* ---- mobile ---- */

#sidebar-toggle {
	display: none;
	background: none;
	border: none;
	color: var(--text);
	font-size: 20px;
	cursor: pointer;
	padding: 8px 12px;
}

@media (max-width: 720px) {
	#sidebar {
		position: fixed;
		top: 0;
		left: 0;
		/* Not `bottom: 0`: fixed elements can resolve against the large viewport
		   (URL bar hidden) on mobile, pushing the pinned Librarian section off-screen.
		   dvh tracks the *visible* viewport, like the body. */
		height: 100vh;
		height: 100dvh;
		height: var(--app-height, 100dvh);
		z-index: 10;
		width: min(280px, 85vw);
		min-width: 0;
		transform: translateX(-100%);
		transition: transform 0.2s ease;
		box-shadow: 4px 0 20px var(--shadow-color);
	}

	/* viewport-fit=cover extends under the gesture bar: keep the pill off the runs
	   (Agenti is the last section in the DOM; Attività sits above it). */
	#librarian-section {
		padding-bottom: env(safe-area-inset-bottom, 0px);
	}

	/* Two expanded sections would leave the conversations no room. */
	.sidebar-section {
		max-height: 32%;
	}

	#job-modal-box {
		max-height: 92vh;
		max-height: calc(var(--app-height, 100dvh) - 24px);
	}

	#sidebar.open {
		transform: translateX(0);
	}

	/* The finger-tracked drag (js/drawer.js) writes an inline transform per
	   frame; the settle animation must not fight it while the finger is down.
	   The class is dropped and the inline transform cleared in the same task, so
	   the .2s transition above picks the drawer up wherever it was released. */
	#sidebar.dragging {
		transition: none;
		will-change: transform;
	}

	/* Permanently in the layer below 720px so it can fade with the drawer:
	   `display` can't transition, but visibility can (discrete — it flips at the
	   end of the fade-out and at the start of the fade-in), and a hidden box is
	   not a hit-test target, so the closed state stays fully click-through. */
	#sidebar-backdrop {
		display: block;
		position: fixed;
		inset: 0;
		z-index: 9;
		background: var(--scrim-soft);
		opacity: 0;
		visibility: hidden;
		transition: opacity 0.2s ease, visibility 0.2s;
	}

	#sidebar.open + #sidebar-backdrop {
		opacity: 1;
		visibility: visible;
	}

	/* Mid-drag the scrim is a pure function of how far the drawer has come out —
	   --drawer-progress is written on <html> once per frame by drawer.js. */
	#sidebar.dragging + #sidebar-backdrop {
		opacity: var(--drawer-progress, 0);
		visibility: visible;
		transition: none;
	}

	#sidebar-toggle {
		display: inline-block;
	}

	#header {
		flex-wrap: wrap;
		padding: 8px 12px;
	}

	#thread-name {
		flex: 1;
		min-width: 0;
		max-width: none;
	}

	#header-controls {
		flex: 1 0 100%;
		margin-left: 0;
	}

	#header-controls select {
		flex: 1 1 0;
		min-width: 0;
		max-width: none;
	}

	#new-conversation,
	#sidebar-menu-btn {
		width: 36px;
		height: 36px;
	}

	/* The desktop margins waste scarce width, and the gesture-bar inset already
	   pads the bottom. */
	#composer {
		gap: 6px;
		padding: 0 10px calc(10px + env(safe-area-inset-bottom, 0px));
	}

	/* Full-width above the composer: a fixed bottom-right stack would sit on
	   top of the send button on a phone. */
	#upload-panel {
		left: 10px;
		right: 10px;
		width: auto;
		bottom: calc(76px + env(safe-area-inset-bottom, 0px));
	}

	/* Below 16px iOS zooms the page on focus. */
	#composer-input,
	#header-controls select,
	#token-provider,
	#token-value,
	.ingest-field input,
	.ingest-field select,
	.ingest-field textarea,
	.agent-modal-field select,
	#search-input,
	.conversation-item input.conv-rename-input {
		font-size: 16px;
	}

	/* The two pickers crowd the wrapped header row: collapse them into the ⚙️
	   button, whose popover holds the same selects. Anchored to the controls
	   row (not the button) so it never overflows the screen edge. */
	#header-controls {
		position: relative;
	}

	#picker-wrap {
		display: block;
	}

	#picker-btn {
		display: inline-block;
	}

	#picker-panel {
		display: none;
	}

	#picker-wrap.open #picker-panel {
		display: flex;
		position: absolute;
		top: calc(100% + 4px);
		right: 0;
		z-index: 20;
		flex-direction: column;
		gap: 8px;
		padding: 8px;
		width: min(280px, 100%);
		background: var(--bg-elevated);
		border: 1px solid var(--border);
		border-radius: 10px;
		box-shadow: 0 8px 24px var(--shadow-color);
	}

	/* Undo the row-layout flex above: stacked full-width selects. */
	#picker-panel select {
		flex: none;
		width: 100%;
	}

	/* The wiki tree can't hold a fixed column next to the page on a phone, so it
	   becomes an overlay panel behind 🗂️ — the sidebar drawer's shape, swipe
	   included: app.js gives it a second drawer.js instance. Nothing to fight
	   over, since the sidebar's own instance is blocked by body.wiki-view for as
	   long as the viewer is up. */
	#wiki-tree-pane {
		position: absolute;
		top: 0;
		bottom: 0;
		left: 0;
		z-index: 8;
		width: min(260px, 80vw);
		transform: translateX(-100%);
		transition: transform 0.2s ease;
		box-shadow: 4px 0 20px var(--shadow-color);
	}

	#wiki-tree-pane.open {
		transform: translateX(0);
	}

	/* Same deal as #sidebar.dragging: the finger writes an inline transform per
	   frame, and the settle animation must not fight it while it is down. */
	#wiki-tree-pane.dragging {
		transition: none;
		will-change: transform;
	}

	#wiki-view {
		position: relative;
	}

	/* Permanently in the layer here — the sidebar scrim's arrangement, and for
	   the same reason: `display` can't transition, `visibility` can, and a hidden
	   box is not a hit-test target, so the closed state stays click-through. The
	   base rule above keeps it display:none on desktop, where it is never used. */
	#wiki-tree-backdrop {
		display: block;
		position: absolute;
		inset: 0;
		z-index: 7;
		background: var(--scrim-soft);
		opacity: 0;
		visibility: hidden;
		transition: opacity 0.2s ease, visibility 0.2s;
	}

	#wiki-tree-pane.open + #wiki-tree-backdrop {
		opacity: 1;
		visibility: visible;
	}

	/* Mid-drag the scrim is a pure function of how far the tree has come out. */
	#wiki-tree-pane.dragging + #wiki-tree-backdrop {
		opacity: var(--drawer-progress, 0);
		visibility: visible;
		transition: none;
	}

	#wiki-tree-toggle {
		display: inline-block;
	}

	#wiki-bar {
		padding: 8px 12px;
	}

	#wiki-doc, #wiki-raw {
		padding: 0 14px;
	}

	/* The graph tools fold behind ⚙️ — a 220px panel would cover half a phone. */
	#wiki-graph-tools-toggle {
		display: inline-block;
	}

	#wiki-graph-tools:not(.open) #wiki-graph-panel {
		display: none;
	}

	#wiki-graph-panel {
		width: min(240px, calc(100vw - 40px));
	}
}

/* Touch devices: no hover, so the pin/rename/delete affordances must always be
   visible — and big enough to hit. */
@media (hover: none) {
	.conversation-item .conv-pin,
	.conversation-item .conv-rename,
	.conversation-item .conv-delete {
		visibility: visible;
		font-size: 16px;
		padding: 6px 8px;
		top: 2px;
	}

	.conversation-item .conv-delete {
		right: 0;
	}

	.conversation-item .conv-rename {
		right: 32px;
	}

	.conversation-item .conv-pin {
		right: 64px;
	}

	.conversation-item .conv-title {
		padding-right: 100px;
	}

	.msg-copy {
		visibility: visible;
		font-size: 14px;
		padding: 2px 4px;
	}

	.attachment-remove {
		width: 24px;
		height: 24px;
		font-size: 13px;
	}

	#recorder-cancel,
	#recorder-stop {
		width: 38px;
		height: 38px;
	}
}
