/* 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: #f8f7fb;
	--surface: #fff;
	--surface-2: #f3f1f8;
	--surface-3: #e9e4f3;
	--line: #e2ddec;
	--line-soft: #efecf5;
	--ink: #221d2e;
	--ink-dim: #574e68;
	--muted: #6b6379;
	--primary: #7c4dbe;
	--primary-dim: #63399e;
	--on-primary: #fff;
	--danger: #b53c3c;
	--ok: #2c7658;
	--warn: #926727;
	--c1: #eee6f9; --c2: #d3bdee; --c3: #a986dc; --c4: #7c4dbe;
	--rail: 16rem;
	--topbar: 4.5rem;
	--gap: 1.5rem;
	--radius: 1rem;
	--radius-sm: .55rem;
	--radius-md: var(--radius);
	--surface-1: var(--surface);
	--shadow: 0 8px 30px rgb(34 29 46 / .07);
	--shadow-sm: 0 2px 8px rgb(34 29 46 / .04);
	--cover-ratio: 2 / 3;
	--card-min: clamp(8.5rem, 42vw, 10.5rem);
	--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: 2px 5px 14px rgb(34 29 46 / .12), 0 1px 3px rgb(34 29 46 / .06);
	--book-shadow-hover: 4px 12px 24px rgb(34 29 46 / .18), 0 2px 6px rgb(34 29 46 / .1);
}

[data-theme="dark"], .reader-body[data-reader-theme="dark"], .reader-body[data-reader-theme="black"] {
	color-scheme: dark;
	--bg: #101017;
	--surface: #16161e;
	--surface-2: #1f1e2a;
	--surface-3: #2b2939;
	--line: #37344a;
	--line-soft: #262433;
	--ink: #eae7f2;
	--ink-dim: #bcb6cc;
	--muted: #9891ab;
	--primary: #c4a7e7;
	--primary-dim: #d7c2f2;
	--on-primary: #1d1526;
	--danger: #f28c8c;
	--ok: #86ceab;
	--warn: #e1bb78;
	--c1: #2a2340; --c2: #453a68; --c3: #6f5aa0; --c4: #c4a7e7;
	--shadow: 0 8px 30px rgb(0 0 0 / .2);
	--shadow-sm: 0 2px 8px rgb(0 0 0 / .15);
	--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: #101017;
		--surface: #16161e;
		--surface-2: #1f1e2a;
		--surface-3: #2b2939;
		--line: #37344a;
		--line-soft: #262433;
		--ink: #eae7f2;
		--ink-dim: #bcb6cc;
		--muted: #9891ab;
		--primary: #c4a7e7;
		--primary-dim: #d7c2f2;
		--on-primary: #1d1526;
		--danger: #f28c8c;
		--ok: #86ceab;
		--warn: #e1bb78;
		--c1: #2a2340; --c2: #453a68; --c3: #6f5aa0; --c4: #c4a7e7;
		--shadow: 0 8px 30px rgb(0 0 0 / .2);
		--shadow-sm: 0 2px 8px rgb(0 0 0 / .15);
		--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(2rem, 3.5vw, 2.65rem)/1.15 Georgia, "Times New Roman", serif; margin: 0 0 .8rem; letter-spacing: -.04em; }
h2 { font-size: 1.1rem; font-weight: 600; margin: 0 0 .75rem; letter-spacing: -.02em; }
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(.75rem, env(safe-area-inset-top, 0px));
	padding-bottom: .75rem;
	padding-left: max(1.5rem, env(safe-area-inset-left, 0px));
	padding-right: max(2rem, 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: 2.5rem; height: 2.5rem; }
.brand .brand-name { display: flex; align-items: baseline; gap: .5rem; line-height: 1; }
.brand .brandmark {
	font: 400 1.65rem/1 Georgia, "Times New Roman", serif;
	letter-spacing: -.035em;
}
.brand .suffix {
	font-size: .72rem; font-weight: 500; color: var(--ink-dim);
	letter-spacing: .025em;
}

.topbar-context { margin-left: 1.25rem; padding-left: 1.5rem; border-left: 1px solid var(--line); color: var(--muted); font-size: .8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account { margin-left: auto; display: flex; align-items: center; gap: 1.25rem; flex: none; }
.account .who { display: flex; align-items: center; gap: .6rem; color: var(--ink-dim); font-size: .8rem; max-width: 14rem; overflow-wrap: anywhere; }
.account-avatar { width: 2rem; height: 2rem; flex: none; display: grid; place-items: center; border-radius: 50%; background: var(--surface-2); color: var(--primary); }
.account-avatar .ui-icon { width: 1rem; height: 1rem; }
.account form { margin: 0; }
.account button { font-size: .8rem; }
.account form:first-child button { width: 2.2rem; height: 2.2rem; justify-content: center; background: var(--surface-2); border: 1px solid var(--line-soft); border-radius: 50%; font-size: 1rem; }
.ui-icon { flex: none; display: block; }

.rail {
	grid-area: rail;
	background: var(--surface);
	border-right: 1px solid var(--line);
	padding: 2rem 1rem 1.25rem;
	position: sticky; top: var(--topbar);
	align-self: start;
	height: calc(100vh - var(--topbar)); height: calc(100dvh - var(--topbar));
	display: flex; flex-direction: column;
}
.rail-caption { margin: 0 .8rem 1rem; font-size: .61rem; font-weight: 600; letter-spacing: .13em; text-transform: uppercase; color: var(--muted); }
.rail nav { display: flex; flex-direction: column; gap: .4rem; }
.rail a {
	display: flex; align-items: center; gap: .6rem;
	min-height: 3rem; padding: .7rem .85rem;
	border-radius: .7rem;
	color: var(--ink-dim); text-decoration: none;
	font-size: .86rem;
}
.rail a:hover { background: var(--surface-2); color: var(--ink); }
.rail a[aria-current="page"] {
	background: color-mix(in srgb, var(--primary) 10%, var(--surface)); color: var(--primary); font-weight: 600;
}
.rail-note { margin-top: auto; padding: 3rem .85rem .5rem; color: var(--ink); }
.rail-note p { margin: .8rem 0; font: 1.6rem/1.25 Georgia, serif; letter-spacing: -.03em; }
.rail-note em { font-weight: 400; color: var(--primary); }
main {
	grid-area: main;
	padding: 2.5rem clamp(1.5rem, 3.5vw, 3.5rem) calc(3rem + env(safe-area-inset-bottom, 0px));
	padding-left: max(clamp(1.5rem, 3.5vw, 3.5rem), env(safe-area-inset-left, 0px));
	padding-right: max(clamp(1.5rem, 3.5vw, 3.5rem), env(safe-area-inset-right, 0px));
	min-width: 0;
}
.page { max-width: 78rem; margin: 0 auto; }
.page-intro { color: var(--muted); margin: .65rem 0 0; font-size: .9rem; }
.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-caption, .rail-note { display: 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.7rem; padding: .5rem .4rem; }
	.topbar-context { display: none; }
	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: 4rem; --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: 2rem; height: 2rem; }
	.brand .brandmark { font-size: 1.45rem; }
	.brand .suffix { font-size: .63rem; }
	.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; }
.cardhead .eyebrow { margin: 0 0 .4rem; }
.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-heading .eyebrow { margin: 0 0 .5rem; }
.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 .eyebrow { margin: 0 0 .15rem; }
.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-heading p { margin: 0; max-width: 42rem; }
.settings-heading .eyebrow { margin-bottom: .5rem; }
.settings-heading .muted { font-size: .9rem; }
.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: 600; font-size: .78rem;
	text-transform: uppercase; letter-spacing: .07em; background: var(--surface-2); }
th:first-child { border-radius: .5rem 0 0 .5rem; }
th:last-child { border-radius: 0 .5rem .5rem 0; }
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 share a book-jacket layout, separate from app cards. */
.auth-main {
	min-height: 100vh; min-height: 100svh;
	display: grid; place-items: center;
	padding: 3rem 1.5rem;
	background: radial-gradient(ellipse at 15% 20%, #72cfb410, transparent 55%),
		radial-gradient(ellipse at 90% 85%, #d5a4540a, transparent 50%), var(--bg);
}
.auth-wrap { width: min(100%, 61rem); }
.auth-shell {
	/* The auth panel 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;
	display: grid; grid-template-columns: .9fr 1.1fr;
	border: 1px solid var(--line); border-radius: 1.5rem;
	overflow: hidden; background: var(--surface);
	box-shadow: 0 24px 80px rgb(0 0 0 / .16);
}
.auth-story {
	position: relative; isolation: isolate; overflow: hidden;
	display: flex; flex-direction: column; align-items: flex-start;
	padding: 2.75rem; color: #f1edf7;
	background: radial-gradient(ellipse at 0 0, #3d2c5c, transparent 75%), #1d1526;
}
.auth-story::after {
	content: ""; position: absolute; z-index: -1;
	width: 34rem; height: 34rem; right: -22rem; bottom: -20rem;
	border: 1px solid #c4a7e724; border-radius: 50%;
	box-shadow: 0 0 0 3rem #c4a7e705, 0 0 0 6rem #c4a7e704;
}
.auth-brand { margin: 0 0 2.5rem; font: 1.7rem Georgia, serif; letter-spacing: .02em; }
.auth-brand span { margin-left: .4rem; color: #b9aecd; font: 500 .7rem ui-sans-serif, system-ui, sans-serif; letter-spacing: .18em; }
.auth-icon { display: block; width: 7rem; height: 7rem; margin-bottom: 1.8rem; }
.auth-headline { margin: 0 0 1.3rem; font: 400 clamp(2.4rem, 4vw, 3.15rem)/1.1 Georgia, serif; letter-spacing: -.035em; }
.auth-headline em { color: var(--auth-primary); font-weight: 400; }
.auth-story-copy { max-width: 25ch; margin: 0 0 2.5rem; color: #c2b8d4; font-size: .95rem; line-height: 1.7; }
.auth-story-foot { margin: auto 0 0; padding-top: 1rem; font-size: .72rem; letter-spacing: .035em; color: #c2b8d4; }
.auth-panel { padding: 3.75rem 3rem; align-self: center; min-width: 0; }
.auth-eyebrow { margin: 0 0 .85rem; color: var(--ink-dim); font-size: .68rem; font-weight: 600; letter-spacing: .15em; text-transform: uppercase; }
.auth-panel h1 { margin: 0 0 .75rem; max-width: 14ch; font-family: ui-sans-serif, system-ui, sans-serif; font-size: 2rem; line-height: 1.18; letter-spacing: -.045em; font-weight: 650; }
.auth-intro { margin: 0 0 2rem; 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 {
	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:hover { border-color: var(--muted); }
.auth-form input: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-wrap { max-width: 29rem; }
	.auth-shell { grid-template-columns: 1fr; border-radius: 1.1rem; }
	.auth-story { padding: 1.75rem; display: grid; grid-template-columns: 4rem 1fr; column-gap: 1rem; align-items: center; }
	.auth-brand { grid-column: 2; grid-row: 1; margin: 0; font-size: 1.6rem; }
	.auth-icon { grid-column: 1; grid-row: 1 / 3; width: 4rem; height: 4rem; margin: 0; }
	.auth-headline, .auth-story-copy { display: none; }
	.auth-story-foot { grid-column: 2; margin: .3rem 0 0; padding: 0; font-size: .65rem; }
	.auth-panel { padding: 2rem 1.75rem; }
	.auth-panel h1 { font-size: 1.9rem; max-width: none; }
}
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: 2rem;
	padding: .1rem .6rem; 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: 1rem; 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; }
.viewtoggle { display: inline-flex; padding: .2rem; border: 1px solid var(--line); border-radius: .6rem; background: var(--surface-2); }
.viewtoggle button, .viewtoggle .btn { border: 0; border-radius: .4rem; background: transparent; padding: .45rem .85rem; }
.viewtoggle .on { background: var(--surface); color: var(--ink); font-weight: 600; box-shadow: var(--shadow-sm); }
.librarytools .viewtoggle, .toolbar .viewtoggle { gap: .15rem; }

.libraryhead {
	display: flex; align-items: flex-end; justify-content: space-between;
	gap: 1.25rem; margin: 0 0 2rem; flex-wrap: wrap;
}
.libraryhead-main { display: flex; flex-direction: column; gap: .25rem; }
.libraryhead-row { display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap; }
.libraryhead h1 { margin: 0; }
.libraryhead .eyebrow { margin: 0; }
.libraryhead .page-intro { margin: .2rem 0 0; color: var(--muted); }
.librarycontrols { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.librarycontrols .upload-btn {
	display: inline-flex; align-items: center; gap: .35rem;
	padding: .35rem .75rem; font-size: .84rem; font-weight: 600;
	background: var(--surface-2); color: var(--ink);
	border: 1px solid var(--line); border-radius: var(--radius-sm);
	cursor: pointer; transition: all .15s ease;
}
.librarycontrols .upload-btn:hover {
	background: var(--surface-3); border-color: var(--line-strong);
	color: var(--primary);
}
.librarycontrols .upload-btn .upload-icon {
	display: inline-flex; align-items: center; color: var(--primary);
}
.librarycontrols .upload-btn .ui-icon { width: 1rem; height: 1rem; }
.librarycontrols .refreshbtn {
	display: inline-flex; align-items: center; gap: .35rem;
	padding: .35rem .75rem; font-size: .84rem; font-weight: 600;
	background: var(--surface-2); color: var(--ink);
	border: 1px solid var(--line); border-radius: var(--radius-sm);
	cursor: pointer; transition: all .15s ease;
}
.librarycontrols .refreshbtn:hover {
	background: var(--surface-3); border-color: var(--line-strong);
	color: var(--primary);
}
.librarycontrols .refreshbtn:disabled {
	opacity: .6; cursor: not-allowed;
}
.librarycontrols .refreshbtn .ui-icon {
	width: 1rem; height: 1rem; color: var(--primary);
}
.folderpick { margin: 0; }
.folderselect-wrap {
	display: inline-flex; align-items: center; gap: .4rem;
	background: var(--surface-2); border: 1px solid var(--line);
	border-radius: var(--radius-sm); padding: .25rem .6rem;
	box-shadow: var(--shadow-sm);
}
.folder-glyph {
	display: inline-flex; align-items: center; color: var(--primary);
}
.folder-glyph .ui-icon { width: 1rem; height: 1rem; }
.folderselect-wrap select {
	border: none; background: transparent; font-size: .85rem;
	font-weight: 600; color: var(--ink); padding: .2rem .25rem;
	cursor: pointer; outline: none; min-width: min(12rem, 45vw);
}

/* Consolidated command deck */
.librarydeck {
	display: flex; flex-direction: column; gap: .85rem;
	padding: 1.1rem 1.25rem; margin: 0 0 2rem;
	border: 1px solid var(--line); border-radius: var(--radius);
	background: var(--surface); box-shadow: var(--shadow-sm);
}
.librarydeck .toolbar.filters {
	display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
	margin: 0; padding: 0 0 .85rem;
	border-bottom: 1px solid var(--line-soft);
}
.shelf-chips { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.shelf-chips .chip {
	padding: .35rem .75rem; font-size: .82rem; font-weight: 500;
	border-radius: var(--radius-sm); border: 1px solid var(--line);
	background: var(--surface-2); color: var(--ink-dim);
	text-decoration: none; transition: all .15s ease;
}
.shelf-chips .chip:hover { background: var(--surface-3); color: var(--ink); }
.shelf-chips .chip.on {
	background: var(--primary); color: var(--on-primary);
	border-color: var(--primary); font-weight: 600;
}
.deck-divider {
	display: inline-block; width: 1px; height: 1.2rem;
	background: var(--line); margin: 0 .25rem;
}
.entity-chips { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.entity-chips .entity-link {
	font-size: .8rem; color: var(--muted); text-decoration: none;
	padding: .3rem .6rem; border-radius: var(--radius-sm);
	border: 1px dashed var(--line); transition: all .15s ease;
}
.entity-chips .entity-link:hover {
	color: var(--primary); border-color: var(--primary); background: var(--surface-2);
}
.librarydeck .librarytools {
	display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
	margin: 0; padding: 0; border: none; background: transparent;
}
.librarydeck .librarytools form:first-child { flex: 1 1 16rem; }
.librarydeck .librarytools input[type="search"] {
	width: min(100%, 28rem); border-radius: var(--radius-sm);
	padding: .55rem .85rem; background: var(--surface-2);
	border: 1px solid var(--line); font-size: .88rem; color: var(--ink);
}
.librarydeck .librarytools input[type="search"]:focus {
	border-color: var(--primary); background: var(--surface);
}
.deck-sort { display: flex; align-items: center; gap: .4rem; }
.deck-sort .sortchip {
	font-size: .8rem; padding: .35rem .65rem; border-radius: var(--radius-sm);
	border: 1px solid var(--line); background: var(--surface-2); color: var(--ink-dim);
	text-decoration: none;
}
.deck-sort .sortchip:hover { background: var(--surface-3); color: var(--ink); }

.librarytools {
	display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
	padding: 1rem; margin: 0 0 1.5rem;
	border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);
}
.librarytools form { margin: 0; display: flex; gap: .4rem; align-items: center; }
.librarytools form:first-child { flex: 1 1 18rem; }
.librarytools input[type="search"] { width: min(100%, 30rem); border-radius: var(--radius-sm); padding: .65rem .9rem; background: var(--surface-2); }
.librarytools .spacer { flex: 1; }
.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 */

.grid {
	display: grid; gap: 2rem 1.5rem;
	grid-template-columns: repeat(auto-fill, minmax(var(--card-min), 1fr));
	margin: 0 0 var(--gap);
}

.bookcard { display: flex; flex-direction: column; gap: .5rem; min-width: 0; }
.bookcard .art {
	position: relative; display: block;
	aspect-ratio: var(--cover-ratio);
	background: var(--surface-2);
	border: 1px solid var(--line);
	border-radius: var(--radius-sm);
	overflow: hidden;
	box-shadow: var(--book-shadow);
	transition: transform .22s cubic-bezier(.2,0,0,1), box-shadow .22s cubic-bezier(.2,0,0,1);
}
.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;
	transition: transform .22s ease;
}
.bookcard:hover .art {
	transform: translateY(-4px);
	box-shadow: var(--book-shadow-hover);
}
.bookcard:hover .art img { transform: scale(1.02); }

/* 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: .92rem; 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: .82rem; 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;
	margin-top: .05rem;
}

/* 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: linear-gradient(160deg, var(--surface-3), var(--surface-2));
}
.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.4rem; font-weight: 700; color: var(--primary);
}

/* 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: 1.2rem; box-shadow: var(--shadow-sm);
}
.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 { align-items: flex-start; flex-direction: column; }
	.libraryhead-row { width: 100%; justify-content: space-between; }
	.librarycontrols { width: 100%; justify-content: space-between; }
	.folderpick, .folderselect-wrap, .folderselect-wrap select { width: 100%; }
	.librarydeck { padding: .85rem; }
	.librarydeck .toolbar.filters {
		gap: .5rem;
		flex-wrap: nowrap;
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
		padding-bottom: .6rem;
		margin: 0 -.85rem;
		padding-left: .85rem;
		padding-right: .85rem;
	}
	.librarydeck .toolbar.filters::-webkit-scrollbar { display: none; }
	.shelf-chips, .entity-chips {
		flex-wrap: nowrap;
		flex-shrink: 0;
	}
	.shelf-chips .chip, .entity-chips .entity-link {
		white-space: nowrap;
		min-height: 2.2rem;
		display: inline-flex;
		align-items: center;
	}
	.deck-divider { flex-shrink: 0; }
	.librarydeck .librarytools { flex-direction: column; align-items: stretch; }
	.librarydeck .librarytools form:first-child, .librarydeck .librarytools input[type="search"] { width: 100%; }
	.deck-sort { justify-content: flex-end; }
	.librarytools { align-items: stretch; }
	.librarytools form:first-child, .librarytools input[type="search"] { width: 100%; }
	.librarytools .viewtoggle { margin-left: auto; }
	.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;
	background: var(--surface); border: 1px solid var(--line);
	border-radius: var(--radius-md); 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; }

/* 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(0,1fr) minmax(0,1fr) minmax(0,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{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: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:3.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:1.1rem}
.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(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;
  width:min(32rem,calc(100vw - 2rem));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;
  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:3rem}
.reader-body[data-reader-autohide="true"] .reader-stage{padding-top:0}
#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:var(--reader-margin,48px);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. */
.reader-settings{position:relative}
.reader-settings>summary{list-style:none;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;
  width:19rem;max-height:min(75vh,34rem);overflow:auto;
  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)}
.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 */

/* The continue-reading banner. "The Reading Desk" — a tactile, bibliographic
   feature for the volume currently in progress. */
.resume {
	display: flex; gap: 2rem; align-items: center;
	padding: 1.75rem 2rem; margin: 0 0 2rem;
	border: 1px solid var(--line); border-radius: var(--radius);
	background: var(--surface);
	box-shadow: var(--shadow-sm);
	position: relative;
	overflow: hidden;
}
.resume::before {
	content: "";
	position: absolute; top: 0; bottom: 0; left: 0; width: 4px;
	background: var(--primary);
}
.resume .art {
	flex: 0 0 8.5rem; max-width: 9.5rem;
	position: relative; display: block; overflow: hidden;
	border-radius: var(--radius-sm); border: 1px solid var(--line);
	box-shadow: var(--book-shadow); aspect-ratio: var(--cover-ratio);
	transition: transform .22s ease, box-shadow .22s ease;
}
.resume .art:hover {
	transform: translateY(-2px) scale(1.02);
	box-shadow: var(--book-shadow-hover);
}
.resume .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;
}
.resume .art::after {
	content: "";
	position: absolute; inset: 0 0 0 auto;
	width: 4px;
	background: var(--book-edge);
	pointer-events: none;
	z-index: 2;
}
.resume .art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.resume .herotext { flex: 1; min-width: 14rem; display: flex; flex-direction: column; gap: .45rem; }
.resume .herostatus { display: flex; align-items: center; gap: .75rem; }
.eyebrow {
	font-size: .68rem; font-weight: 700; letter-spacing: .12em;
	text-transform: uppercase; color: var(--primary);
}
.resume .heropct {
	font-size: .75rem; font-weight: 700; color: var(--ink-dim);
	background: var(--surface-2); padding: .15rem .5rem;
	border-radius: var(--radius-sm); border: 1px solid var(--line-soft);
}
.resume .title {
	font: 600 clamp(1.4rem, 2.8vw, 1.95rem)/1.2 Georgia, "Iowan Old Style", "Apple Garamond", serif;
	color: var(--ink); text-decoration: none; letter-spacing: -.02em;
}
.resume .title:hover { color: var(--primary); }
.resume .by { font-size: .95rem; color: var(--ink-dim); font-style: italic; }
.resume .by-prefix { font-style: normal; color: var(--muted); font-size: .85rem; margin-right: .2rem; }
.resume .herotrack {
	display: flex; align-items: center; gap: .9rem; margin: .35rem 0 .4rem; flex-wrap: wrap;
}
.resume .bar {
	flex: 1; max-width: 18rem; height: 6px;
	background: var(--surface-3); border-radius: 3px; overflow: hidden;
}
.resume .bar span { display: block; height: 100%; background: var(--primary); border-radius: 3px; }
.resume .herotrack .muted { font-size: .82rem; color: var(--muted); white-space: nowrap; }
.resume .rowlinks {
	display: flex; align-items: center; gap: .6rem; margin: .6rem 0 0; flex-wrap: wrap;
}
.resume .rowlinks .btn.resume-btn {
	padding: .55rem 1.15rem; font-weight: 600; font-size: .92rem;
	background: var(--primary); color: var(--on-primary);
	border-radius: var(--radius-sm); box-shadow: var(--shadow-sm);
	transition: transform .15s ease, background .15s ease;
}
.resume .rowlinks .btn.resume-btn:hover {
	background: var(--primary-dim); color: var(--on-primary);
	transform: translateX(2px);
}
.resume .rowlinks .btn:not(.primary) {
	padding: .5rem .85rem; font-size: .82rem; color: var(--ink-dim);
	border: 1px solid var(--line); border-radius: var(--radius-sm);
	background: var(--surface-2); text-decoration: none;
}
.resume .rowlinks .btn:not(.primary):hover {
	background: var(--surface-3); color: var(--ink);
}

@media (max-width: 38em) {
	.resume {
		flex-direction: row; align-items: flex-start; gap: 1rem;
		padding: 1rem;
	}
	.resume .art { flex: 0 0 5.5rem; max-width: 5.5rem; }
	.resume .herotext { min-width: 0; gap: .3rem; }
	.resume .title { font-size: 1.15rem; }
	.resume .by { font-size: .85rem; }
	.resume .bar { max-width: 100%; }
	.resume .rowlinks { gap: .4rem; margin-top: .4rem; }
	.resume .rowlinks .btn.resume-btn { padding: .45rem .85rem; font-size: .84rem; }
	.resume .rowlinks .btn:not(.primary) { display: none; }
}

/* The filter row under the banner. A chip is a link, so a filtered
   shelf has a URL somebody can send. */
.toolbar.filters {
	margin: .2rem 0 1.25rem; padding: .5rem 0;
	border-bottom: 1px solid var(--line-soft);
}
.sortchip { display: inline-flex; gap: .35rem; align-items: center; }
.groupseries label.check { margin: 0; white-space: nowrap; }
.groupseries input { accent-color: var(--primary); }

/* The open button on every cover.

   Always visible rather than revealed on hover, because hover does not
   exist on a phone and a web page cannot claim long-press — which is
   the whole reason this page needed designing rather than merging. It
   is a link to the book's page, not a menu: a popup anchored inside an
   overflow-hidden cover is clipped by the picture it hangs off, and
   every action it would have listed is on that page already. */
.cardopen {
	position: absolute; top: .35rem; right: .35rem; z-index: 3;
	display: flex; align-items: center; justify-content: center;
	width: 1.7rem; height: 1.7rem; border-radius: var(--radius-sm);
	background: rgb(0 0 0 / .6); color: #fff;
	font-size: 1.1rem; line-height: 1; text-decoration: none;
	opacity: .6; transition: opacity .15s ease, background .15s ease;
}
.bookcard:hover .cardopen, .cardopen:focus-visible { opacity: 1; }
.cardopen:hover { opacity: 1; background: var(--primary); color: var(--on-primary); }

/* A text tile needs its link to fill the tile, since the menu sits on
   top of it and the two must not overlap. */
.bookcard .art-text .fill {
	display: flex; flex-direction: column; justify-content: center;
	gap: .3rem; height: 100%; text-decoration: none; color: inherit;
}
.bookcard .art-text .tag { align-self: center; margin-top: .4rem; }

/* 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: 1px solid var(--line); border-radius: var(--radius-sm);
	background: var(--surface-2); box-shadow: var(--shadow-sm);
}
.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 .stackfront img { transition: transform .18s ease; }
.seriescard .stackback.far img { opacity: .45; }
.seriescard .stackback.near img { opacity: .65; }
.seriescard .stackfront.complete img { opacity: .55; }
.seriescard:hover .stackfront img { transform: scale(1.03); }
.seriescount {
	position: absolute; z-index: 2; top: .4rem; right: .9rem;
	display: flex; align-items: center; justify-content: center;
	width: 1.75rem; height: 1.75rem; border-radius: 50%;
	border: 1px solid var(--primary); background: var(--surface-2);
	color: var(--ink); font-size: .75rem; font-weight: 700;
}
.seriesrail {
	display: flex; gap: 2px; height: 4px; margin: .1rem .7rem 0 0;
}
.seriesrail span {
	flex: 1; min-width: 2px; border-radius: 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; }

/* 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, .librarytools,
	.toolbar.filters, .cta, .cardopen, .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;
	}
}
