/* liseur-sync web UI (ADR-0011).
 *
 * One stylesheet, no build step, no framework. It is ordered tokens →
 * reset → shell → components → pages, and every declaration lives here:
 * no markup carries a style attribute, so a Content-Security-Policy for
 * /ui stays a decision rather than a migration.
 */

/* ---------------------------------------------------------------- tokens */

:root {
	color-scheme: light;
	--bg: #fafaf9;
	--surface: #fff;
	--surface-2: #f2f2f0;
	--surface-3: #e6e5e2;
	--line: #deddd9;
	--line-soft: #ecebe8;
	--ink: #1f1e1c;
	--ink-dim: #4f4d49;
	--muted: #6d6a65;
	--primary: #7c4dbe;
	--primary-dim: #63399e;
	--on-primary: #fff;
	--danger: #b53c3c;
	--ok: #2c7658;
	--warn: #926727;
	--c1: #eee6f9; --c2: #d3bdee; --c3: #a986dc; --c4: #7c4dbe;
	--rail: 13rem;
	--topbar: 3.5rem;
	--gap: 1.5rem;
	--radius: .375rem;
	--radius-sm: .25rem;
	--radius-md: var(--radius);
	--surface-1: var(--surface);
	/* Only things that float over the page (dialogs, menus) cast one. */
	--shadow: 0 6px 24px rgb(0 0 0 / .12);
	--shadow-sm: none;
	--cover-ratio: 2 / 3;
	--card-min: clamp(7.5rem, 40vw, 11rem);
	--book-spine-width: 14px;
	--book-spine: linear-gradient(to right, rgb(0 0 0 / .2) 0%, rgb(0 0 0 / .06) 35%, rgb(255 255 255 / .2) 65%, transparent 100%);
	--book-edge: linear-gradient(to left, rgb(0 0 0 / .12) 0%, transparent 100%);
	--book-shadow: 0 1px 3px rgb(0 0 0 / .14);
	--book-shadow-hover: 0 1px 3px rgb(0 0 0 / .14);
}

[data-theme="dark"], .reader-body[data-reader-theme="dark"], .reader-body[data-reader-theme="black"] {
	color-scheme: dark;
	--bg: #121212;
	--surface: #18181a;
	--surface-2: #212123;
	--surface-3: #2b2b2e;
	--line: #353538;
	--line-soft: #262628;
	--ink: #e9e7e4;
	--ink-dim: #bdbab5;
	--muted: #96938e;
	--primary: #c4a7e7;
	--primary-dim: #d7c2f2;
	--on-primary: #1d1526;
	--danger: #f28c8c;
	--ok: #86ceab;
	--warn: #e1bb78;
	--c1: #2a2340; --c2: #453a68; --c3: #6f5aa0; --c4: #c4a7e7;
	--shadow: 0 6px 24px rgb(0 0 0 / .45);
	--shadow-sm: none;
	--book-spine: linear-gradient(to right, rgb(0 0 0 / .45) 0%, rgb(0 0 0 / .18) 35%, rgb(255 255 255 / .12) 65%, transparent 100%);
	--book-edge: linear-gradient(to left, rgb(0 0 0 / .35) 0%, transparent 100%);
	--book-shadow: 2px 6px 18px rgb(0 0 0 / .4), 0 2px 4px rgb(0 0 0 / .3);
	--book-shadow-hover: 4px 14px 28px rgb(0 0 0 / .55), 0 3px 8px rgb(0 0 0 / .4);
}

[data-theme="tokyo-night"], .reader-body[data-reader-theme="tokyo-night"] {
	color-scheme: dark;

	--bg: #1a1b26;
	--surface: #16161e;
	--surface-2: #1f2335;
	--surface-3: #292e42;
	--line: #3b4261;
	--line-soft: #292e42;
	--ink: #c0caf5;
	--ink-dim: #a9b1d6;
	--muted: #8994b8;
	--primary: #7aa2f7;
	--primary-dim: #3d59a1;
	--on-primary: #1a1b26;
	--danger: #f7768e;
	--ok: #73daca;
	--warn: #e0af68;

	--c1: #1f2a44; --c2: #2d4f7a; --c3: #3d7fb3; --c4: #7dcfff;
	--book-spine: linear-gradient(to right, rgb(0 0 0 / .45) 0%, rgb(0 0 0 / .18) 35%, rgb(255 255 255 / .12) 65%, transparent 100%);
	--book-edge: linear-gradient(to left, rgb(0 0 0 / .35) 0%, transparent 100%);
	--book-shadow: 2px 6px 18px rgb(0 0 0 / .4), 0 2px 4px rgb(0 0 0 / .3);
	--book-shadow-hover: 4px 14px 28px rgb(0 0 0 / .55), 0 3px 8px rgb(0 0 0 / .4);
}

[data-theme="rose-pine"], .reader-body[data-reader-theme="rose-pine"] {
	color-scheme: dark;

	--bg: #191724;
	--surface: #1f1d2e;
	--surface-2: #26233a;
	--surface-3: #403d52;
	--line: #524f67;
	--line-soft: #26233a;
	--ink: #e0def4;
	--ink-dim: #908caa;
	--muted: #a5a0b8;
	--primary: #c4a7e7;
	--primary-dim: #907aa9;
	--on-primary: #191724;
	--danger: #eb6f92;
	--ok: #9ccfd8;
	--warn: #f6c177;

	--c1: #213b44; --c2: #2f6172; --c3: #6e9ca8; --c4: #9ccfd8;
	--book-spine: linear-gradient(to right, rgb(0 0 0 / .45) 0%, rgb(0 0 0 / .18) 35%, rgb(255 255 255 / .12) 65%, transparent 100%);
	--book-edge: linear-gradient(to left, rgb(0 0 0 / .35) 0%, transparent 100%);
	--book-shadow: 2px 6px 18px rgb(0 0 0 / .4), 0 2px 4px rgb(0 0 0 / .3);
	--book-shadow-hover: 4px 14px 28px rgb(0 0 0 / .55), 0 3px 8px rgb(0 0 0 / .4);
}

/* Only an explicit system preference follows the OS palette. */
@media (prefers-color-scheme: dark) {
	[data-theme="system"] {
		color-scheme: dark;
		--bg: #121212;
		--surface: #18181a;
		--surface-2: #212123;
		--surface-3: #2b2b2e;
		--line: #353538;
		--line-soft: #262628;
		--ink: #e9e7e4;
		--ink-dim: #bdbab5;
		--muted: #96938e;
		--primary: #c4a7e7;
		--primary-dim: #d7c2f2;
		--on-primary: #1d1526;
		--danger: #f28c8c;
		--ok: #86ceab;
		--warn: #e1bb78;
		--c1: #2a2340; --c2: #453a68; --c3: #6f5aa0; --c4: #c4a7e7;
		--shadow: 0 6px 24px rgb(0 0 0 / .45);
		--shadow-sm: none;
		--book-spine: linear-gradient(to right, rgb(0 0 0 / .45) 0%, rgb(0 0 0 / .18) 35%, rgb(255 255 255 / .12) 65%, transparent 100%);
		--book-edge: linear-gradient(to left, rgb(0 0 0 / .35) 0%, transparent 100%);
		--book-shadow: 2px 6px 18px rgb(0 0 0 / .4), 0 2px 4px rgb(0 0 0 / .3);
		--book-shadow-hover: 4px 14px 28px rgb(0 0 0 / .55), 0 3px 8px rgb(0 0 0 / .4);
	}
}

/* ----------------------------------------------------------------- reset */

* { box-sizing: border-box; }

body {
	margin: 0;
	background: var(--bg);
	color: var(--ink);
	font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
	font-size: 15px;
	line-height: 1.55;
	-webkit-text-size-adjust: 100%;
	/* Mobile browsers flash a platform-coloured box on every tap; this
	   UI's own pressed states are the answer it would be repeating. */
	-webkit-tap-highlight-color: transparent;
}

a { color: var(--primary); }
a:hover { color: var(--ink); }

h1 { font: 400 clamp(1.6rem, 2.6vw, 1.95rem)/1.2 Georgia, "Times New Roman", serif; margin: 0 0 .8rem; letter-spacing: -.01em; }
h2 { font-size: 1.05rem; font-weight: 600; margin: 0 0 .75rem; letter-spacing: 0; }
h3 { font-size: .95rem; margin: 0 0 .4rem; }

:focus-visible {
	outline: 2px solid var(--primary);
	outline-offset: 2px;
	border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
	* { transition: none !important; animation: none !important; }
}

.visually-hidden {
	position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
	overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip {
	position: absolute; left: .5rem; top: -3rem; z-index: 60;
	background: var(--primary); color: var(--on-primary);
	padding: .5rem .9rem; border-radius: var(--radius-sm);
	text-decoration: none; transition: top .15s;
}
.skip:focus { top: .5rem; }

/* ----------------------------------------------------------------- shell */

.app {
	display: grid;
	grid-template-areas: "top top" "rail main";
	grid-template-columns: var(--rail) 1fr;
	grid-template-rows: auto 1fr;
	min-height: 100vh;
}

.topbar {
	grid-area: top;
	display: flex; align-items: center; gap: 1rem;
	min-height: calc(var(--topbar) + env(safe-area-inset-top, 0px));
	padding-top: max(.5rem, env(safe-area-inset-top, 0px));
	padding-bottom: .5rem;
	padding-left: max(1.25rem, env(safe-area-inset-left, 0px));
	padding-right: max(1.5rem, env(safe-area-inset-right, 0px));
	background: var(--surface);
	border-bottom: 1px solid var(--line);
	position: sticky; top: 0; z-index: 30;
}

.brand {
	font-weight: 700; letter-spacing: -.02em;
	color: var(--ink); text-decoration: none;
	display: flex; align-items: center; gap: .7rem; flex: none;
}
.brand:hover { color: var(--primary); }
.brand .brand-icon { display: block; flex: none; width: 1.9rem; height: 1.9rem; }
.brand .brand-name { display: flex; align-items: baseline; gap: .5rem; line-height: 1; }
.brand .brandmark {
	font: 400 1.35rem/1 Georgia, "Times New Roman", serif;
	letter-spacing: -.02em;
}
.brand .suffix { font-size: .75rem; font-weight: 400; color: var(--muted); }

.account { margin-left: auto; display: flex; align-items: center; gap: 1rem; flex: none; }
.account .who { display: flex; align-items: center; gap: .4rem; color: var(--muted); font-size: .8rem; max-width: 14rem; overflow-wrap: anywhere; }
.account-avatar { flex: none; display: grid; place-items: center; color: var(--muted); }
.account-avatar .ui-icon { width: 1rem; height: 1rem; }
.account form { margin: 0; }
.account button {
	font-size: .8rem; color: var(--muted);
	background: none; border: 0; padding: .3rem .2rem; min-height: 0;
}
.account button:hover { color: var(--ink); background: none; }
.account form:first-child button { font-size: 1rem; padding: .3rem; }
.ui-icon { flex: none; display: block; }

.rail {
	grid-area: rail;
	background: var(--surface);
	border-right: 1px solid var(--line);
	padding: 1.25rem .75rem 1rem;
	position: sticky; top: var(--topbar);
	align-self: start;
	height: calc(100vh - var(--topbar)); height: calc(100dvh - var(--topbar));
	display: flex; flex-direction: column;
}
.rail nav { display: flex; flex-direction: column; gap: .1rem; }
.rail a {
	display: flex; align-items: center; gap: .6rem;
	min-height: 2.25rem; padding: .35rem .65rem;
	border-radius: var(--radius-sm);
	color: var(--ink-dim); text-decoration: none;
	font-size: .875rem;
}
.rail a:hover { color: var(--ink); }
.rail a[aria-current="page"] { background: var(--surface-2); color: var(--ink); font-weight: 600; }
.rail a[aria-current="page"] .ui-icon { color: var(--primary); }
main {
	grid-area: main;
	padding: 1.5rem clamp(1.25rem, 2.5vw, 2.25rem) calc(3rem + env(safe-area-inset-bottom, 0px));
	padding-left: max(clamp(1.25rem, 2.5vw, 2.25rem), env(safe-area-inset-left, 0px));
	padding-right: max(clamp(1.25rem, 2.5vw, 2.25rem), env(safe-area-inset-right, 0px));
	min-width: 0;
}
/* Content starts at the rail, as in any desktop app, rather than
   floating in the middle of the window with a gap on either side. */
.page { max-width: 84rem; margin: 0; }
.foot { padding: 2.5rem 0 0; }

@media (max-width: 60em) {
	.app { grid-template-areas: "top" "rail" "main"; grid-template-columns: 1fr; }
	.rail {
		position: sticky; top: var(--topbar); width: auto; height: auto; z-index: 20;
		padding: .5rem clamp(.75rem, 3vw, 1.5rem);
		border-right: 0; border-bottom: 1px solid var(--line);
		box-shadow: none;
	}
	.rail nav { flex-direction: row; flex-wrap: wrap; justify-content: center; gap: .25rem; }
	.rail a { flex: 1 1 auto; justify-content: center; min-height: 2.5rem; padding: .4rem .4rem; }
	main {
		padding: 1.75rem 1.25rem calc(3rem + env(safe-area-inset-bottom, 0px));
		padding-left: max(1.25rem, env(safe-area-inset-left, 0px));
		padding-right: max(1.25rem, env(safe-area-inset-right, 0px));
	}
}
@media (max-width: 36em) {
	:root { --topbar: 3.25rem; --gap: 1.1rem; }
	.topbar {
		padding-top: max(.6rem, env(safe-area-inset-top, 0px));
		padding-bottom: .6rem;
		padding-left: max(1rem, env(safe-area-inset-left, 0px));
		padding-right: max(1rem, env(safe-area-inset-right, 0px));
		gap: .7rem;
	}
	.brand .brand-icon { width: 1.7rem; height: 1.7rem; }
	.brand .brandmark { font-size: 1.25rem; }
	.brand .suffix { font-size: .7rem; }
	.brand, .brand .brand-name { gap: .35rem; }
	.account { gap: .65rem; }
	.account .who { display: none; }
	.account button { font-size: .72rem; }
	.rail a { gap: .4rem; padding-inline: .3rem; font-size: .78rem; }
}

/* ------------------------------------------------------------ components */

.card {
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	padding: 1.5rem 1.65rem;
	margin: 0 0 var(--gap);
	box-shadow: var(--shadow-sm);
}
.cardhead {
	display: flex; align-items: center; justify-content: space-between;
	gap: 1rem; margin: 0 0 1rem;
}
.cardhead h2 { margin: 0; }
.section-link { font-size: .8rem; text-decoration: none; white-space: nowrap; }
.section-link span { margin-left: .4rem; }
details.card > summary {
	cursor: pointer; font-weight: 600; color: var(--ink);
	list-style: none; display: flex; align-items: center; gap: .5rem;
}
details.card > summary::before { content: "+"; color: var(--muted); }
details.card[open] > summary::before { content: "−"; }

/* A disclosure inside a form (the root-library form's advanced axes):
   quieter than a card summary, since the action below it is the point. */
details.advanced { margin: 0 0 .8rem; }
details.advanced > summary {
	cursor: pointer; color: var(--muted); list-style: none;
	display: flex; align-items: center; gap: .5rem; font-size: .95rem;
}
details.advanced > summary::before { content: "+"; }
details.advanced[open] > summary::before { content: "−"; }

/* --------------------------------------------------------- folder setup */

.folder-heading {
	display: flex; align-items: end; justify-content: space-between;
	gap: 1rem; margin: 0 0 .55rem; flex-wrap: wrap;
}
.folder-heading h1 { margin: 0; }
.folder-actions { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.folder-actions .scanform { margin: 0; }
.folder-intro { max-width: 52rem; margin: 0 0 1.1rem; }
.onboarding-card {
	display: flex; align-items: flex-start; gap: 1.1rem;
	padding: clamp(1.25rem, 4vw, 2.2rem);
	border-color: color-mix(in srgb, var(--primary) 45%, var(--line));
	background:
		linear-gradient(135deg, color-mix(in srgb, var(--primary) 13%, var(--surface)), var(--surface));
}
.onboarding-icon {
	display: flex; align-items: center; justify-content: center;
	flex: 0 0 2.8rem; height: 2.8rem; border-radius: 50%;
	background: var(--primary); color: var(--on-primary);
	font-size: 1.7rem; font-weight: 700; line-height: 1;
}
.onboarding-copy { max-width: 43rem; }
.onboarding-copy h2 { font-size: 1.35rem; margin-bottom: .55rem; }
.onboarding-copy p { margin: .45rem 0; }
.onboarding-actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: 1rem; }
.folder-form-fallback { margin-top: 1.2rem; }
.dialog-supported .folder-form-fallback { display: none; }
.folder-form-fallback .cardhead { align-items: flex-start; }
.folder-form-fallback .cardhead p { margin: .25rem 0 0; max-width: 42rem; }
.folder-form { max-width: 42rem; }
.folder-form .folder-roots { margin: .2rem 0 .8rem; }
.folder-form .folder-roots code { overflow-wrap: anywhere; }
.folder-form .folder-hint { margin: -.35rem 0 0; font-size: .78rem; }
.folder-form .form-actions { margin-top: .9rem; }
.folder-dialog {
	display: none;
	width: min(34rem, calc(100% - 2rem)); max-width: 100%;
	margin: auto; padding: 1.25rem 1.35rem;
	background: var(--surface); color: inherit;
	border: 1px solid var(--line); border-radius: var(--radius);
	box-shadow: var(--shadow); font-size: .95rem;
}
.folder-dialog[open] { display: block; }
.folder-dialog::backdrop { background: rgb(0 0 0 / .58); }
.dialog-head {
	display: flex; align-items: flex-start; justify-content: space-between;
	gap: 1rem; margin: 0 0 .7rem;
}
.dialog-head h2 { font-size: 1.25rem; margin: 0; }
.dialog-head form { margin: 0; }
.dialog-close { color: var(--muted); }
.dialog-close:hover { color: var(--ink); }

/* The add-library form: the folder fields only matter once "a folder on
   this server" is picked. Hidden with :has() so it needs no script; a
   browser without :has() shows the whole form, which still works. */
.addlib .choice { border: 0; padding: 0; margin: 0 0 .8rem; }
.addlib .choice legend { font-weight: 600; padding: 0 0 .3rem; }
.addlib:has(input[name="from"][value="uploads"]:checked) .folder-only {
	display: none;
}

.narrow { max-width: 23rem; margin: 4rem auto; }

/* Settings is the single account hub. The two-level tab treatment keeps
   profile, devices and admin work distinct without adding another rail
   entry or requiring JavaScript. */
.settings-heading { margin: 0 0 2rem; }
.settings-tabs, .settings-subnav {
	display: flex; flex-wrap: wrap; gap: .35rem;
	margin: 0 0 var(--gap); padding: 0;
	border-bottom: 1px solid var(--line);
}
.settings-tabs a, .settings-subnav a {
	padding: .85rem 1rem;
	border-bottom: 2px solid transparent;
	color: var(--ink-dim);
	text-decoration: none;
	font-size: .82rem;
	white-space: nowrap;
}
.settings-tabs a:hover, .settings-subnav a:hover {
	background: var(--surface-3);
	color: var(--ink);
}
.settings-tabs a[aria-current="page"] {
	border-bottom-color: var(--primary);
	color: var(--primary);
	font-weight: 600;
}
.settings-subnav {
	margin-top: -.25rem;
	background: var(--surface-2);
	border: 1px solid var(--line-soft); border-radius: var(--radius-sm); padding: .25rem;
}
.settings-subnav a { border: 0; border-radius: .4rem; padding: .5rem .8rem; font-size: .78rem; }
.settings-subnav a[aria-current="page"] {
	background: var(--surface-3);
	color: var(--ink);
	font-weight: 600;
}
.settings-card > form { max-width: 36rem; }
.card > h2 + .muted { font-size: .87rem; margin-top: -.15rem; max-width: 65ch; }
.card form > label:not(.check), .card form > p:has(input, select, textarea),
.card fieldset > label:not(.check) {
	display: grid; gap: .5rem; max-width: 36rem; margin: 1.1rem 0;
}
.card form > label:not(.check) > :is(input, select, textarea),
.card fieldset > label:not(.check) > :is(input, select, textarea) { width: 100%; min-width: 0; }
.card fieldset { margin: 1.5rem 0; padding: 1rem 1.2rem; border: 1px solid var(--line); border-radius: var(--radius-sm); }
.card fieldset legend { padding: 0 .4rem; color: var(--ink-dim); font-size: .8rem; font-weight: 600; }
.card fieldset label:last-child { margin-bottom: 0; }
/* The reader prompt is a piece of writing rather than a field: it gets
   room to be read back whole, and a grip to make it taller. */
#reader-prompt-template { min-height: 13rem; line-height: 1.5; resize: vertical; }
.card form.inline { align-items: flex-end; }
.card form.inline > label { margin-bottom: 0; }

@media (max-width: 42em) {
	.onboarding-card { flex-direction: column; }
	.folder-dialog { padding: 1rem; }
}

/* The admin section's own navigation: one rail entry, several pages. */
.subnav {
	display: flex; flex-wrap: wrap; gap: .3rem;
	margin: 0 0 var(--gap); padding-bottom: .5rem;
	border-bottom: 1px solid var(--line);
}
.subnav a {
	padding: .3rem .7rem; border-radius: 2rem; text-decoration: none;
	color: var(--ink-dim); font-size: .9rem;
}
.subnav a:hover { background: var(--surface-3); color: var(--ink); }
.subnav a[aria-current="page"] {
	background: var(--primary); color: var(--on-primary);
}

/* A labelled list of read-only facts: config, build, counts. */
dl.facts {
	display: grid; grid-template-columns: minmax(10rem, auto) 1fr;
	gap: .35rem 1rem; margin: 0;
}
dl.facts dt { color: var(--muted); font-size: .85rem; }
dl.facts dd { margin: 0; }
@media (max-width: 30rem) {
	dl.facts { grid-template-columns: 1fr; gap: 0 0; }
	dl.facts dd { margin: 0 0 .5rem; }
}

/* Six or seven tiles will not fit across one line at the width the tile
   was drawn at, and squeezing them makes a value like "5%/h" run under
   the corner icon. A grid keeps every tile the same readable size and
   wraps the surplus onto a second line. */
.cards {
	display: grid; gap: var(--gap); margin: 0 0 var(--gap);
	grid-template-columns: repeat(auto-fit, minmax(13.5rem, 1fr));
}
/* Below the width two of those fit in, auto-fit gives up and stacks
   them one to a row, which turns six short numbers into a page of
   scrolling. A phone gets two columns and smaller type instead. */
@media (max-width: 34em) {
	.cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem; }
	.stat { padding: 1rem; }
}
.stat {
	background: var(--surface); border: 1px solid var(--line);
	border-radius: var(--radius); padding: 1.6rem; min-width: 0;
	position: relative; box-shadow: var(--shadow-sm);
	display: flex; flex-direction: column;
	transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.stat:hover {
	transform: translateY(-2px);
	border-color: color-mix(in srgb, var(--primary) 30%, var(--line));
	box-shadow: var(--shadow);
}
.stat .num { font: 400 2.7rem/1.15 Georgia, serif; letter-spacing: -.04em; font-variant-numeric: lining-nums tabular-nums; padding-right: 3rem; }
.stat .lbl { color: var(--muted); font-size: .78rem; margin-top: .55rem; }
/* A date or a duration is several words wide where a count is two
   characters, so it is set smaller and kept on one line. */
.stat .num.words { font-size: 1.65rem; letter-spacing: -.01em; white-space: nowrap; }
.stat-icon { position: absolute; top: 1.5rem; right: 1.5rem; display: grid; place-items: center; width: 2.5rem; height: 2.5rem; border-radius: .8rem; color: var(--primary); background: var(--surface-2); }
/* The streak is the one figure on the row that reaches back past the
   span, so it is the one tile that is coloured differently. It says so
   by name: it used to be :last-child, which stopped meaning "streak"
   the moment another tile was added after it. */
.stat.accent .stat-icon { color: var(--warn); background: color-mix(in srgb, var(--warn) 9%, var(--surface)); }
@media (max-width: 48em) {
	.stat { padding: 1.1rem; min-width: 0; }
	.stat-icon { position: static; width: 2rem; height: 2rem; margin-bottom: .7rem; }
	.stat .num { font-size: 2rem; padding-right: 0; }
	.stat .num.words { font-size: 1.4rem; }
	.stat .lbl { font-size: .7rem; }
}

button, .btn {
	font: inherit; line-height: 1.4;
	padding: .6rem 1rem; border-radius: var(--radius-sm);
	border: 1px solid var(--line); background: var(--surface);
	color: var(--ink); cursor: pointer; text-decoration: none;
	display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
	font-size: .85rem; font-weight: 500; transition: background .15s, border-color .15s, color .15s;
}
button:hover, .btn:hover { background: var(--surface-3); color: var(--ink); }
button.primary, .btn.primary {
	background: var(--primary); border-color: var(--primary); color: var(--on-primary);
	font-weight: 600;
}
button.primary:hover, .btn.primary:hover { background: var(--primary-dim); color: var(--on-primary); }
button.danger { color: var(--danger); border-color: transparent; background: none; }
button.danger:hover { background: color-mix(in srgb, var(--danger) 15%, transparent); }
button.link { background: none; border: none; color: var(--muted); padding: .2rem; }
button.link:hover { color: var(--ink); background: none; }
button[disabled] { opacity: .5; cursor: not-allowed; }

table { border-collapse: collapse; width: 100%; margin: .4rem 0; font-size: .92rem; }
th, td { text-align: left; padding: .85rem .8rem; border-bottom: 1px solid var(--line-soft); }
th { color: var(--muted); font-weight: 500; font-size: .8rem; border-bottom-color: var(--line); }
td { font-size: .85rem; }
td a { text-decoration-thickness: 1px; text-underline-offset: .2em; }
tbody tr:hover { background: var(--surface-2); }
tr:last-child td { border-bottom: 0; }

.session-device { width: 10rem; max-width: 10rem; }
.session-device .device-name,
.session-device .device-id {
	display: block; max-width: 100%; overflow: hidden;
	text-overflow: ellipsis; white-space: nowrap;
}
.session-device .device-name { font-weight: 600; }
.session-device .device-id {
	width: 100%; color: var(--muted); font-size: .76em;
}

code {
	background: var(--surface-2); border: 1px solid var(--line-soft);
	padding: .1em .35em; border-radius: var(--radius-sm);
	font-size: .88em;
}
code.big { font-size: 1rem; display: inline-block; padding: .45rem .6rem; word-break: break-all; }
.page code, .page dd { overflow-wrap: anywhere; }
.page dd { min-width: 0; }
.secret-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.secret-row .copy-btn { flex: none; }

/* A field is a stack: label, control, then whatever the server has to
   say about it. Left to themselves these run into each other, because a
   textarea and its hint are siblings. */
.card form p { display: flex; flex-direction: column; gap: .25rem; max-width: 40rem; }
.card form p > label { margin: 0; }
.card form p > .muted { font-size: .8rem; }
.card form p > input, .card form p > textarea { width: 100%; }

form.inline { display: flex; gap: .6rem; align-items: center; margin-top: .7rem; flex-wrap: wrap; }
form label { display: block; margin: .6rem 0; color: var(--ink-dim); font-size: .9rem; }
form label.check { display: flex; gap: .5rem; align-items: center; }
.narrow form > label {
	display: grid; grid-template-columns: max-content minmax(0, 1fr);
	align-items: center; column-gap: .6rem; text-align: right;
}
.narrow form > label > input { width: 100%; min-width: 0; }
input, select, textarea {
	font: inherit; padding: .65rem .8rem;
	background: var(--surface); color: var(--ink);
	border: 1px solid var(--line); border-radius: var(--radius-sm);
}
input:hover, select:hover { border-color: var(--muted); }
input[type="checkbox"], input[type="radio"] { accent-color: var(--primary); flex: none; }

/* Setup and sign-in: one centred card. */
.auth-main {
	min-height: 100vh; min-height: 100svh;
	display: grid; place-items: center;
	padding: 3rem 1.5rem;
	background: var(--bg);
}
.auth-wrap { width: min(100%, 28rem); }
.auth-shell {
	/* The auth form keeps one brand palette in every theme, so its
	   colours are tokens of its own rather than literals repeated where
	   a theme change would never find them. */
	--auth-primary: #c4a7e7;
	--auth-primary-dim: #d7c2f2;
	--auth-on-primary: #1d1526;
	--auth-focus: #9d7fd0;
	padding: 2.5rem 2.25rem;
	border: 1px solid var(--line); border-radius: var(--radius);
	background: var(--surface);
}
.auth-brand { display: flex; align-items: center; gap: .7rem; margin: 0 0 2rem; color: var(--ink); }
.auth-icon { display: block; flex: none; width: 2.5rem; height: 2.5rem; }
.auth-brand .brand-name { display: flex; align-items: baseline; gap: .5rem; line-height: 1; }
.auth-brand .brandmark { font: 400 1.65rem/1 Georgia, "Times New Roman", serif; letter-spacing: -.035em; }
.auth-brand .suffix { font-size: .72rem; font-weight: 500; color: var(--ink-dim); letter-spacing: .025em; }
.auth-shell h1 { margin: 0 0 1.25rem; font-family: ui-sans-serif, system-ui, sans-serif; font-size: 1.6rem; line-height: 1.2; letter-spacing: -.03em; font-weight: 650; }
.auth-intro { margin: -.5rem 0 1.5rem; color: var(--ink-dim); font-size: .9rem; }
.auth-form { display: grid; gap: 1.1rem; }
.auth-form label { display: grid; gap: .45rem; margin: 0; color: var(--ink); font-size: .8rem; font-weight: 500; }
.auth-form input:not([type="checkbox"]) {
	width: 100%; min-width: 0; min-height: 3rem; padding: .75rem .9rem;
	background: var(--bg); border: 1px solid var(--line); border-radius: .6rem;
	color: var(--ink); font: inherit; font-size: 1rem;
	transition: border-color .15s, box-shadow .15s;
}
.auth-form input:not([type="checkbox"]):hover { border-color: var(--muted); }
.auth-form input:not([type="checkbox"]):focus-visible { border-color: var(--auth-focus); outline: 2px solid color-mix(in srgb, var(--auth-focus) 50%, transparent); outline-offset: 2px; }
.auth-hint { font-size: .73rem; font-weight: 400; color: var(--ink-dim); }
.auth-form .auth-submit {
	justify-content: space-between; width: 100%; min-height: 3.1rem; margin-top: .4rem;
	padding: .8rem 1rem; border: 1px solid var(--auth-primary); border-radius: .6rem;
	background: var(--auth-primary); color: var(--auth-on-primary); font-size: .87rem; font-weight: 650;
	transition: background .15s;
}
.auth-form .auth-submit:hover { background: var(--auth-primary-dim); }
.auth-submit span { font-size: 1.25rem; line-height: 1; }
.auth-note { margin: 1.4rem 0 0; padding-top: 1.2rem; border-top: 1px solid var(--line-soft); color: var(--ink-dim); font-size: .75rem; line-height: 1.7; }

/* The optional folder half of the first-run form (#53). It is set apart
 * from the account fields rather than listed with them, so that "you can
 * leave this" is legible before anything is typed. */
.auth-folder { margin: .4rem 0 0; padding: 1.1rem 1.1rem 1.2rem; display: grid; gap: .9rem; border-radius: .7rem; border-color: var(--line-soft); background: var(--surface-2); }
.auth-folder legend { display: flex; align-items: center; gap: .5rem; padding: 0 .4rem; color: var(--ink); font-size: .8rem; font-weight: 600; }
.auth-folder .auth-hint { margin: 0; line-height: 1.6; }
.auth-folder .auth-hint code { font-size: .95em; }
.auth-optional { padding: .1rem .4rem; border-radius: .3rem; background: var(--surface-3); color: var(--muted); font-size: .62rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; }

.auth-footer { margin: 1.5rem 0 0; text-align: center; }

/* The credit line: small print in the shell footer and on the auth
 * screens — everywhere but the reader, which owns its whole viewport.
 * Low contrast on purpose — it should be
 * findable, not loud. */
.credit { margin: 0; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: .45rem; color: var(--ink-dim); font-size: .72rem; letter-spacing: .025em; }
.credit-link { display: inline-flex; align-items: center; gap: .3rem; color: var(--ink-dim); text-decoration: none; border-bottom: 1px solid transparent; }
.credit-link:hover, .credit-link:focus-visible { color: var(--primary); border-bottom-color: currentColor; }
.credit-icon { flex: none; vertical-align: -.15em; }
.credit-dot { color: var(--line); }
.credit-build { color: var(--muted); font-variant-numeric: tabular-nums; }
@media (max-width: 44em) {
	.auth-main { padding: 1.5rem 1rem; }
	.auth-shell { padding: 2rem 1.5rem; }
}
fieldset { border: 1px solid var(--line); border-radius: var(--radius-sm); margin: .8rem 0; }
legend { color: var(--muted); font-size: .8rem; padding: 0 .4rem; }

.error { color: var(--danger); }
.ok, .notice { color: var(--ok); }
.muted { color: var(--muted); }

.tag, .chip {
	display: inline-flex; align-items: center; gap: .3rem;
	font-size: .78rem; background: var(--surface-3); color: var(--ink-dim);
	border: 1px solid var(--line); border-radius: var(--radius-sm);
	padding: .1rem .5rem; text-decoration: none;
}
a.chip:hover { background: var(--primary); border-color: var(--primary); color: var(--on-primary); }
.chip.on { background: var(--primary); border-color: var(--primary); color: var(--on-primary); }
.chips { display: flex; flex-wrap: wrap; gap: .35rem; margin: .4rem 0; }
.secret { border-color: var(--primary); }

.bar {
	display: inline-block; width: 7em; height: .5rem; vertical-align: middle;
	background: var(--surface-3); border-radius: 1px; overflow: hidden; margin-right: .5rem;
}
.bar span { display: block; height: 100%; background: var(--primary); }

/* ------------------------------------------------------------- insights */

.insightshead {
	display: flex; align-items: end; justify-content: space-between;
	gap: 1rem; margin: 0 0 2rem; flex-wrap: wrap;
}
.insightshead h1 { margin: 0; }
.insightshead .eyebrow { margin: 0 0 .5rem; }
.spanpick { margin: 0; }
.spanpick select { min-width: min(11rem, 44vw); font-size: .8rem; }

/* One bar a bucket. The row stretches, so a week's seven bars are wide
   and a quarter's thirteen are narrower, and neither leaves a gap at
   the end. */
.daybars {
	display: flex; align-items: flex-end; gap: 3px;
	margin: 1rem 0 0; padding-top: 1rem;
	background: repeating-linear-gradient(to top, transparent, transparent 47px, var(--line-soft) 47px, var(--line-soft) 48px);
}
/* Wider buckets are fewer, and a bar as wide as a book cover reads as a
   panel rather than as a measurement. */
.bars-week, .bars-month, .bars-year { gap: .5rem; }
.bars-week .daybar, .bars-month .daybar, .bars-year .daybar { max-width: 5rem; }
.daybar { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: .35rem; }
/* The amount only appears where there is room for it not to collide
   with the one next door; past that the height is the answer and the
   tooltip has the rest. */
.daybar-amt {
	font-size: .68rem; color: var(--muted); text-align: center;
	white-space: nowrap; overflow: hidden; min-height: 1em;
	font-variant-numeric: lining-nums tabular-nums;
}
/* No track behind the bar. A full-height box on a day with no reading
   in it reads as a bar in its own right, and the chart then says the
   opposite of what happened; an empty day is a stub on the baseline. */
.daybar-track { height: 9rem; display: flex; align-items: flex-end; }
/* The height classes are the progress bars' percentage steps (.p0-.p100)
   turned on their side; the CSP has no unsafe-inline, so a bar's height
   has to be a class like every other measurement here. */
.daybar-track > span {
	width: 100%; align-self: flex-end;
	border-radius: 3px 3px 0 0;
}
.daybar-track > .c0 { height: 2px; background: var(--surface-3) }
.daybar-track > .p5 { height: 5% }
.daybar-track > .p10 { height: 10% } .daybar-track > .p15 { height: 15% }
.daybar-track > .p20 { height: 20% } .daybar-track > .p25 { height: 25% }
.daybar-track > .p30 { height: 30% } .daybar-track > .p35 { height: 35% }
.daybar-track > .p40 { height: 40% } .daybar-track > .p45 { height: 45% }
.daybar-track > .p50 { height: 50% } .daybar-track > .p55 { height: 55% }
.daybar-track > .p60 { height: 60% } .daybar-track > .p65 { height: 65% }
.daybar-track > .p70 { height: 70% } .daybar-track > .p75 { height: 75% }
.daybar-track > .p80 { height: 80% } .daybar-track > .p85 { height: 85% }
.daybar-track > .p90 { height: 90% } .daybar-track > .p95 { height: 95% }
.daybar-track > .p100 { height: 100% }
.daybar-cap {
	font-size: .65rem; color: var(--muted); text-align: center;
	white-space: nowrap; overflow: hidden; min-height: 1em;
}

/* Weeks run down the columns and weekdays across the rows, so the grid
   reads like a wall calendar. It scrolls rather than shrinking: a year
   of 11px squares is wider than a phone, and squeezing them would cost
   the one thing the picture has, which is that a square is a day. */
.heatwrap { overflow-x: auto; margin: .5rem 0 0; padding-bottom: .25rem; }
.heatmap { display: flex; gap: 3px; width: max-content; }
.heatweek { width: 11px; display: flex; flex-direction: column; gap: 3px; }
.heatdays { display: grid; grid-template-rows: repeat(7, 11px); gap: 3px; }
/* The label is wider than the column it names and is allowed to say so:
   it runs on over the columns to its right, which are the rest of that
   same month. */
.heatmonth {
	font-size: .65rem; line-height: 1; height: 1em;
	color: var(--muted); white-space: nowrap;
}
.cell { width: 11px; height: 11px; border-radius: 2px; background: var(--surface-3); }
.cell.blank { background: none; }
.cell.c1 { background: var(--c1); } .cell.c2 { background: var(--c2); }
.cell.c3 { background: var(--c3); } .cell.c4 { background: var(--c4); }

/* ------------------------------------------------- headline and breakdown */

/* The one number the dashboard is about, said once and said large. The
   tiles beneath it are the supporting cast, so this is deliberately a
   different shape from them: no grid, no icon in the corner, nothing
   competing for the eye. */
.headline-total {
	text-align: center;
	padding: 2.2rem 1.6rem;
	margin: 0 0 var(--gap);
}
.headline-label {
	margin: 0; display: inline-flex; align-items: center; gap: .5rem;
	color: var(--muted); font-size: .74rem;
	letter-spacing: .14em; text-transform: uppercase;
}
.headline-icon { display: inline-grid; place-items: center; color: var(--primary); }
.headline-icon .ui-icon { width: 1rem; height: 1rem; }
.headline-value {
	margin: .6rem 0 0;
	font: 400 3.4rem/1.05 Georgia, serif; letter-spacing: -.045em;
	font-variant-numeric: lining-nums tabular-nums;
}
.headline-caption { margin: .55rem 0 0; color: var(--muted); font-size: .85rem; }
@media (max-width: 48em) {
	.headline-total { padding: 1.5rem 1.1rem; }
	.headline-value { font-size: 2.5rem; }
}

/* Two links, not a control: each is a URL that says which view it is
   and carries the span with it, so switching view cannot silently
   switch span, and neither needs a script. */
.charttabs { display: inline-flex; gap: .25rem; padding: .25rem; border-radius: 999px; background: var(--surface-2); }
.charttabs a, .charttabs span {
	padding: .35rem .85rem; border-radius: 999px;
	font-size: .78rem; text-decoration: none; color: var(--muted);
}
.charttabs a:hover { color: var(--ink); background: var(--surface-3); }
.charttabs [aria-current] { color: var(--ink); background: var(--surface); box-shadow: var(--shadow-sm); }

.count-badge {
	display: grid; place-items: center; min-width: 1.7rem; height: 1.7rem;
	padding: 0 .45rem; border-radius: 999px;
	background: var(--surface-2); color: var(--muted);
	font-size: .75rem; font-variant-numeric: lining-nums tabular-nums;
}

/* Where the span's reading went, most time first. A list rather than a
   shelf of covers: the shelf above already asks "what am I reading",
   and this asks a different question, which is "what did I actually
   spend the time on". */
.bookstats { list-style: none; margin: .4rem 0 0; padding: 0; }
.bookstat {
	display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
	padding: .8rem 0; border-bottom: 1px solid var(--line-soft);
}
.bookstat:last-child { border-bottom: 0; padding-bottom: 0; }
.bookstat-what { min-width: 0; display: flex; flex-direction: column; gap: .2rem; }
.bookstat-title { font-weight: 500; text-decoration: none; }
.bookstat-title:hover { text-decoration: underline; }
.bookstat-author { color: var(--muted); font-size: .8rem; }
.bookstat-state { display: flex; align-items: center; gap: .45rem; color: var(--muted); font-size: .75rem; }
.bookstat-state.finished { color: var(--primary); }
.bookstat-state .ui-icon { width: .9rem; height: .9rem; }
.bookstat-track { width: 4.5rem; height: 4px; border-radius: 2px; background: var(--surface-3); overflow: hidden; }
.bookstat-fill { display: block; height: 100%; border-radius: 2px; background: var(--primary); }
.bookstat-much { text-align: right; flex: none; display: flex; flex-direction: column; gap: .2rem; }
.bookstat-duration { font-variant-numeric: lining-nums tabular-nums; }
.bookstat-sittings { color: var(--muted); font-size: .75rem; }

/* How long is left, in a sentence rather than in a tile: it is an
   estimate, and a number in a box beside four measured ones would be
   read as another measurement. */
.work-eta {
	display: flex; align-items: center; gap: .6rem;
	margin: 0 0 var(--gap); padding: .9rem 1.1rem;
	border-radius: var(--radius); border: 1px solid var(--line);
	background: var(--surface-2); color: var(--muted); font-size: .88rem;
}
.work-eta-icon { display: inline-grid; place-items: center; color: var(--primary); }
.work-eta-icon .ui-icon { width: 1.1rem; height: 1.1rem; }

/* -------------------------------------------------------- browse toolbar */

.toolbar {
	display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
	margin: 0 0 var(--gap);
}
.toolbar .spacer { flex: 1; }
.toolbar form { margin: 0; display: flex; gap: .4rem; align-items: center; }
/* A two-way view switch: one bordered box, the chosen half filled. */
.viewtoggle { display: inline-flex; border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
.viewtoggle button, .viewtoggle .btn {
	border: 0; border-radius: 0; background: var(--surface); color: var(--muted);
	padding: .3rem .65rem; font-size: .8rem; font-weight: 500;
}
.viewtoggle button + button { border-left: 1px solid var(--line); }
.viewtoggle button:hover { background: var(--surface-2); color: var(--ink); }
.viewtoggle .on, .viewtoggle .on:hover { background: var(--surface-3); color: var(--ink); }

/* One header row: the title and folder on the left, everything that
   changes what the shelf shows or holds on the right. */
.libraryhead {
	display: flex; align-items: center; justify-content: space-between;
	gap: .75rem 1.5rem; margin: 0 0 1.5rem; flex-wrap: wrap;
}
.libraryhead-row { display: flex; align-items: baseline; gap: .75rem; flex-wrap: wrap; }
.libraryhead h1 { margin: 0; }
.librarycontrols {
	flex: 1 1 32rem; display: flex; align-items: center; justify-content: flex-end;
	gap: .4rem; flex-wrap: wrap;
}
.librarycontrols form { margin: 0; display: flex; align-items: center; }
.librarycontrols .searchform { flex: 0 1 20rem; min-width: 12rem; margin-right: .35rem; }
.librarycontrols input[type="search"] { width: 100%; padding: .38rem .65rem; font-size: .875rem; }
.librarycontrols select { padding: .34rem .45rem; font-size: .84rem; }
.librarycontrols .upload-btn { padding: .34rem .65rem; font-size: .84rem; margin-left: .35rem; }
.librarycontrols .upload-btn .upload-icon { display: inline-flex; color: var(--muted); }
.librarycontrols .upload-btn .ui-icon { width: .95rem; height: .95rem; }
.librarycontrols .refreshbtn {
	background: none; border: 0; color: var(--muted); padding: .4rem;
}
.librarycontrols .refreshbtn:hover { color: var(--ink); background: var(--surface-2); }
.librarycontrols .refreshbtn:disabled { opacity: .6; cursor: not-allowed; }
.librarycontrols .refreshbtn .ui-icon { width: 1.05rem; height: 1.05rem; }
.librarycontrols .refreshlabel {
	position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.folderpick { margin: 0; }
.folderselect-wrap { display: inline-flex; align-items: center; gap: .3rem; color: var(--muted); }
.folder-glyph { display: inline-flex; align-items: center; }
.folder-glyph .ui-icon { width: .95rem; height: .95rem; }
.folderselect-wrap select {
	border: 0; background: transparent; font-size: .9rem; color: var(--ink-dim);
	padding: .2rem 1.2rem .2rem .1rem; cursor: pointer; max-width: min(18rem, 60vw);
}
.folderselect-wrap select:hover { color: var(--ink); }

.librarynote { margin: 0 0 1.25rem; font-size: .9rem; }
.librarynote p { margin: 0; }

.shelfbar { margin: 0 0 1.5rem; }
.iconbtn {
	display: inline-grid; place-items: center; width: 2rem; height: 2rem;
	color: var(--muted); text-decoration: none; border-radius: var(--radius-sm);
}
.iconbtn:hover { color: var(--ink); background: var(--surface-2); }

.shelftabs {
	display: flex; align-items: flex-end; justify-content: space-between; gap: .5rem 1.5rem;
	flex-wrap: wrap; border-bottom: 1px solid var(--line);
}
.tabs { display: flex; gap: 1.1rem; flex-wrap: wrap; }
.tabs a {
	padding: .45rem 0; margin-bottom: -1px; font-size: .875rem;
	color: var(--muted); text-decoration: none; border-bottom: 2px solid transparent;
}
.tabs a:hover { color: var(--ink); }
.tabs a.on { color: var(--ink); font-weight: 600; border-bottom-color: var(--primary); }
.shelfextras { display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap; padding: .35rem 0; font-size: .82rem; }
.browselinks { display: flex; gap: .7rem; align-items: baseline; }
.browselinks a { color: var(--ink-dim); text-decoration: none; }
.browselinks a:hover { color: var(--primary); text-decoration: underline; }
.groupseries { margin: 0; }
.groupseries label.check { margin: 0; white-space: nowrap; font-size: .82rem; color: var(--ink-dim); gap: .35rem; }
.libraryshelf { margin: 0 0 var(--gap); }

/* Upload modal & drag-and-drop */
.upload-dialog {
	width: min(34rem, calc(100% - 2rem)); max-width: 100%;
}
.upload-dialog-form { display: flex; flex-direction: column; margin: 0; }
.upload-dropzone {
	display: flex; flex-direction: column; align-items: center; justify-content: center;
	text-align: center; padding: 2.2rem 1.5rem; margin: .5rem 0 1rem;
	border: 2px dashed var(--line); border-radius: var(--radius);
	background: var(--surface-2); cursor: pointer;
	transition: border-color .2s ease, background-color .2s ease, transform .15s ease;
}
.upload-dropzone:hover, .upload-dropzone.dragover {
	border-color: var(--primary); background: var(--surface-3);
}
.upload-dropzone.dragover { transform: scale(1.01); }
.upload-dropzone-glyph {
	display: inline-flex; align-items: center; justify-content: center;
	width: 3rem; height: 3rem; border-radius: 50%;
	background: var(--surface-1); color: var(--primary); margin-bottom: .8rem;
}
.upload-dropzone-glyph .ui-icon { width: 1.5rem; height: 1.5rem; }
.upload-dropzone-prompt {
	font-size: .95rem; color: var(--ink); margin: 0 0 .3rem;
}
.upload-browse-link {
	color: var(--primary); text-decoration: underline; font-weight: 600;
}
.upload-dropzone-hint { font-size: .8rem; margin: 0; }

.upload-queue {
	margin: 0 0 1rem; border: 1px solid var(--line-soft);
	border-radius: var(--radius-sm); background: var(--surface-1);
	overflow: hidden;
}
.upload-queue-head {
	display: flex; align-items: center; justify-content: space-between;
	padding: .5rem .75rem; background: var(--surface-2);
	border-bottom: 1px solid var(--line-soft); font-size: .8rem; font-weight: 600;
}
.upload-queue-clear { font-size: .78rem; color: var(--muted); cursor: pointer; }
.upload-queue-clear:hover { color: var(--danger); text-decoration: underline; }
.upload-queue-list {
	list-style: none; margin: 0; padding: 0; max-height: 12rem; overflow-y: auto;
}
.upload-queue-item {
	display: flex; align-items: center; gap: .6rem;
	padding: .5rem .75rem; border-bottom: 1px solid var(--line-soft);
	font-size: .82rem;
}
.upload-queue-item:last-child { border-bottom: none; }
.upload-item-name {
	flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
	font-weight: 500; color: var(--ink);
}
.upload-item-size { font-size: .76rem; white-space: nowrap; }
.upload-item-badge {
	font-size: .72rem; font-weight: 600; padding: .15rem .45rem;
	border-radius: var(--radius-sm); background: var(--surface-3); color: var(--muted);
	white-space: nowrap;
}
.upload-queue-item.done .upload-item-badge {
	background: #10b98120; color: #10b981;
}
.upload-queue-item.error .upload-item-badge {
	background: #ef444420; color: #ef4444;
}
.upload-queue-item.uploading .upload-item-badge {
	background: var(--primary-dim); color: var(--on-primary);
}
.upload-item-remove {
	background: none; border: none; font-size: 1.15rem; line-height: 1;
	color: var(--muted); cursor: pointer; padding: 0 .2rem;
}
.upload-item-remove:hover { color: var(--danger); }
.upload-dialog-actions {
	display: flex; align-items: center; gap: 1rem; margin-top: .5rem; flex-wrap: wrap;
}

/* ------------------------------------------------------------- the grid */

/* At most four books to a row, so a cover is big enough to read; a
   narrower window drops to three or two once a column would fall under
   --card-min. */
.grid {
	--grid-gap: 2rem;
	display: grid; gap: 2.25rem var(--grid-gap);
	grid-template-columns: repeat(auto-fill, minmax(max(var(--card-min), calc((100% - 3 * var(--grid-gap)) / 4)), 1fr));
	margin: 0 0 var(--gap);
}

.bookcard { display: flex; flex-direction: column; gap: .15rem; min-width: 0; position: relative; }
.bookcard .art {
	position: relative; display: block;
	aspect-ratio: var(--cover-ratio);
	background: var(--surface-2);
	border-radius: 2px;
	overflow: hidden;
	box-shadow: var(--book-shadow);
	margin-bottom: .45rem;
}
.bookcard .art::before {
	content: "";
	position: absolute; inset: 0 auto 0 0;
	width: var(--book-spine-width, 14px);
	background: var(--book-spine);
	pointer-events: none;
	z-index: 2;
}
.bookcard .art::after {
	content: "";
	position: absolute; inset: 0 0 0 auto;
	width: 4px;
	background: var(--book-edge);
	pointer-events: none;
	z-index: 2;
}
.bookcard .art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bookcard .art:hover img { filter: brightness(.94); }

/* Hover actions sit over the art and are revealed on hover or focus, so
   a keyboard reaches them exactly where a pointer does. */
.bookcard .actions {
	position: absolute; inset: auto 0 0 0;
	display: flex; gap: .4rem; padding: .5rem;
	background: linear-gradient(to top, rgb(0 0 0 / .72), transparent);
	opacity: 0; transition: opacity .15s ease;
	z-index: 3;
}
.bookcard:hover .actions, .bookcard .actions:focus-within { opacity: 1; }
/* A phone has no hover to reveal them with; there they are simply on. */
@media (hover: none) {
	.bookcard .actions { opacity: 1; }
	.bookcard .sublinks a, .bookcard .sublinks button.linkish {
		padding: .25rem .15rem;
		min-height: 2rem;
		display: inline-flex;
		align-items: center;
	}
}
.bookcard .actions .btn { padding: .25rem .6rem; font-size: .8rem; }

.bookcard .badge {
	position: absolute; top: .4rem; right: .4rem;
	background: var(--primary); color: var(--on-primary);
	font-size: .68rem; font-weight: 700; letter-spacing: .04em;
	padding: .1rem .45rem; border-radius: 1rem;
}
.bookcard .badge.done { background: var(--ok); }

.bookcard .title {
	font-size: .875rem; font-weight: 600; color: var(--ink);
	text-decoration: none; line-height: 1.3;
	display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
	overflow: hidden;
}
.bookcard .title:hover { color: var(--primary); }
.bookcard .by {
	font-size: .8rem; color: var(--muted);
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bookcard .card-activity {
	font-size: .74rem; color: var(--muted);
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Title and the ⋯ menu share the first caption line. */
.bookcard .caption { display: flex; align-items: flex-start; gap: .25rem; }
.bookcard .caption .title { flex: 1; min-width: 0; }
.bookmenu { flex: none; }
.bookmenu summary {
	list-style: none; cursor: pointer; color: var(--muted);
	width: 1.6rem; height: 1.4rem; margin: -.1rem -.3rem 0 0;
	display: grid; place-items: center; border-radius: var(--radius-sm);
	font-size: 1rem; line-height: 1;
}
.bookmenu summary::-webkit-details-marker { display: none; }
.bookmenu summary:hover, .bookmenu[open] summary { color: var(--ink); background: var(--surface-2); }
.bookmenu .menu {
	position: absolute; right: 0; z-index: 25; margin-top: .2rem;
	min-width: 10rem; padding: .3rem 0;
	background: var(--surface); border: 1px solid var(--line);
	border-radius: var(--radius-sm); box-shadow: var(--shadow);
	display: flex; flex-direction: column;
}
.bookmenu .menu a, .bookmenu .menu button.linkish {
	display: block; width: 100%; text-align: left;
	padding: .4rem .8rem; font-size: .84rem; line-height: 1.35;
	color: var(--ink); text-decoration: none;
}
.bookmenu .menu a:hover, .bookmenu .menu button.linkish:hover,
.bookmenu .menu a:focus-visible, .bookmenu .menu button.linkish:focus-visible {
	background: var(--surface-2); color: var(--ink); text-decoration: none;
}
.bookmenu .menu button.linkish.danger { color: var(--danger); }
.bookmenu .menu form { margin: 0; display: block; }
.bookmenu .menu .reading-status { margin: 0; }
@media (hover: none) {
	.bookmenu summary { width: 2.25rem; height: 2rem; }
	.bookmenu .menu a, .bookmenu .menu button.linkish { padding: .6rem .9rem; }
}

/* The sublinks at the foot of a card. The cover carries on reading and
   the title names the book; the ways to the numbers behind it come
   after both, small and quiet. */
.bookcard .sublinks {
	display: flex; flex-wrap: wrap; gap: .1rem .5rem;
	font-size: .74rem; margin-top: .15rem;
}
.bookcard .sublinks a { color: var(--muted); text-decoration: none; }
.bookcard .sublinks a:hover, .bookcard .sublinks a:focus-visible {
	color: var(--primary); text-decoration: underline;
}
.bookcard .sublinks .reading-status { display: inline; margin: 0; }

/* A delete that sits in a line of links has to look like one of them:
   the control is a form, because it writes, and a button that looked
   like a button would shout across a wall of quiet covers. */
.inline-delete { display: inline; }
button.linkish {
	background: none; border: none; padding: 0;
	font: inherit; color: var(--muted); cursor: pointer;
}
button.linkish.danger { border-color: transparent; }
button.linkish:hover, button.linkish:focus-visible {
	color: var(--danger); background: none; text-decoration: underline;
}
.rowlinks { display: flex; gap: .5rem; align-items: center; margin: .6rem 0 1rem; }
.cta .reading-status { margin: 0; }

/* Progress across the foot of the cover. The width is a class rather
   than a style attribute — see ADR-0011 — so the stylesheet keeps every
   declaration and a CSP for /ui stays possible. */
.bookcard .prog {
	position: absolute; inset: auto 0 0 0; height: 3px;
	background: rgb(0 0 0 / .45);
}
.bookcard .prog span { display: block; height: 100%; background: var(--primary); }
.p0 { width: 0 } .p5 { width: 5% } .p10 { width: 10% } .p15 { width: 15% }
.p20 { width: 20% } .p25 { width: 25% } .p30 { width: 30% } .p35 { width: 35% }
.p40 { width: 40% } .p45 { width: 45% } .p50 { width: 50% } .p55 { width: 55% }
.p60 { width: 60% } .p65 { width: 65% } .p70 { width: 70% } .p75 { width: 75% }
.p80 { width: 80% } .p85 { width: 85% } .p90 { width: 90% } .p95 { width: 95% }
.p100 { width: 100% }

/* A shelf is a grid that does not want to be tall: fewer, wider tiles. */
.shelf { grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); }

/* The endless-scroll sentinel: a plain link until htmx reaches it. */
.more { grid-column: 1 / -1; text-align: center; padding: .8rem 0; }

/* A work has no cover to show either, so its tile is made of its title
   and how far through it you are. */
.bookcard .art-text {
	display: flex; flex-direction: column; justify-content: center; gap: .3rem;
	padding: 1rem; text-align: center; text-decoration: none; color: var(--ink);
	background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--line-soft);
}
.bookcard .art-text .note { margin-top: .4rem; font-size: .72rem; color: var(--muted); }
.bookcard .art-text .name {
	font-weight: 600; font-size: .95rem; line-height: 1.3;
	display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;
	overflow: hidden;
}
.bookcard .art-text .count { color: var(--muted); font-size: .8rem; }
.bookcard .art-text .big { margin-top: .4rem; font-size: 1.15rem; font-weight: 600; color: var(--ink-dim); }

/* An author, a series or a tag has no cover of its own, so it gets a
   tile that says what it is and how much of it there is. */
.entitycard .art {
	display: flex; flex-direction: column; align-items: center; justify-content: center;
	gap: .3rem; text-align: center; padding: .8rem;
	background: linear-gradient(145deg, var(--surface-2), var(--surface-3));
	color: var(--ink); text-decoration: none;
}
.entitycard .art .name { font-weight: 600; font-size: .95rem; }
.entitycard .art .count { color: var(--muted); font-size: .8rem; }

/* --------------------------------------------------------- book detail */

.hero {
	display: flex; gap: clamp(1.5rem, 4vw, 3rem); flex-wrap: wrap;
	align-items: flex-start; margin: .75rem 0 2rem;
	padding: clamp(1.5rem, 3vw, 2.5rem); background: var(--surface);
	border: 1px solid var(--line); border-radius: var(--radius);
}
.hero .art {
	flex: 0 0 17rem; max-width: 42%;
	border-radius: var(--radius); overflow: hidden;
	border: 1px solid var(--line); box-shadow: var(--shadow);
}
.hero .art img { width: 100%; height: auto; display: block; }
.hero .facts { flex: 1; min-width: min(100%, 18rem); max-width: 48rem; }
.hero .facts h1 { font-size: clamp(2rem, 4vw, 3.2rem); line-height: 1.08; margin-bottom: .65rem; }
.hero .byline { color: var(--muted); margin: 0 0 .8rem; }
.hero .cta { display: flex; gap: .5rem; flex-wrap: wrap; margin: .9rem 0; }
.reading-state { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; margin: .9rem 0 .3rem; }
.reading-state .bar { margin: 0; width: 10rem; }
.tag.done { background: color-mix(in srgb, var(--ok) 18%, var(--surface-3)); border-color: var(--ok); color: var(--ink); }
.bookrelations { display: grid; gap: .7rem; margin: 1.2rem 0; }
.bookrelations .chips { margin: .2rem 0 0; }
.bookorganization { margin-top: 1.5rem; }
.book-organization-section { padding: .9rem 0; border-top: 1px solid var(--line-soft); }
.book-organization-section:first-of-type { border-top: 0; padding-top: .4rem; }
.book-organization-section:last-child { padding-bottom: 0; }
.book-organization-section > h2 { margin-bottom: .45rem; }
.description { max-width: 68ch; }

@media (max-width: 38em) {
	.libraryhead { margin-bottom: 1rem; }
	.librarycontrols { flex-basis: 100%; justify-content: flex-start; }
	.librarycontrols .searchform { flex: 1 1 100%; margin: 0 0 .2rem; }
	.librarycontrols .upload-btn { margin-left: auto; }
	.tabs {
		flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
		-webkit-overflow-scrolling: touch;
	}
	.tabs::-webkit-scrollbar { display: none; }
	.tabs a { white-space: nowrap; padding: .6rem 0; }
	.shelftabs { flex-direction: column; align-items: stretch; }
	.shelfextras { justify-content: space-between; }
	.hero { gap: 1.25rem; margin-top: 0; }
	.hero .art { flex-basis: 9rem; max-width: 38%; }
	.hero .facts { min-width: 0; }
	.hero .facts h1 { font-size: 2rem; }
	.reading-state .bar { width: 8rem; }
}

.kv { display: grid; grid-template-columns: max-content 1fr; gap: .3rem 1rem; font-size: .9rem; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; }

/* Covers in a table row, which the list view still uses. */
/* A token table has five columns and a form in one of them; below the
   drawer breakpoint it scrolls sideways rather than wrapping into an
   unreadable stack. */
@media (max-width: 46em) {
	.card > table { display: block; overflow-x: auto; }
	.settings-tabs, .settings-subnav {
		flex-wrap: nowrap;
		overflow-x: auto;
	}
}

.cover-thumb { width: 40px; height: 60px; object-fit: cover; border-radius: 2px; display: block; }

@media (max-width: 46em) {
	.hero .art { flex-basis: 100%; max-width: 12rem; }
	:root { --card-min: clamp(8.5rem, 42vw, 10.5rem); }
}

.empty-state {
	text-align: center; padding: 3rem 1.5rem; margin: 1rem 0;
	display: flex; flex-direction: column; align-items: center; gap: .75rem;
}
.empty-state h3 { margin: 0; font-size: 1.1rem; color: var(--ink); }
.empty-state p { margin: 0; max-width: 26rem; font-size: .9rem; color: var(--muted); }

.dev-cell {
	display: inline-flex; align-items: center; gap: .5rem;
}
.dev-icon {
	display: inline-flex; align-items: center; justify-content: center;
	width: 1.6rem; height: 1.6rem; border-radius: var(--radius-sm);
	background: var(--surface-2); color: var(--muted); flex-shrink: 0;
}
.dev-web { color: var(--primary); background: var(--surface-1); }
.dev-mobile { color: var(--ok); background: var(--surface-1); }
.dev-ereader { color: var(--primary); background: var(--surface-1); opacity: .85; }
.dev-desktop { color: var(--ink); background: var(--surface-1); opacity: .75; }

/* ----------------------------------------------------- audiobookshelf */

.abs-head {
	display: flex; align-items: flex-end; justify-content: space-between;
	gap: 1rem; margin: 0 0 1.25rem; flex-wrap: wrap;
}
.abs-head h1 { margin: 0 0 .25rem; }
.abs-head p { margin: 0; max-width: 46rem; }

.abs-shelfbar { margin: 0 0 1.75rem; }
.abs-shelfbar .tabs a {
	display: inline-flex; align-items: center; gap: .45rem;
}
.abs-tab-count {
	display: inline-flex; align-items: center; justify-content: center;
	min-width: 1.35rem; height: 1.35rem; padding: 0 .38rem;
	border-radius: 999px; background: var(--surface-2);
	color: var(--ink-dim); font-size: .74rem; font-weight: 600;
	font-variant-numeric: lining-nums tabular-nums;
}
.abs-shelfbar .tabs a.on .abs-tab-count {
	background: color-mix(in srgb, var(--primary) 16%, var(--surface-2));
	color: var(--primary);
}

.abs-empty { max-width: 34rem; margin: 2rem auto; }
.abs-empty-glyph {
	display: grid; place-items: center;
	width: 3rem; height: 3rem; border-radius: 50%;
	background: var(--surface-2); color: var(--primary);
}
.abs-empty h2 { margin: 0; font: 400 1.3rem/1.25 Georgia, "Times New Roman", serif; }

.abs-group { margin: 0 0 2.25rem; }
.abs-group-head { margin: 0 0 .9rem; }
.abs-group-title {
	display: flex; align-items: center; gap: .55rem; margin: 0 0 .2rem;
}
.abs-group-title h2 {
	margin: 0; font: 400 1.25rem/1.2 Georgia, "Times New Roman", serif;
	letter-spacing: -.01em;
}
.abs-group-count {
	display: inline-flex; align-items: center; justify-content: center;
	min-width: 1.4rem; height: 1.4rem; padding: 0 .4rem;
	border-radius: 999px; background: var(--surface-2);
	border: 1px solid var(--line-soft);
	color: var(--muted); font-size: .75rem; font-weight: 600;
	font-variant-numeric: lining-nums tabular-nums;
}
.abs-group-head > p { margin: 0; font-size: .86rem; max-width: 52rem; }

.abs-list { display: grid; gap: 1.15rem; }

.abs-card {
	margin: 0; padding: 1.35rem 1.5rem;
	display: flex; flex-direction: column; gap: 1.15rem;
	position: relative; overflow: hidden;
	transition: border-color .15s ease;
}
.abs-card-pending {
	border-left: 3px solid var(--warn);
	background: linear-gradient(180deg, color-mix(in srgb, var(--warn) 4%, var(--surface)), var(--surface) 7rem);
}
.abs-card-active {
	border-left: 3px solid var(--ok);
}
.abs-card-paused {
	border-left: 3px solid var(--danger);
}

.abs-card-top {
	display: flex; align-items: center; justify-content: space-between;
	gap: .65rem 1rem; flex-wrap: wrap;
}
.abs-badge {
	display: inline-flex; align-items: center; gap: .4rem;
	padding: .22rem .65rem; border-radius: 999px;
	font-size: .78rem; font-weight: 600;
	border: 1px solid var(--line);
	background: var(--surface-2); color: var(--ink-dim);
}
.abs-badge::before {
	content: ""; width: .45rem; height: .45rem; border-radius: 50%;
	background: currentColor; flex: none;
}
.abs-badge-active {
	background: color-mix(in srgb, var(--ok) 13%, var(--surface));
	border-color: color-mix(in srgb, var(--ok) 38%, var(--line));
	color: var(--ok);
}
.abs-badge-pending {
	background: color-mix(in srgb, var(--warn) 14%, var(--surface));
	border-color: color-mix(in srgb, var(--warn) 42%, var(--line));
	color: var(--warn);
}
.abs-badge-paused {
	background: color-mix(in srgb, var(--danger) 13%, var(--surface));
	border-color: color-mix(in srgb, var(--danger) 38%, var(--line));
	color: var(--danger);
}
.abs-badge-unmatched {
	background: var(--surface-2);
	border-color: var(--line);
	color: var(--muted);
}

.abs-coverage-pills {
	display: flex; align-items: center; gap: .45rem; flex-wrap: wrap;
}
.abs-pill {
	display: inline-flex; align-items: center;
	padding: .2rem .6rem; border-radius: var(--radius-sm);
	font-size: .78rem; background: var(--surface-2);
	border: 1px solid var(--line-soft); color: var(--ink-dim);
	font-variant-numeric: lining-nums tabular-nums;
}
.abs-pill-full {
	background: color-mix(in srgb, var(--ok) 10%, var(--surface-2));
	border-color: color-mix(in srgb, var(--ok) 30%, var(--line-soft));
	color: var(--ink);
}
.abs-pill-partial {
	background: color-mix(in srgb, var(--warn) 11%, var(--surface-2));
	border-color: color-mix(in srgb, var(--warn) 32%, var(--line-soft));
	color: var(--ink);
}

.abs-bridge {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
	gap: 1rem; align-items: stretch;
}
.abs-edition {
	display: flex; align-items: stretch; gap: 1rem;
	padding: 1rem 1.15rem; border-radius: var(--radius);
	background: var(--surface-2);
	border: 1px solid var(--line-soft);
	min-width: 0;
}
.abs-audio-disc {
	flex: 0 0 3.6rem; width: 3.6rem; height: 3.6rem;
	border-radius: .65rem;
	display: grid; place-items: center;
	background: linear-gradient(145deg, color-mix(in srgb, var(--primary) 18%, var(--surface)), var(--surface-3));
	border: 1px solid color-mix(in srgb, var(--primary) 28%, var(--line));
	color: var(--primary);
}
.abs-book-cover {
	flex: 0 0 3.6rem; width: 3.6rem;
	aspect-ratio: 2 / 3;
	border-radius: var(--radius-sm); overflow: hidden;
	border: 1px solid var(--line);
	background: var(--surface-3);
	box-shadow: var(--book-shadow);
	display: block; align-self: flex-start;
}
.abs-book-cover img {
	width: 100%; height: 100%; object-fit: cover; display: block;
}
.abs-book-cover-placeholder {
	display: grid; place-items: center;
	background: linear-gradient(145deg, var(--surface), var(--surface-3));
	color: var(--ink-dim);
}
.abs-edition-body {
	flex: 1; min-width: 0;
	display: flex; flex-direction: column; gap: .22rem;
}
.abs-edition-top {
	display: flex; align-items: center; justify-content: space-between;
	gap: .5rem; flex-wrap: wrap;
}
.abs-edition-kind {
	font-size: .74rem; font-weight: 500; color: var(--muted);
}
.abs-read-pill {
	display: inline-flex; align-items: center;
	padding: .14rem .55rem; border-radius: 999px;
	font-size: .74rem; font-weight: 500;
	background: var(--surface);
	border: 1px solid var(--line);
	color: var(--ink); text-decoration: none;
}
.abs-read-pill:hover {
	border-color: var(--primary); color: var(--primary);
}
.abs-title {
	margin: .05rem 0 0;
	font: 400 1.16rem/1.25 Georgia, "Times New Roman", serif;
	color: var(--ink);
	overflow-wrap: anywhere;
}
.abs-title a {
	color: var(--ink); text-decoration: none;
}
.abs-title a:hover {
	color: var(--primary); text-decoration: underline;
	text-decoration-thickness: 1px; text-underline-offset: .16em;
}
.abs-title-muted { color: var(--muted); }
.abs-subtitle {
	margin: 0; font-size: .82rem; color: var(--ink-dim);
}
.abs-byline {
	margin: 0; font-size: .83rem; color: var(--muted);
}
.abs-meta {
	margin: 0; font-size: .75rem; color: var(--muted);
	display: inline-flex; flex-wrap: wrap; gap: .25rem .6rem;
	font-variant-numeric: lining-nums tabular-nums;
}
.abs-progress-row {
	display: flex; align-items: center; gap: .65rem;
	margin-top: auto; padding-top: .55rem; flex-wrap: wrap;
}
.abs-prog {
	position: static; width: 8.5rem; height: .38rem;
	border-radius: 999px; overflow: hidden;
	background: var(--surface-3);
	border: 1px solid var(--line-soft);
}
.abs-prog span {
	display: block; height: 100%; border-radius: 999px;
	background: var(--primary);
}
.abs-prog-book span { background: var(--ok); }
.abs-progress-label {
	font-size: .78rem; color: var(--ink-dim);
	font-variant-numeric: lining-nums tabular-nums;
}
.abs-connector {
	display: flex; align-items: center; justify-content: center;
	color: var(--muted);
}
.abs-connector-badge {
	width: 2rem; height: 2rem; border-radius: 999px;
	display: grid; place-items: center;
	background: var(--surface);
	border: 1px solid var(--line);
	color: var(--ink-dim);
}
.abs-connector-unmatched .abs-connector-badge {
	border-style: dashed; color: var(--muted);
}

/* Narrative coverage ruler: 20 equal steps across the 0%..100% ebook axis,
   styled strictly with classes so CSP style-src 'self' remains intact. */
.abs-ruler {
	padding: .9rem 1.1rem;
	border-radius: var(--radius);
	background: var(--surface-2);
	border: 1px solid var(--line-soft);
	display: flex; flex-direction: column; gap: .45rem;
}
.abs-ruler-head {
	display: flex; align-items: baseline; justify-content: space-between;
	gap: .75rem; flex-wrap: wrap; font-size: .8rem;
}
.abs-ruler-title { color: var(--ink-dim); font-weight: 500; }
.abs-ruler-range {
	color: var(--ink); font-weight: 600;
	font-variant-numeric: lining-nums tabular-nums;
}
.abs-ruler-track {
	display: grid;
	grid-template-columns: repeat(20, minmax(0, 1fr));
	height: .65rem;
	border-radius: 999px;
	background:
		repeating-linear-gradient(
			to right,
			transparent 0,
			transparent calc(25% - 1px),
			var(--line) calc(25% - 1px),
			var(--line) 25%
		),
		var(--surface-3);
	border: 1px solid var(--line);
	overflow: hidden;
}
.abs-ruler-fill {
	display: block; height: 100%;
	border-radius: 999px;
	background: linear-gradient(90deg, var(--primary), color-mix(in srgb, var(--ok) 75%, var(--primary)));
}
.abs-card-pending .abs-ruler-fill {
	background: linear-gradient(90deg, var(--warn), var(--primary));
}
.abs-cstart-1 { grid-column-start: 1 } .abs-cstart-2 { grid-column-start: 2 }
.abs-cstart-3 { grid-column-start: 3 } .abs-cstart-4 { grid-column-start: 4 }
.abs-cstart-5 { grid-column-start: 5 } .abs-cstart-6 { grid-column-start: 6 }
.abs-cstart-7 { grid-column-start: 7 } .abs-cstart-8 { grid-column-start: 8 }
.abs-cstart-9 { grid-column-start: 9 } .abs-cstart-10 { grid-column-start: 10 }
.abs-cstart-11 { grid-column-start: 11 } .abs-cstart-12 { grid-column-start: 12 }
.abs-cstart-13 { grid-column-start: 13 } .abs-cstart-14 { grid-column-start: 14 }
.abs-cstart-15 { grid-column-start: 15 } .abs-cstart-16 { grid-column-start: 16 }
.abs-cstart-17 { grid-column-start: 17 } .abs-cstart-18 { grid-column-start: 18 }
.abs-cstart-19 { grid-column-start: 19 } .abs-cstart-20 { grid-column-start: 20 }
.abs-cend-2 { grid-column-end: 2 } .abs-cend-3 { grid-column-end: 3 }
.abs-cend-4 { grid-column-end: 4 } .abs-cend-5 { grid-column-end: 5 }
.abs-cend-6 { grid-column-end: 6 } .abs-cend-7 { grid-column-end: 7 }
.abs-cend-8 { grid-column-end: 8 } .abs-cend-9 { grid-column-end: 9 }
.abs-cend-10 { grid-column-end: 10 } .abs-cend-11 { grid-column-end: 11 }
.abs-cend-12 { grid-column-end: 12 } .abs-cend-13 { grid-column-end: 13 }
.abs-cend-14 { grid-column-end: 14 } .abs-cend-15 { grid-column-end: 15 }
.abs-cend-16 { grid-column-end: 16 } .abs-cend-17 { grid-column-end: 17 }
.abs-cend-18 { grid-column-end: 18 } .abs-cend-19 { grid-column-end: 19 }
.abs-cend-20 { grid-column-end: 20 } .abs-cend-21 { grid-column-end: 21 }

.abs-ruler-scale {
	display: flex; justify-content: space-between;
	font-size: .72rem; color: var(--muted);
	font-variant-numeric: lining-nums tabular-nums;
}

.abs-cuesheet {
	border: 1px solid var(--line-soft);
	border-radius: var(--radius-sm);
	background: var(--surface);
}
.abs-cuesheet > summary {
	cursor: pointer; list-style: none;
	display: flex; align-items: center; justify-content: space-between;
	gap: .75rem; padding: .65rem .95rem;
	font-size: .85rem; font-weight: 600; color: var(--ink);
}
.abs-cuesheet > summary::-webkit-details-marker { display: none; }
.abs-cuesheet > summary > span:first-child {
	display: inline-flex; align-items: center; gap: .45rem;
}
.abs-cuesheet > summary > span:first-child::before {
	content: "+"; color: var(--muted); font-weight: 400; width: .75rem;
}
.abs-cuesheet[open] > summary > span:first-child::before { content: "−"; }
.abs-cuesheet-meta {
	font-size: .76rem; font-weight: 400; color: var(--muted);
	font-variant-numeric: lining-nums tabular-nums;
}
.abs-cuesheet-body {
	border-top: 1px solid var(--line-soft);
	max-height: 20rem; overflow-y: auto;
}
.abs-cuetable {
	margin: 0; width: 100%; font-size: .83rem;
}
.abs-cuetable th, .abs-cuetable td {
	padding: .55rem .95rem;
}
.abs-Cue-label, .abs-cue-label { font-weight: 500; color: var(--ink); }
.abs-cue-time, .abs-cue-span {
	white-space: nowrap;
	font-variant-numeric: lining-nums tabular-nums;
	color: var(--ink-dim);
}
.abs-approx-tag {
	display: inline-block; padding: .12rem .45rem;
	border-radius: var(--radius-sm);
	background: color-mix(in srgb, var(--warn) 12%, var(--surface-2));
	color: var(--ink-dim); font-size: .75rem;
}

.abs-card-foot {
	display: flex; align-items: center; justify-content: space-between;
	gap: 1rem; flex-wrap: wrap;
	padding-top: .85rem; border-top: 1px solid var(--line-soft);
}
.abs-card-foot form { margin: 0; }
.abs-decision {
	display: flex; align-items: center; justify-content: space-between;
	gap: .85rem 1.25rem; flex-wrap: wrap;
	flex: 1 1 24rem;
	padding: .85rem 1rem;
	border-radius: var(--radius-sm);
	background: color-mix(in srgb, var(--warn) 9%, var(--surface-2));
	border: 1px solid color-mix(in srgb, var(--warn) 32%, var(--line));
}
.abs-decision-copy {
	margin: 0; font-size: .86rem; color: var(--ink);
	max-width: 40rem;
}
.abs-decision-text {
	display: flex; flex-direction: column; gap: .25rem;
	max-width: 40rem;
}
.abs-decision-text p { margin: 0; font-size: .86rem; }
.abs-unalignable {
	margin: 0; flex: 1 1 20rem;
	padding: .75rem 1rem; border-radius: var(--radius-sm);
	background: color-mix(in srgb, var(--danger) 10%, var(--surface-2));
	border: 1px solid color-mix(in srgb, var(--danger) 30%, var(--line));
	font-size: .86rem; color: var(--ink);
}
.abs-unlink-form { margin-left: auto; }
.abs-unlink-btn {
	font-size: .82rem; color: var(--muted);
}
.abs-unlink-btn:hover { color: var(--danger); }

.abs-matcher {
	display: flex; flex-direction: column; justify-content: center;
	gap: .85rem; min-width: 0;
}
.abs-matcher-slot {
	padding: 1rem 1.15rem;
	border-radius: var(--radius);
	border: 1px dashed var(--line);
	background: color-mix(in srgb, var(--surface-2) 55%, var(--surface));
}
.card form.abs-search-form {
	display: flex; align-items: flex-end; gap: .6rem; flex-wrap: wrap;
	margin: 0; width: 100%;
}
.card form.abs-search-form > label.abs-search-label {
	flex: 1 1 13rem; margin: 0; display: flex; flex-direction: column; gap: .35rem;
	font-size: .78rem; font-weight: 500; color: var(--ink-dim);
}
.abs-choices {
	display: flex; flex-direction: column; gap: .65rem;
}
.abs-choices > p { margin: 0; font-size: .84rem; }
.abs-choice-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
	gap: .65rem;
}
.abs-choice-card {
	display: flex; align-items: center; gap: .75rem;
	padding: .6rem .75rem;
	border-radius: var(--radius-sm);
	background: var(--surface);
	border: 1px solid var(--line-soft);
}
.abs-choice-cover {
	flex: 0 0 2.5rem; width: 2.5rem; aspect-ratio: 2 / 3;
	border-radius: 2px; overflow: hidden;
	border: 1px solid var(--line); background: var(--surface-3);
	display: block;
}
.abs-choice-cover img {
	width: 100%; height: 100%; object-fit: cover; display: block;
}
.abs-choice-info {
	flex: 1; min-width: 0;
	display: flex; flex-direction: column; gap: .12rem;
}
.abs-choice-title {
	font: 400 .96rem/1.25 Georgia, "Times New Roman", serif;
	color: var(--ink); text-decoration: none;
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.abs-choice-title:hover { color: var(--primary); text-decoration: underline; }
.abs-choice-sub, .abs-choice-author {
	font-size: .76rem; color: var(--muted);
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.abs-choice-card form { margin: 0; flex: none; }
.abs-btn-sm {
	padding: .35rem .7rem; font-size: .78rem;
}

@media (max-width: 52em) {
	.abs-bridge {
		grid-template-columns: 1fr;
		gap: .55rem;
	}
	.abs-connector {
		padding: .05rem 0;
	}
	.abs-connector-badge svg {
		transform: rotate(90deg);
	}
}
@media (max-width: 38em) {
	.abs-shelfbar { gap: .15rem; }
	.abs-tab { padding: .48rem .52rem; font-size: .8rem; gap: .3rem; }
	.abs-tab-count { min-width: 1.15rem; height: 1.15rem; padding: 0 .3rem; font-size: .7rem; }
	.abs-card { padding: 1.05rem 1.1rem; }
	.abs-edition, .abs-matcher-slot { padding: .85rem .9rem; }
	.abs-cuetable th:nth-child(3),
	.abs-cuetable td:nth-child(3) { display: none; }
	.abs-choice-grid { grid-template-columns: 1fr; }
}

/* The reader (ADR-0007): a book wants the window, so this page drops
   the usual chrome and gives the sandboxed frame everything left.

   The progress line and status float over the stage. By default the
   visible bar reserves an inset. When auto-hide is enabled the bars
   float over the book, keeping the viewport height steady as they
   appear and disappear so the engine does not repaginate while somebody
   is reading. */
.reader-body{margin:0;height:100vh;height:100dvh;box-sizing:border-box;padding-top:3px;
  display:flex;flex-direction:column;overflow:hidden}
.reader-body[data-reader-margin="none"]{--reader-margin:8px}
.reader-body[data-reader-margin="narrow"]{--reader-margin:16px}
.reader-body[data-reader-margin="normal"]{--reader-margin:48px}
.reader-body[data-reader-margin="wide"]{--reader-margin:72px}
.reader-bar{position:fixed;top:3px;left:0;right:0;z-index:50;
  display:grid;grid-template-columns:minmax(max-content,1fr) minmax(0,1fr) minmax(max-content,1fr);
  align-items:center;gap:.6rem;
  padding:calc(.35rem + env(safe-area-inset-top, 0px)) max(.75rem, env(safe-area-inset-right, 0px)) .35rem max(.75rem, env(safe-area-inset-left, 0px));
  border-bottom:1px solid var(--line);
  background:color-mix(in srgb,var(--surface) 88%,transparent);
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  box-shadow:0 4px 18px rgba(0,0,0,.28);
  transition:opacity .18s ease,transform .18s ease}
.reader-navigation{display:flex;align-items:center;gap:.6rem;min-width:0}
.reader-back{display:inline-flex;align-items:center;gap:.3rem;
  color:var(--muted);text-decoration:none;white-space:nowrap;
  font-size:.9rem;padding:.25rem .45rem;border-radius:8px}
.reader-back:hover{color:var(--ink);background:var(--surface-3)}
.reader-back svg{flex:none}
.reader-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  text-align:center;font-size:.95rem;letter-spacing:.005em}
.reader-title strong{font-weight:600}
.reader-actions{position:relative;display:flex;align-items:center;justify-self:end;gap:.35rem;min-width:0}
/* One shape for every control on the bar: TOC, Aa, full screen. */
.reader-icon-btn{display:inline-flex;align-items:center;justify-content:center;
  box-sizing:border-box;width:2rem;height:2rem;padding:0;
  border:1px solid var(--line);border-radius:8px;background:transparent;
  color:inherit;cursor:pointer;font:inherit;font-size:.85rem;font-weight:600;line-height:1}
.reader-icon-btn:hover{background:var(--surface-3)}
.reader-icon-btn:focus-visible{outline:2px solid var(--primary);outline-offset:1px}
.reader-icon-btn svg{display:block}
/* An icon button is a flex box, and a class beats the browser's own
   rule for the hidden attribute: without this the prompt button would
   be permanently on the bar. */
.reader-icon-btn[hidden]{display:none}
/* The prompt's fallback dialog is a plain box of text to copy by hand.
   The text box is capped against the *visible* height (dvh, so a
   phone's retracting toolbar counts) because a centred modal taller
   than its viewport grows off both edges at once, and the edge that
   matters is the one carrying the title. */
#reader-prompt-fallback textarea{
  box-sizing:border-box;width:100%;max-height:40vh;max-height:40dvh;font:inherit;font-size:.9rem;
  margin:.6rem 0;padding:.5rem;border:1px solid var(--line);border-radius:8px;
  background:var(--surface-2);color:inherit}
.reader-prompt-fallback-actions{margin-top:.2rem;display:flex;gap:.5rem;
  justify-content:flex-end;align-items:center}
.reader-prompt-fallback-actions form{margin:0}
@media (max-width:32em){
  .reader-bar{gap:.4rem;padding:calc(.4rem + env(safe-area-inset-top, 0px)) max(.55rem, env(safe-area-inset-right, 0px)) .4rem max(.55rem, env(safe-area-inset-left, 0px))}
  .reader-back-label{display:none}
  .reader-back{padding:.25rem}
}
/* The reading footer: a quiet line in the bottom margin the engine
   keeps under the text, the way the app draws it. Its height is that
   margin (--reader-margin, set from the Margins setting), so it never
   covers a line and never resizes the stage; it lives in the stage so
   its edges are the text's; and it is not a stage surface, so a click
   here changes the middle slot and turns nothing.
   It is the one thing left on screen once the bars have gone. */
.reader-footer{position:absolute;left:0;right:0;bottom:0;z-index:5;
  height:calc(var(--reader-margin,48px) + env(safe-area-inset-bottom, 0px));box-sizing:border-box;
  padding:0 max(var(--reader-margin,48px), env(safe-area-inset-right, 0px)) env(safe-area-inset-bottom, 0px) max(var(--reader-margin,48px), env(safe-area-inset-left, 0px));
  display:flex;align-items:center;justify-content:space-between;gap:.9rem;
  font-size:min(.78rem,calc(var(--reader-margin,48px) * .7));
  letter-spacing:.01em;line-height:1;
  font-variant-numeric:tabular-nums;color:var(--reader-footer-fg,var(--ink));
  opacity:.62;user-select:none;-webkit-user-select:none;
  transition:opacity .18s ease}
.reader-footer:hover{opacity:.9}
.reader-footer button{background:none;border:none;padding:0;font:inherit;
  letter-spacing:inherit;line-height:inherit;font-variant-numeric:inherit;
  color:inherit;cursor:pointer;display:inline-block;border-radius:3px}
.reader-footer button:focus-visible{outline:2px solid var(--primary);outline-offset:2px}
.reader-footer button:hover{text-decoration:underline}
.reader-footer button[disabled]{opacity:.5;cursor:default;text-decoration:none}
.reader-footer-edge{flex:none;white-space:nowrap}
.reader-footer-mid{flex:1;min-width:0;text-align:center;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.reader-footer-mid:empty{visibility:hidden}
/* The footer sits over the stage, so its ink follows the stage's
   theme rather than the app's. */
.reader-body[data-reader-theme="original"] .reader-footer{--reader-footer-fg:#1b1b1f}
.reader-body[data-reader-theme="light"] .reader-footer{--reader-footer-fg:#1b1b1f}
.reader-body[data-reader-theme="sepia"] .reader-footer{--reader-footer-fg:#5b4636}
.reader-body[data-reader-theme="dark"] .reader-footer{--reader-footer-fg:#cfcfd4}
.reader-body[data-reader-theme="tokyo-night"] .reader-footer{--reader-footer-fg:#c0caf5}
.reader-body[data-reader-theme="rose-pine"] .reader-footer{--reader-footer-fg:#e0def4}
.reader-body[data-reader-theme="black"] .reader-footer{--reader-footer-fg:#ababae}
.reader-progress{position:fixed;top:0;left:0;right:0;z-index:70;
  height:3px;background:var(--surface-3);overflow:hidden}
#reader-progress-bar{height:100%;width:0;background:var(--primary);transition:width .2s}
#reader-progress-bar[data-reader-progress="0"]{width:0%}
#reader-progress-bar[data-reader-progress="1"]{width:1%}
#reader-progress-bar[data-reader-progress="2"]{width:2%}
#reader-progress-bar[data-reader-progress="3"]{width:3%}
#reader-progress-bar[data-reader-progress="4"]{width:4%}
#reader-progress-bar[data-reader-progress="5"]{width:5%}
#reader-progress-bar[data-reader-progress="6"]{width:6%}
#reader-progress-bar[data-reader-progress="7"]{width:7%}
#reader-progress-bar[data-reader-progress="8"]{width:8%}
#reader-progress-bar[data-reader-progress="9"]{width:9%}
#reader-progress-bar[data-reader-progress="10"]{width:10%}
#reader-progress-bar[data-reader-progress="11"]{width:11%}
#reader-progress-bar[data-reader-progress="12"]{width:12%}
#reader-progress-bar[data-reader-progress="13"]{width:13%}
#reader-progress-bar[data-reader-progress="14"]{width:14%}
#reader-progress-bar[data-reader-progress="15"]{width:15%}
#reader-progress-bar[data-reader-progress="16"]{width:16%}
#reader-progress-bar[data-reader-progress="17"]{width:17%}
#reader-progress-bar[data-reader-progress="18"]{width:18%}
#reader-progress-bar[data-reader-progress="19"]{width:19%}
#reader-progress-bar[data-reader-progress="20"]{width:20%}
#reader-progress-bar[data-reader-progress="21"]{width:21%}
#reader-progress-bar[data-reader-progress="22"]{width:22%}
#reader-progress-bar[data-reader-progress="23"]{width:23%}
#reader-progress-bar[data-reader-progress="24"]{width:24%}
#reader-progress-bar[data-reader-progress="25"]{width:25%}
#reader-progress-bar[data-reader-progress="26"]{width:26%}
#reader-progress-bar[data-reader-progress="27"]{width:27%}
#reader-progress-bar[data-reader-progress="28"]{width:28%}
#reader-progress-bar[data-reader-progress="29"]{width:29%}
#reader-progress-bar[data-reader-progress="30"]{width:30%}
#reader-progress-bar[data-reader-progress="31"]{width:31%}
#reader-progress-bar[data-reader-progress="32"]{width:32%}
#reader-progress-bar[data-reader-progress="33"]{width:33%}
#reader-progress-bar[data-reader-progress="34"]{width:34%}
#reader-progress-bar[data-reader-progress="35"]{width:35%}
#reader-progress-bar[data-reader-progress="36"]{width:36%}
#reader-progress-bar[data-reader-progress="37"]{width:37%}
#reader-progress-bar[data-reader-progress="38"]{width:38%}
#reader-progress-bar[data-reader-progress="39"]{width:39%}
#reader-progress-bar[data-reader-progress="40"]{width:40%}
#reader-progress-bar[data-reader-progress="41"]{width:41%}
#reader-progress-bar[data-reader-progress="42"]{width:42%}
#reader-progress-bar[data-reader-progress="43"]{width:43%}
#reader-progress-bar[data-reader-progress="44"]{width:44%}
#reader-progress-bar[data-reader-progress="45"]{width:45%}
#reader-progress-bar[data-reader-progress="46"]{width:46%}
#reader-progress-bar[data-reader-progress="47"]{width:47%}
#reader-progress-bar[data-reader-progress="48"]{width:48%}
#reader-progress-bar[data-reader-progress="49"]{width:49%}
#reader-progress-bar[data-reader-progress="50"]{width:50%}
#reader-progress-bar[data-reader-progress="51"]{width:51%}
#reader-progress-bar[data-reader-progress="52"]{width:52%}
#reader-progress-bar[data-reader-progress="53"]{width:53%}
#reader-progress-bar[data-reader-progress="54"]{width:54%}
#reader-progress-bar[data-reader-progress="55"]{width:55%}
#reader-progress-bar[data-reader-progress="56"]{width:56%}
#reader-progress-bar[data-reader-progress="57"]{width:57%}
#reader-progress-bar[data-reader-progress="58"]{width:58%}
#reader-progress-bar[data-reader-progress="59"]{width:59%}
#reader-progress-bar[data-reader-progress="60"]{width:60%}
#reader-progress-bar[data-reader-progress="61"]{width:61%}
#reader-progress-bar[data-reader-progress="62"]{width:62%}
#reader-progress-bar[data-reader-progress="63"]{width:63%}
#reader-progress-bar[data-reader-progress="64"]{width:64%}
#reader-progress-bar[data-reader-progress="65"]{width:65%}
#reader-progress-bar[data-reader-progress="66"]{width:66%}
#reader-progress-bar[data-reader-progress="67"]{width:67%}
#reader-progress-bar[data-reader-progress="68"]{width:68%}
#reader-progress-bar[data-reader-progress="69"]{width:69%}
#reader-progress-bar[data-reader-progress="70"]{width:70%}
#reader-progress-bar[data-reader-progress="71"]{width:71%}
#reader-progress-bar[data-reader-progress="72"]{width:72%}
#reader-progress-bar[data-reader-progress="73"]{width:73%}
#reader-progress-bar[data-reader-progress="74"]{width:74%}
#reader-progress-bar[data-reader-progress="75"]{width:75%}
#reader-progress-bar[data-reader-progress="76"]{width:76%}
#reader-progress-bar[data-reader-progress="77"]{width:77%}
#reader-progress-bar[data-reader-progress="78"]{width:78%}
#reader-progress-bar[data-reader-progress="79"]{width:79%}
#reader-progress-bar[data-reader-progress="80"]{width:80%}
#reader-progress-bar[data-reader-progress="81"]{width:81%}
#reader-progress-bar[data-reader-progress="82"]{width:82%}
#reader-progress-bar[data-reader-progress="83"]{width:83%}
#reader-progress-bar[data-reader-progress="84"]{width:84%}
#reader-progress-bar[data-reader-progress="85"]{width:85%}
#reader-progress-bar[data-reader-progress="86"]{width:86%}
#reader-progress-bar[data-reader-progress="87"]{width:87%}
#reader-progress-bar[data-reader-progress="88"]{width:88%}
#reader-progress-bar[data-reader-progress="89"]{width:89%}
#reader-progress-bar[data-reader-progress="90"]{width:90%}
#reader-progress-bar[data-reader-progress="91"]{width:91%}
#reader-progress-bar[data-reader-progress="92"]{width:92%}
#reader-progress-bar[data-reader-progress="93"]{width:93%}
#reader-progress-bar[data-reader-progress="94"]{width:94%}
#reader-progress-bar[data-reader-progress="95"]{width:95%}
#reader-progress-bar[data-reader-progress="96"]{width:96%}
#reader-progress-bar[data-reader-progress="97"]{width:97%}
#reader-progress-bar[data-reader-progress="98"]{width:98%}
#reader-progress-bar[data-reader-progress="99"]{width:99%}
#reader-progress-bar[data-reader-progress="100"]{width:100%}
.reader-progress.loading::after{
  content:"";position:absolute;top:0;left:0;bottom:0;width:45%;
  background:linear-gradient(90deg,transparent 0%,color-mix(in srgb,var(--primary) 25%,transparent) 20%,var(--primary) 50%,color-mix(in srgb,var(--primary) 25%,transparent) 80%,transparent 100%);
  animation:reader-progress-sweep 1.25s cubic-bezier(.4,0,.2,1) infinite;
  box-shadow:0 0 12px var(--primary)}
@keyframes reader-progress-sweep{
  0%{transform:translateX(-100%)}
  100%{transform:translateX(320%)}
}
.reader-chapter-loader{
  position:fixed;z-index:60;top:calc(var(--reader-bar-height,3rem) + .8rem);left:50%;
  transform:translate(-50%,-4px) scale(.96);
  display:inline-flex;align-items:center;gap:.65rem;
  padding:.45rem 1.05rem;font-size:.82rem;font-weight:500;
  letter-spacing:.01em;white-space:nowrap;
  color:var(--ink);background:color-mix(in srgb,var(--surface) 90%,transparent);
  -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
  border:1px solid color-mix(in srgb,var(--line) 85%,transparent);border-radius:9999px;
  box-shadow:0 4px 20px rgba(0,0,0,.16),0 1px 3px rgba(0,0,0,.08);
  pointer-events:none;opacity:0;
  transition:top .2s cubic-bezier(.16,1,.3,1),opacity .2s cubic-bezier(.16,1,.3,1),transform .2s cubic-bezier(.16,1,.3,1)}
.reader-body[data-reader-chrome="hidden"] .reader-chapter-loader{top:calc(1.1rem + env(safe-area-inset-top, 0px))}
.reader-chapter-loader:not([hidden]){
  opacity:1;transform:translate(-50%,0) scale(1)}
.reader-chapter-loader[hidden]{display:none}
.reader-chapter-spinner{
  width:.85rem;height:.85rem;flex-shrink:0;
  border:2px solid color-mix(in srgb,var(--primary) 25%,transparent);
  border-top-color:var(--primary);
  border-radius:50%;animation:spin .75s linear infinite}
/* The status is only on screen while the book is loading or when it
   failed to; centred, it can say so whether or not the bar is up. */
.reader-status{position:fixed;z-index:45;top:50%;left:50%;transform:translate(-50%,-50%);
  margin:0;padding:.5rem .9rem;font-size:.9rem;opacity:.8;
  background:var(--surface);border:1px solid var(--line);border-radius:8px;
  box-shadow:0 8px 24px rgba(0,0,0,.35)}
.reader-status.problem{color:var(--danger);opacity:1}
/* A confirmation is not something to answer, and it is drawn over the
   middle of the page, where a tap toggles the bars. So it lets that tap
   through rather than swallowing it on its way out. */
.reader-status.flash{pointer-events:none}
.reader-annotation-actions{position:fixed;z-index:55;left:50%;bottom:4.5rem;
  transform:translateX(-50%);display:flex;gap:.35rem;padding:.45rem;
  border:1px solid var(--line);border-radius:10px;background:var(--surface);
  box-shadow:0 8px 24px rgba(0,0,0,.35)}
.reader-annotation-actions[hidden]{display:none}
.reader-annotation-actions button{border:1px solid var(--line);border-radius:7px;
  padding:.4rem .65rem;background:var(--surface-2);color:inherit;cursor:pointer}
.reader-annotation-actions button:hover,.reader-annotation-actions button:focus-visible{
  background:var(--surface-3);outline:2px solid var(--primary);outline-offset:1px}
.reader-annotation-actions label{display:flex;align-items:center;gap:.25rem;font-size:.8rem}
.reader-annotation-actions select{border:1px solid var(--line);border-radius:6px;
  padding:.35rem;background:var(--surface-2);color:inherit}
/* Chrome that has stepped aside is invisible and untouchable, never
   absent: a click at the edge falls through to the page-turn zones,
   and the buttons stay where a script (or a test) can reach them. */
.reader-body[data-reader-chrome="hidden"] .reader-bar{
  opacity:0;transform:translateY(-100%);pointer-events:none}
/* A dialog owns the screen while it is open. The bar is fixed, blurred
   and shadowed, so a box that reaches its band reads as hidden behind
   it even when the dialog paints on top; and a bar nobody can press
   through a modal backdrop has no reason to be drawn. */
.reader-body:has(dialog[open]) .reader-bar{opacity:0;pointer-events:none}
.reader-body[data-reader-chrome="hidden"] .reader-turn{opacity:0;pointer-events:none}
/* Tab is a way of reaching for the bar like any other. */
.reader-body[data-reader-chrome="hidden"]:has(.reader-bar:focus-within) .reader-bar{
  opacity:1;transform:none;pointer-events:auto}
@media (prefers-reduced-motion:reduce){
  .reader-bar,.reader-turn,.reader-footer,.reader-chapter-loader{transition:none}
  .reader-progress.loading::after{animation:none;width:100%;opacity:.4}
  .reader-chapter-spinner{animation:none}
}
.reader-help{background:var(--surface);color:inherit;border:1px solid var(--line);
  border-radius:10px;box-shadow:0 12px 32px rgba(0,0,0,.45);padding:1.1rem 1.3rem;
  min-width:20rem;font-size:.9rem}
.reader-help::backdrop{background:rgba(0,0,0,.55)}
.reader-help h2{margin:0 0 .7rem;font-size:1rem}
.reader-help table{border-collapse:collapse}
.reader-help td{padding:.25rem .4rem;vertical-align:top}
.reader-help td:first-child{white-space:nowrap;padding-right:1.1rem}
.reader-help kbd{background:var(--surface-3);border:1px solid var(--line);
  border-radius:4px;padding:.05rem .35rem;font-size:.8rem;font-family:inherit}
.reader-help form{margin:.8rem 0 0;text-align:right}
.reader-help-note{margin:.9rem 0 0;font-size:.85rem;opacity:.75;max-width:26rem}
.reader-goto{background:var(--surface);color:inherit;border:1px solid var(--line);
  border-radius:10px;box-shadow:0 12px 32px rgba(0,0,0,.45);padding:1.1rem 1.3rem;
  box-sizing:border-box;
  min-width:min(18rem,calc(100vw - 2rem));max-width:calc(100vw - 2rem);
  max-height:calc(100vh - 2rem);max-height:calc(100dvh - 2rem);overflow:auto;font-size:.9rem}
#reader-prompt-fallback{width:min(60ch,calc(100vw - 2rem))}
.reader-goto::backdrop{background:rgba(0,0,0,.55)}
.reader-goto h2{margin:0 0 .7rem;font-size:1rem}
.reader-goto-row{display:flex;align-items:center;gap:.5rem;margin:.6rem 0}
.reader-goto-label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.reader-goto-input{flex:1;min-width:0;padding:.4rem .6rem;border:1px solid var(--line);
  border-radius:6px;background:var(--surface-2);color:inherit;font-size:1rem;
  font-variant-numeric:tabular-nums}
.reader-goto-unit{font-size:.85rem;opacity:.75;font-variant-numeric:tabular-nums;white-space:nowrap}
.reader-goto-actions{margin-top:1rem;display:flex;gap:.5rem;justify-content:flex-end}
/* The book's two positions, side by side, asked for rather than
   offered (ADR-0040). Two columns where there is room and one where
   there is not: the point of the dialog is that the reader compares
   them, and a comparison that scrolls is not one. */
.reader-sync-dialog{background:var(--surface);color:inherit;border:1px solid var(--line);
  border-radius:10px;box-shadow:0 12px 32px rgba(0,0,0,.45);padding:1.1rem 1.3rem;
  box-sizing:border-box;width:min(32rem,calc(100vw - 2rem));
  max-height:calc(100vh - 2rem);max-height:calc(100dvh - 2rem);overflow:auto;font-size:.9rem}
.reader-sync-dialog::backdrop{background:rgba(0,0,0,.55)}
.reader-sync-dialog h2{margin:0 0 .7rem;font-size:1rem}
.reader-sync-summary{margin:0 0 .9rem}
.reader-sync-sides{display:flex;flex-wrap:wrap;gap:.9rem}
.reader-sync-side{flex:1 1 12rem;min-width:0;padding:.6rem .7rem;
  border:1px solid var(--line);border-radius:8px;background:var(--surface-2)}
.reader-sync-side[hidden]{display:none}
.reader-sync-side h3{margin:0 0 .3rem;font-size:.75rem;text-transform:uppercase;
  letter-spacing:.04em;opacity:.7}
.reader-sync-place{margin:0;font-variant-numeric:tabular-nums}
.reader-sync-actions{margin-top:1rem;display:flex;flex-wrap:wrap;gap:.5rem;justify-content:flex-end}
.reader-sync-actions button[hidden]{display:none}
/* The percentage behind a page number, and how long ago: quieter than
   the question, because it is what settles the question rather than
   what asks it. */
.reader-catchup-detail{font-size:.85rem;opacity:.75;font-variant-numeric:tabular-nums}
/* Another device's text, and possibly a great deal of it. Two lines is
   enough to recognise a passage, and a partner sending a chapter must
   not push the buttons off the screen. */
.reader-catchup-excerpt{margin:0 0 .75rem;font-size:.85rem;font-style:italic;opacity:.8;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;
  overflow:hidden}
.reader-catchup-detail[hidden],.reader-catchup-excerpt[hidden]{display:none}
.reader-toc{position:fixed;top:0;bottom:0;left:0;z-index:60;
  width:min(22rem,85vw);overflow-y:auto;padding:1rem 1.1rem;
  box-sizing:border-box;overscroll-behavior:contain;
  padding-top:max(1rem,env(safe-area-inset-top, 0px));
  padding-bottom:max(1rem,env(safe-area-inset-bottom, 0px));
  padding-left:max(1.1rem,env(safe-area-inset-left, 0px));
  background:var(--surface);border-right:1px solid var(--line);
  box-shadow:0 0 32px rgba(0,0,0,.45);font-size:.9rem}
.reader-toc h2{margin:0 0 .6rem;font-size:1rem}
.reader-toc ul{list-style:none;margin:0;padding:0}
.reader-toc ul ul{padding-left:1rem}
.reader-toc li{margin:0}
.reader-toc a,.reader-toc span{display:block;padding:.3rem .45rem;border-radius:6px;
  color:inherit;text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.reader-toc span{opacity:.65}
.reader-toc a:hover,.reader-toc a:focus-visible{background:var(--surface-3)}
.reader-toc a.current{background:var(--surface-3);color:var(--primary);font-weight:600}
.reader-annotations{margin-top:1rem;border-top:1px solid var(--line);padding-top:.75rem}
.reader-ann-kind{display:block;font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;opacity:.65}
.reader-ann-text{display:block;font-size:.85rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.reader-ann-actions{display:flex;gap:.35rem;margin-top:.35rem}
.reader-ann-actions button{border:1px solid var(--line);border-radius:5px;padding:.2rem .45rem;
  background:var(--surface-2);color:inherit;cursor:pointer;font-size:.75rem}
.annlist{list-style:none;margin:0;padding:0}
.annlist li{padding:.6rem 0;border-bottom:1px solid var(--line)}
.annlist li:last-child{border-bottom:none}
.annlist .ann-kind{font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;opacity:.75;
  padding:.1rem .45rem;border-radius:999px;border:1px solid var(--line)}
.annlist .ann-excerpt{margin:.35rem 0;padding-left:.75rem;border-left:3px solid var(--line);font-style:italic}
.annlist .ann-body{margin:.35rem 0;white-space:pre-wrap}
.ann-color-yellow{border-left:4px solid #ffd54f}
.ann-color-green{border-left:4px solid #81c784}
.ann-color-blue{border-left:4px solid #64b5f6}
.ann-color-pink{border-left:4px solid #f06292}
.ann-color-purple{border-left:4px solid #ba68c8}
.ann-color-orange{border-left:4px solid #ffb74d}
.reader-stage{flex:1;display:flex;min-height:0;position:relative;
  box-sizing:border-box;padding-top:var(--reader-bar-height,calc(3rem + env(safe-area-inset-top, 0px)))}
.reader-body[data-reader-autohide="true"] .reader-stage{padding-top:env(safe-area-inset-top, 0px)}
#reader-view{flex:1;min-width:0;height:100%;background:var(--surface);overflow:hidden;position:relative}
readium-view .readium-navigator-iframe{width:100%;height:100%;border:0;display:block}
.reader-turn{position:absolute;top:0;bottom:calc(var(--reader-margin,48px) + env(safe-area-inset-bottom, 0px));z-index:6;width:2.75rem;
  border:0;background:transparent;cursor:pointer;font-size:2rem;
  padding:0;opacity:.35;color:inherit;transition:opacity .18s ease}
#reader-prev{left:0}
#reader-next{right:0}
.reader-turn:hover{opacity:.9}

/* Reader appearance settings (ADR-0012): a details popover, no script
   needed to open it and none to style it. The stage colour tracks the
   chosen theme so page gaps match the book instead of flashing white. */
/* Anchor the popover to the actions' right edge; anchoring it to Aa
   pushes its left edge off screen in Split View. */
.reader-settings{position:static}
.reader-settings>summary{list-style:none;user-select:none;-webkit-user-select:none}
.reader-settings>summary::-webkit-details-marker{display:none}
.reader-settings[open]>summary{background:var(--surface-3)}
.reader-settings>form{position:absolute;right:0;top:calc(100% + .55rem);z-index:40;
  box-sizing:border-box;width:21rem;
  max-width:calc(100vw - 2rem - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
  max-height:min(75vh,34rem);
  max-height:min(34rem,calc(100dvh - var(--reader-bar-height,3rem) - 1.5rem - env(safe-area-inset-bottom, 0px)));
  overflow:auto;overscroll-behavior:contain;
  background:var(--surface);border:1px solid var(--line);border-radius:10px;
  box-shadow:0 12px 32px rgba(0,0,0,.45);padding:.9rem;
  display:grid;gap:.7rem;font-size:.85rem;white-space:normal}
.reader-settings fieldset{border:0;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:.35rem}
.reader-settings legend{width:100%;padding:0 0 .3rem;opacity:.65;
  font-size:.75rem;text-transform:uppercase;letter-spacing:.05em}
.reader-settings .chip{display:inline-flex;align-items:center;cursor:pointer;
  border:1px solid var(--line);border-radius:999px;padding:.2rem .65rem}
.reader-settings .chip input{position:absolute;opacity:0;pointer-events:none}
.reader-settings .chip:has(input:checked){border-color:var(--primary);
  background:var(--surface-3);color:var(--primary)}
.reader-settings .row{display:grid;grid-template-columns:auto 1fr auto;
  align-items:center;gap:.6rem}
.reader-settings .row-size{align-items:center}
.reader-settings .row select{justify-self:end}
.reader-settings .row2{display:flex;gap:.35rem}
.reader-size-control{display:grid;grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;gap:.45rem}
.reader-size-step{border:1px solid var(--line);border-radius:999px;
  width:1.9rem;height:1.9rem;padding:0;background:var(--surface-2);
  color:inherit;cursor:pointer;font:inherit;font-size:1rem;line-height:1}
.reader-size-step:hover,.reader-size-step:focus-visible{background:var(--surface-3);
  outline:2px solid var(--primary);outline-offset:1px}
.reader-size-step:disabled{opacity:.5;cursor:default;outline:none}
.reader-settings input[type="range"]{width:100%}
.reader-settings output{opacity:.75;min-width:3ch;text-align:right}
#reader-settings-reset{border:1px solid var(--line);border-radius:6px;
  background:transparent;color:inherit;cursor:pointer;padding:.35rem .6rem}
#reader-settings-reset:hover{background:var(--surface-3)}
/* Match capabilities, including iPads with a trackpad attached. Keep
   native selection and pinch zoom available in the publication. */
@media (any-pointer:coarse){
  .reader-icon-btn,.reader-size-step{width:44px;height:44px;flex-shrink:0}
  .reader-back{box-sizing:border-box;min-width:44px;min-height:44px;justify-content:center}
  .reader-settings .chip,.reader-toc a,.reader-toc button,
  .reader-goto button,.reader-sync-dialog button,#reader-settings-reset{
    box-sizing:border-box;min-height:44px}
  .reader-toc a{padding-top:.65rem;padding-bottom:.65rem}
  .reader-settings input[type="range"]{min-height:44px}
  /* Small form text makes Safari zoom the whole page on focus. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  select,textarea,.reader-settings select,#reader-prompt-fallback textarea{font-size:16px}
}
.reader-body[data-reader-theme="light"] #reader-view{background:#ffffff}
.reader-body[data-reader-theme="sepia"] #reader-view{background:#f6ecd9}
.reader-body[data-reader-theme="dark"] #reader-view{background:#202124}
.reader-body[data-reader-theme="tokyo-night"] #reader-view{background:#1a1b26}
.reader-body[data-reader-theme="rose-pine"] #reader-view{background:#191724}
.reader-body[data-reader-theme="black"] #reader-view{background:#000000}

/* htmx's request indicators. These are the rules htmx injects into the
   head at load; it is told not to (see the htmx-config meta in the
   layout) because a <style> element it wrote itself is exactly what a
   style-src the page can trust must refuse. Spelled here, they are
   served from this origin like everything else. */
.htmx-indicator { opacity: 0 }
.htmx-request .htmx-indicator { opacity: 1; transition: opacity 200ms ease-in }
.htmx-request.htmx-indicator { opacity: 1; transition: opacity 200ms ease-in }

/* The scan buttons. htmx puts htmx-request on the element that made the
   request — the form — for as long as the pass runs, which is what the
   icon and the spinner hang off. There is no hx-indicator: naming one
   would move the class onto that element instead, and the icon inside
   the button would never see it. */
.scanform { margin: 0; display: inline-flex; }
.scanbtn {
	display: inline-flex; align-items: center; gap: .35rem;
	cursor: pointer;
}
.scanbtn:disabled { opacity: .6; cursor: not-allowed; }
.scanicon {
	display: inline-block; font-size: 1.05em; line-height: 1;
	transition: transform 150ms ease;
}
.htmx-request .scanicon {
	animation: spin 800ms linear infinite;
}
@keyframes spin {
	from { transform: rotate(0deg); }
	to { transform: rotate(360deg); }
}
.scanspinner {
	display: none; width: .75rem; height: .75rem;
	border: 2px solid currentColor; border-right-color: transparent;
	border-radius: 50%;
	animation: spin 600ms linear infinite;
}
.htmx-request .scanspinner { display: inline-block; }

/* A plain form on its way to the server: the submitter is disabled by
   ui.js, this is what the wait looks like everywhere else on it. */
form.working { cursor: progress; }
form.working :is(input, select, textarea, button) { pointer-events: none; }

/* The one message a failed fragment request can show: fixed, brief,
   and above whatever asked. It is an element ui.js adds on the first
   failure rather than markup every page carries. */
.flash-error {
	position: fixed; left: 50%; bottom: 1.25rem; transform: translateX(-50%);
	max-width: min(30rem, calc(100vw - 2rem));
	margin: 0; padding: .7rem 1rem; border-radius: var(--radius-sm);
	background: var(--surface); color: var(--danger);
	border: 1px solid var(--danger); box-shadow: var(--shadow);
	font-size: .9rem; z-index: 60;
}

/* ------------------------------------------------------------- library */

/* Continue reading: one row above the shelf on a translucent tint of the
   theme's accent, so every theme colours it. The bar, the figure and
   the date say the reading state once. */
.resume {
	display: flex; gap: 1.4rem; align-items: center;
	margin: 0 0 1.75rem; padding: 1.1rem 1.25rem;
	border-radius: var(--radius);
	background: color-mix(in srgb, var(--primary) 8%, transparent);
}
.resume .art {
	flex: 0 0 10rem; display: block; overflow: hidden;
	border-radius: 2px; box-shadow: var(--book-shadow);
	aspect-ratio: var(--cover-ratio);
}
.resume .art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.resume .herotext { flex: 0 1 36rem; min-width: 0; display: flex; flex-direction: column; gap: .05rem; }
.resume .herolabel { margin: 0; font-size: .78rem; color: var(--muted); }
.resume h2 { margin: 0; font: 400 1.55rem/1.2 Georgia, "Iowan Old Style", "Times New Roman", serif; }
.resume .title { color: var(--ink); text-decoration: none; }
.resume .title:hover { color: var(--primary); }
.resume .by { margin: 0; font-size: .85rem; color: var(--ink-dim); }
.resume .herotrack {
	display: flex; align-items: center; gap: .6rem; margin: .4rem 0 0; flex-wrap: wrap;
	font-size: .78rem;
}
.resume .bar { width: 9rem; height: 3px; margin: 0; border-radius: 0; background: var(--surface-3); }
.resume .pct { color: var(--ink-dim); font-variant-numeric: tabular-nums; }
.resume .herotrack .muted { white-space: nowrap; }
.resume .heroactions {
	display: flex; align-items: center; gap: 1rem; margin: .65rem 0 0; flex-wrap: wrap;
	font-size: .84rem;
}
.resume .heroactions .btn { padding: .36rem .85rem; }
.resume .quiet { color: var(--muted); text-decoration: none; }
.resume .quiet:hover { color: var(--ink); text-decoration: underline; }

/* A small sentence-case label over a heading; never decoration. */
.eyebrow { font-size: .8rem; font-weight: 500; color: var(--muted); }

@media (max-width: 38em) {
	.resume { gap: 1rem; padding: .9rem; }
	.resume .art { flex-basis: 7rem; }
	.resume h2 { font-size: 1.25rem; }
}

/* A text tile's link fills the tile, so the whole of it opens the work. */
.bookcard .art-text .fill {
	display: flex; flex-direction: column; justify-content: center;
	gap: .3rem; height: 100%; text-decoration: none; color: inherit;
}

/* A series occupies one grid cell, just like Android's mixed shelf. The
   front cover leaves a narrow edge at the right and foot for two real
   volumes to peek through, so the pile reads before its label does. */
.seriescard .seriesart {
	position: relative; display: block; aspect-ratio: .62;
	color: inherit; text-decoration: none;
}
.seriescard .stackback, .seriescard .stackfront {
	position: absolute; display: block; overflow: hidden;
	border-radius: 2px; background: var(--surface-2); box-shadow: var(--book-shadow);
}
.seriescard .stackback.far { inset: .7rem 0 0 .7rem; }
.seriescard .stackback.near { inset: .35rem .35rem .35rem .35rem; }
.seriescard .stackfront { inset: 0 .7rem .7rem 0; }
.seriescard .stackback img, .seriescard .stackfront img {
	width: 100%; height: 100%; object-fit: cover; display: block;
}
.seriescard .stackback.far img { opacity: .45; }
.seriescard .stackback.near img { opacity: .65; }
.seriescard .stackfront.complete img { opacity: .55; }
.seriescard .seriesart:hover .stackfront img { filter: brightness(.94); }
.seriesrail {
	display: flex; gap: 2px; height: 3px; margin: .35rem .7rem .4rem 0;
}
.seriesrail span { flex: 1; min-width: 2px; background: var(--surface-3); }
.seriesrail span.started { background: var(--primary-dim); }
.seriesrail span.done { background: var(--primary); }


/* --------------------------------------------------------- series shelf */

/* The shelf is a numbered run, so it is an <ol> with its own markers
   suppressed: the position shown is the series' number, which is not
   always the row's ordinal — a novella at 1.5 is the second row and the
   list must not call it book two. */
.serieslist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.seriesvol {
	display: flex; gap: 1rem; align-items: flex-start;
	padding: .8rem 0; border-top: 1px solid var(--line);
}
.seriesvol:first-child { border-top: 0; }
.seriesvol .art {
	flex: 0 0 5rem; border-radius: var(--radius); overflow: hidden;
	border: 1px solid var(--line); display: block;
}
.seriesvol .art img { width: 100%; height: auto; display: block; }
.voltext { flex: 1; min-width: 10rem; display: flex; flex-direction: column; gap: .25rem; }
.voltext .title { font-weight: 600; }
.voltext .by { color: var(--muted); font-size: .9rem; }
.volnum {
	color: var(--muted); font-size: .8rem; font-variant-numeric: tabular-nums;
	letter-spacing: .04em;
}
.volactions { display: flex; gap: .5rem; align-items: center; }
.volactions .reading-status { margin: 0; }

@media (max-width: 46em) {
	.volactions { flex-basis: 100%; }
}

/* The assign dialog. Each row is a name and a position, and the position
   stays narrow so the name gets the width it needs.
   The row selector is doubled on purpose. A row is a <p> inside a card
   form, so `.card form p` matches it, and that rule stacks a label over
   its field with `flex-direction: column`. Being more specific than one
   class it won, and a flex-basis written to mean "5rem wide" was read as
   "5rem tall". Hence two classes here, and widths rather than a basis on
   the fields, so no surrounding direction can flip them again. */
.assignrows { display: flex; flex-direction: column; gap: .4rem; margin: .2rem 0 .8rem; }
.assignrows .assignrow {
	display: flex; flex-direction: row; align-items: center;
	gap: .5rem; margin: 0; max-width: 40rem;
}
.assignrows .assignrow > input[type="text"] { min-width: 0; }
.assignrows .assignrow > input[name="name"] { flex: 1 1 auto; width: auto; }
.assignrows .assignrow > input[name="position"] { flex: 0 0 auto; width: 5rem; }

/* The headings for those two columns, held to the same two widths. */
.assignhead {
	display: flex; gap: .5rem; max-width: 40rem; margin: .8rem 0 0;
	font-size: .8rem; color: var(--muted);
}
.assignhead > :first-child { flex: 1 1 auto; min-width: 0; }
.assignhead > :last-child { flex: 0 0 auto; width: 5rem; }

/* Saving and undoing are two forms, so left alone they stack into a pile
   of buttons. The undo button sits in the first form and names the
   second one, which is what the form attribute is for. */
.assignactions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin: .8rem 0 0; }

/* The series a book is in, stated on the book page before the editor is
   opened. */
.seriesnow { display: flex; flex-wrap: wrap; gap: .4rem; margin: 0 0 .4rem; }
.seriesnow .num { color: var(--muted); }

/* The names a merge or a split made lead to a shelf (ADR-0021). Each is
   a statement and the button that takes it back, so they sit on one
   line with the undo pushed to the end. */
.bindings { list-style: none; padding: 0; margin: .4rem 0 .8rem; }
.bindings li {
  display: flex; align-items: center; gap: .6rem;
  padding: .3rem 0; border-bottom: 1px solid var(--line);
}
.bindings li form { margin: 0 0 0 auto; }
.reader-catchup {
  position: fixed;
  z-index: 40;
  bottom: 4rem;
  left: 50%;
  transform: translateX(-50%);
  width: min(30rem, calc(100vw - 2rem));
  padding: 1rem;
  border: 1px solid currentColor;
  border-radius: 0.6rem;
  background: Canvas;
  color: CanvasText;
  box-shadow: 0 0.25rem 1rem #0003;
}
.reader-catchup[hidden] { display: none; }
.reader-catchup p { margin: 0 0 0.75rem; }
.reader-catchup > div { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.reader-catchup button { min-height: 2.75rem; }
.reader-catchup button:focus-visible { outline: 3px solid currentColor; outline-offset: 3px; }
/* A disagreement is not the same news as an invitation: it says two
   devices are in different places and needs a decision, so it is marked
   rather than left looking like an ordinary offer. */
.reader-catchup.conflict { border-width: 2px; }
/* Two questions can be open at once — a position on another device and a
   change that will not go. They stack rather than cover each other. */
.reader-catchup:not([hidden]) ~ .reader-stuck { bottom: 13rem; }

/* The pull indicator and the refresh button.

   Both are the same command: deliver what this browser read offline,
   then draw the shelf the server has now. The pill is fixed rather than
   in the flow so that showing it never moves the page under a finger
   that is mid-gesture, and it leaves `transform` alone — the gesture
   writes into that as the finger moves. */
.pullindicator {
	position: fixed; z-index: 25;
	top: calc(var(--topbar) + .75rem); left: 0; right: 0;
	margin-inline: auto; width: max-content; max-width: 80vw;
	display: flex; align-items: center; gap: .5rem;
	padding: .4rem .85rem;
	border: 1px solid var(--line); border-radius: 999px;
	background: var(--surface); color: var(--ink-dim);
	font-size: .78rem; box-shadow: var(--shadow);
}
.pullindicator[hidden] { display: none; }
.pullindicator::before {
	content: ""; flex: none; width: .8rem; height: .8rem;
	border: 2px solid currentColor; border-right-color: transparent;
	border-radius: 50%; opacity: .45;
}
.pullindicator.armed::before { opacity: 1; color: var(--primary); }
.pullindicator[data-state="refreshing"]::before {
	opacity: 1; animation: spin 600ms linear infinite;
}
.pullindicator[data-state="done"]::before { color: var(--ok); opacity: 1; }
.pullindicator[data-state="partial"]::before { color: var(--warn); opacity: 1; }
.pullindicator[data-state="failed"] { color: var(--danger); }

.refreshbtn {
	display: inline-flex; align-items: center; gap: .35rem;
	cursor: pointer;
}
.refreshbtn[hidden] { display: none; }
.refreshbtn .ui-icon { width: 1rem; height: 1rem; }

/* Integrated Web Reader Audiobook Player (ADR-0057) */
.reader-icon-btn.active {
	border-color: var(--primary);
	color: var(--primary);
	background: color-mix(in srgb, var(--primary) 12%, transparent);
}
.reader-stage { padding-bottom: var(--reader-audio-inset, 0px); }
.reader-body:has(.reader-audio-bar[data-audio-layout="dock"]:not([hidden])) .reader-footer {
	position: fixed;
}
.reader-audio-bar {
	--audio-safe-top: env(safe-area-inset-top, 0px);
	--audio-safe-right: env(safe-area-inset-right, 0px);
	--audio-safe-bottom: env(safe-area-inset-bottom, 0px);
	--audio-safe-left: env(safe-area-inset-left, 0px);
	position: fixed;
	left: 12px;
	top: var(--reader-bar-height, 4rem);
	z-index: 54;
	width: 420px;
	box-sizing: border-box;
	overflow: auto;
	overscroll-behavior: contain;
	padding: 8px;
	border: 1px solid var(--line);
	border-radius: 16px;
	background: color-mix(in srgb, var(--surface) 96%, transparent);
	-webkit-backdrop-filter: blur(12px);
	backdrop-filter: blur(12px);
	box-shadow: var(--shadow);
	font-size: 14px;
	line-height: 1.3;
	color: var(--ink);
}
.reader-audio-bar[hidden] { display: none; }
.reader-audio-bar[data-audio-size="expanded"] { width: 344px; }
.reader-audio-bar[data-audio-layout="dock"] { width: min(420px, calc(100vw - 24px)); }
.reader-audio-bar[data-audio-layout="dock"][data-audio-size="expanded"] { width: min(344px, calc(100vw - 24px)); }
.reader-audio-bar [hidden] { display: none !important; }
.reader-audio-summary {
	display: grid;
	grid-template-columns: 24px minmax(0, 1fr) 132px 32px 32px;
	align-items: center;
}
.reader-audio-header { display: contents; }
.reader-audio-summary .reader-audio-handle { grid-column: 1; grid-row: 1; width: 24px; }
.reader-audio-heading { grid-column: 2; grid-row: 1; }
.reader-audio-summary .reader-audio-size-btn { grid-column: 4; grid-row: 1; width: 32px; }
#reader-audio-close { grid-column: 5; grid-row: 1; width: 32px; }
.reader-audio-bar[data-audio-size="expanded"] .reader-audio-summary { grid-template-columns: 24px minmax(0, 1fr) 32px 32px; }
.reader-audio-bar[data-audio-size="expanded"] .reader-audio-size-btn { grid-column: 3; }
.reader-audio-bar[data-audio-size="expanded"] #reader-audio-close { grid-column: 4; }
.reader-audio-bar[data-audio-size="expanded"] .reader-audio-transport { grid-column: 1 / -1; grid-row: 2; margin-top: 8px; }
.reader-audio-bar[data-audio-layout="dock"] .reader-audio-summary { grid-template-columns: minmax(0, 1fr) 132px 44px; }
.reader-audio-bar[data-audio-layout="dock"] .reader-audio-heading { grid-column: 1; }
.reader-audio-bar[data-audio-layout="dock"] .reader-audio-transport { grid-column: 2; }
.reader-audio-bar[data-audio-layout="dock"] .reader-audio-size-btn { grid-column: 3; width: 44px; }
.reader-audio-bar[data-audio-layout="dock"][data-audio-size="mini"] #reader-audio-close { display: none; }
.reader-audio-bar[data-audio-layout="dock"][data-audio-size="expanded"] .reader-audio-summary { grid-template-columns: minmax(0, 1fr) 44px 44px; }
.reader-audio-bar[data-audio-layout="dock"][data-audio-size="expanded"] .reader-audio-size-btn { grid-column: 2; }
.reader-audio-bar[data-audio-layout="dock"][data-audio-size="expanded"] #reader-audio-close { grid-column: 3; width: 44px; }
.reader-audio-bar[data-audio-layout="dock"][data-audio-size="expanded"] .reader-audio-transport { grid-column: 1 / -1; }
.reader-audio-heading {
	flex: 1;
	min-width: 0;
	display: grid;
	gap: 2px;
	padding-inline: 4px;
}
.reader-audio-chapter {
	display: block;
	font-size: 13px;
	line-height: 18px;
	font-weight: 600;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.reader-audio-times {
	display: block;
	font-size: 12px;
	line-height: 16px;
	color: var(--muted);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}
.reader-audio-bar[data-audio-size="mini"] .reader-audio-total { display: none; }
.reader-audio-bar[data-audio-size="mini"] .reader-audio-collapse-icon { display: none; }
.reader-audio-bar[data-audio-size="expanded"] .reader-audio-expand-icon { display: none; }
.reader-audio-transport {
	grid-column: 3;
	grid-row: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 0;
	min-height: 44px;
}
.reader-audio-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	flex: none;
	padding: 0;
	border: 0;
	border-radius: 10px;
	background: transparent;
	color: var(--ink-dim);
	font: inherit;
	font-size: 13px;
	font-weight: 500;
	line-height: 1;
	cursor: pointer;
	white-space: nowrap;
}
.reader-audio-btn svg {
	display: block;
	flex: none;
	pointer-events: none;
}
.reader-audio-btn:hover {
	background: var(--surface-2);
	color: var(--ink);
}
.reader-audio-btn:active {
	background: var(--surface-3);
}
.reader-audio-handle {
	color: var(--muted);
	cursor: grab;
	touch-action: none;
}
.reader-audio-bar[data-audio-dragging] { user-select: none; cursor: grabbing; }
.reader-audio-bar[data-audio-dragging] .reader-audio-handle { cursor: grabbing; }
.reader-audio-drag-shield { position: fixed; inset: 0; z-index: 53; cursor: grabbing; }
.reader-audio-skip { position: relative; }
.reader-audio-skip svg { position: absolute; width: 28px; height: 28px; }
.reader-audio-skip span { font-size: 9px; font-weight: 600; transform: translateY(1px); }
.reader-audio-play-btn {
	width: 44px;
	height: 44px;
	border-radius: 50%;
	background: var(--primary);
	color: var(--on-primary);
	font-size: 14px;
	font-weight: 600;
}
.reader-audio-play-btn[data-playing="false"] .reader-audio-pause-icon,
.reader-audio-play-btn[data-playing="true"] .reader-audio-play-icon { display: none; }
.reader-audio-play-icon { transform: translateX(1px); }
.reader-audio-play-btn:hover {
	background: color-mix(in srgb, var(--primary) 90%, var(--ink));
	color: var(--on-primary);
}
#reader-audio-details {
	margin-top: 4px;
	display: grid;
	gap: 6px;
	min-width: 0;
}
.reader-audio-timeline {
	min-width: 0;
	display: flex;
	align-items: center;
}
.reader-audio-seek {
	appearance: none;
	width: 100%;
	min-width: 0;
	height: 28px;
	margin: 0;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
	accent-color: var(--primary);
	cursor: pointer;
}
.reader-audio-seek:hover {
	border-color: transparent;
}
.reader-audio-seek::-webkit-slider-runnable-track {
	height: 3px;
	border-radius: 999px;
	background: var(--line);
}
.reader-audio-seek::-webkit-slider-thumb {
	appearance: none;
	width: 14px;
	height: 14px;
	margin-top: -5.5px;
	border-radius: 50%;
	background: var(--primary);
	border: 2px solid var(--surface);
	box-sizing: border-box;
}
.reader-audio-seek::-moz-range-track {
	height: 3px;
	border-radius: 999px;
	background: var(--line);
}
.reader-audio-seek::-moz-range-thumb {
	width: 14px;
	height: 14px;
	border-radius: 50%;
	background: var(--primary);
	border: 2px solid var(--surface);
	box-sizing: border-box;
}
.reader-audio-settings-row {
	display: grid;
	grid-template-columns: 76px minmax(0, 1fr) auto;
	gap: 8px;
	align-items: center;
}
.reader-audio-settings-row .reader-audio-pill {
	padding: 0 8px;
	white-space: nowrap;
}
.reader-audio-rate-label {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
}
.reader-audio-rate {
	height: 44px;
	width: 100%;
	border: 0;
	border-radius: 999px;
	padding: 0 10px;
	background: var(--surface-2);
	color: var(--ink);
	font: inherit;
	font-size: 14px;
	font-weight: 500;
	cursor: pointer;
}
@media (any-pointer: coarse) {
	.reader-audio-rate { font-size: 16px; }
}
.reader-audio-pill {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	height: 44px;
	padding: 0 12px;
	border: 0;
	border-radius: 999px;
	background: var(--surface-2);
	color: var(--ink-dim);
	font: inherit;
	font-size: 13px;
	font-weight: 500;
	cursor: pointer;
	white-space: nowrap;
}
.reader-audio-pill:hover {
	background: var(--surface-3);
	color: var(--ink);
}
.reader-audio-pill.active {
	color: var(--primary);
	background: color-mix(in srgb, var(--primary) 12%, var(--surface-2));
	font-weight: 600;
}
.reader-audio-text-actions {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0 8px;
}
.reader-audio-text-btn {
	min-height: 44px;
	padding: 0 8px;
	border: 0;
	border-radius: 8px;
	background: transparent;
	color: var(--ink-dim);
	font: inherit;
	font-size: 12px;
	font-weight: 500;
	cursor: pointer;
}
.reader-audio-text-btn:hover {
	color: var(--primary);
	background: transparent;
	text-decoration: underline;
}
.reader-audio-stt-status {
	color: var(--muted);
	font-size: 12px;
	line-height: 18px;
	text-align: center;
}
.reader-audio-stt-status:empty { display: none; }
.reader-audio-position {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
	margin-top: 2px;
	border-top: 1px solid var(--line-soft);
	padding-top: 6px;
}
.reader-audio-position-label {
	font-size: 12px;
	color: var(--muted);
}
.reader-audio-docking { display: inline-flex; flex: none; }
.reader-audio-docking .reader-audio-btn { border-radius: 50%; }
.reader-audio-docking .reader-audio-btn[aria-pressed="true"] {
	color: var(--primary);
	background: color-mix(in srgb, var(--primary) 14%, var(--surface));
}
.reader-audio-bar[data-audio-layout="dock"] .reader-audio-position { display: none; }
.reader-audio-sr { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.reader-audio-bar button:focus-visible, .reader-audio-bar input:focus-visible,
.reader-audio-bar select:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
	.reader-audio-bar button { transition: none; }
}

/* Paper has no rail, no chrome, no buttons and no dark theme: a printed
   page is the content, in black on white, at the full width. */
@media print {
	.app { display: block; }
	.topbar, .rail, .skip, .librarycontrols, .shelfbar, .bookmenu,
	.cta, .bookcard .actions, .refreshbtn,
	.flash-error, #pull-indicator { display: none !important; }
	:root, [data-theme] {
		--bg: #fff; --surface: #fff; --surface-2: #fff; --surface-3: #eee;
		--ink: #000; --ink-dim: #222; --muted: #444;
		--line: #ccc; --line-soft: #ddd;
		--shadow: none; --shadow-sm: none;
	}
}

/* Audiobooks (ADR-0057, ADR-0058): a badge on covers that have one,
   and the player and upload card on the book page. */
.bookcard .audio-badge {
	position: absolute; top: .4rem; right: .4rem; z-index: 4;
	display: grid; place-items: center;
	width: 1.9rem; height: 1.9rem; border-radius: 999px;
	background: rgb(0 0 0 / .62); color: #fff;
	box-shadow: 0 1px 3px rgb(0 0 0 / .3);
}
.bookcard .audio-badge:hover, .bookcard .audio-badge:focus-visible { background: var(--primary); }
.bookcard .audio-badge .ui-icon { width: 1.05rem; height: 1.05rem; }
.audio-tag {
	display: inline-flex; align-items: center; gap: .25rem;
	margin-left: .4rem; padding: .05rem .4rem;
	border-radius: 999px; background: var(--surface-2);
	color: var(--ink-dim); font-size: .72rem; text-decoration: none;
	vertical-align: middle;
}
.audio-tag:hover { color: var(--primary); }
.audio-tag .ui-icon { width: .85rem; height: .85rem; }
.listen-btn { display: inline-flex; align-items: center; gap: .4rem; }
.listen-btn .ui-icon { width: 1rem; height: 1rem; }
.book-listen h2 { display: flex; align-items: center; gap: .5rem; }
.book-listen h2 .ui-icon { color: var(--primary); }
.book-listen:focus { outline: none; }
.book-listen:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.book-listen .listen-meta { margin-top: -.25rem; }
/* The reader's audiobook player, set in the page instead of over it. */
.reader-audio-bar.reader-audio-inline,
.reader-audio-bar.reader-audio-inline[data-audio-layout="dock"][data-audio-size] {
	position: static;
	z-index: auto;
	width: 100%;
	max-width: 30rem;
	margin: .75rem 0 .25rem;
	overflow: visible;
	background: var(--surface);
	-webkit-backdrop-filter: none;
	backdrop-filter: none;
	box-shadow: none;
}
.reader-audio-inline .reader-audio-settings-row { grid-template-columns: 76px minmax(0, 1fr); }
.reader-audio-bar.reader-audio-inline[data-audio-size="expanded"] .reader-audio-summary { grid-template-columns: minmax(0, 1fr) 44px; }
.audiobook-upload { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; }
.audiobook-upload progress { flex: 1 1 12rem; }
