/* ON Suite — warm and a little more alive than it used to be.
 *
 * Everything is driven by the tokens in :root. Apps must not introduce new
 * colours or spacing values; they compose these. That constraint is what
 * makes four separately-built apps look like one suite.
 */

:root {
	/* A warm cream base carries the interface. Teal is the everyday accent
	 * (links, buttons, active states); --c-accent-attention (orange) is
	 * deliberately separate and used only for things that need attention —
	 * a due/overdue badge — so it stays meaningful by staying rare. */
	--c-bg:          #fefefe;
	--c-bg-subtle:   #f2e9db;
	--c-bg-inset:    #efe0cf;
	--c-border:      #e3d3bf;
	--c-border-firm: #c9b49a;
	--c-text:        #3a2f26;
	--c-text-dim:    #6b5d4f;
	--c-text-faint:  #756758; /* 4.5:1+ on --c-bg and --c-bg-subtle (#556) */
	--c-accent:      #2f6f6a;
	--c-accent-bg:   #d9e8e6;
	--c-accent-attention:    #a84c1b; /* 4.5:1+ as text on --c-accent-attention-bg (#556) */
	--c-accent-attention-bg: #f7e4d5;
	--c-danger:      #a51d2d;
	--c-danger-bg:   #fbeaec;

	/* A 4px scale. Still the scarce-vertical-space discipline of a code
	 * listing, just with more breathing room applied on top in the shell
	 * chrome (see .shell-bar, main, .app-sidebar below). */
	--s-1: 0.25rem;
	--s-2: 0.5rem;
	--s-3: 0.75rem;
	--s-4: 1rem;
	--s-5: 1.5rem;
	--s-6: 2rem;

	/* --font-ui drives headings and UI chrome; --font-body drives prose.
	 * They're equal in the default and literary pairings and differ only in
	 * specialized pairings. --font-mono never changes with the pairing. */
	--font-ui: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	--font-body: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

	--fs-2xs: 0.625rem;
	--fs-xs: 0.75rem;
	--fs-sm: 0.8125rem;
	--fs-base: 0.9375rem;
	--fs-lg: 1.125rem;
	--fs-xl: 1.375rem;
	--fs-2xl: 1.75rem;

	--radius: 6px;
	--border: 1px solid var(--c-border);
	--ring: 2px solid var(--c-accent);

	--measure: 68ch; /* comfortable reading width, for prose and forms */
	--content-max: 75rem; /* main's own cap — wider, since a snippet list or
	                          a wide code block has no comfortable-line-length
	                          reason to stay prose-narrow */
}

/* Runtime font switcher. data-font is set on <html> from a cookie, read
 * server-side on every request (internal/platform/web/prefs.go) so the first
 * paint already has the right fonts — no flash of the wrong typeface. */
[data-font="serif"] {
	--font-ui: 'Source Serif 4', Georgia, "Times New Roman", serif;
	--font-body: 'Source Serif 4', Georgia, "Times New Roman", serif;
}

[data-font="duo"] {
	--font-ui: 'iA Writer Duo S', monospace, sans-serif;
	--font-body: 'iA Writer Duo S', monospace, sans-serif;
	--fs-base: 0.875rem;
	--fs-sm: 0.75rem;
	--fs-xs: 0.7rem;
}

/* Self-hosted fonts. One file per subset per family; Google's own served CSS declares
 * every weight it supports against the same file, so we mirror that exactly
 * rather than guessing at a variable-font weight range. Subsets declare unicode-range
 * so browsers fetch Cyrillic files only when needed, and fall back to system fonts
 * for unbundled scripts like Hebrew. */
/* Inter */
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/inter-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/inter-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url("/static/fonts/inter-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/inter-cyrillic.woff2") format("woff2"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/inter-cyrillic.woff2") format("woff2"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url("/static/fonts/inter-cyrillic.woff2") format("woff2"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/inter.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/inter.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url("/static/fonts/inter.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

/* JetBrains Mono */
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/jetbrains-mono-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/jetbrains-mono-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/jetbrains-mono-cyrillic.woff2") format("woff2"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/jetbrains-mono-cyrillic.woff2") format("woff2"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/jetbrains-mono.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/jetbrains-mono.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

/* Source Serif 4 */
@font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/source-serif-4-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/source-serif-4-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 600; font-display: swap; src: url("/static/fonts/source-serif-4-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/source-serif-4-cyrillic.woff2") format("woff2"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/source-serif-4-cyrillic.woff2") format("woff2"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 600; font-display: swap; src: url("/static/fonts/source-serif-4-cyrillic.woff2") format("woff2"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/source-serif-4.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/source-serif-4.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 600; font-display: swap; src: url("/static/fonts/source-serif-4.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

/* iA Writer Duo S */
@font-face { font-family: 'iA Writer Duo S'; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/ia-writer-duo-s-regular.woff2") format("woff2"); }
@font-face { font-family: 'iA Writer Duo S'; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/ia-writer-duo-s-regular.woff2") format("woff2"); }
@font-face { font-family: 'iA Writer Duo S'; font-style: normal; font-weight: 600; font-display: swap; src: url("/static/fonts/ia-writer-duo-s-bold.woff2") format("woff2"); }
@font-face { font-family: 'iA Writer Duo S'; font-style: normal; font-weight: 700; font-display: swap; src: url("/static/fonts/ia-writer-duo-s-bold.woff2") format("woff2"); }
@font-face { font-family: 'iA Writer Duo S'; font-style: italic; font-weight: 400; font-display: swap; src: url("/static/fonts/ia-writer-duo-s-italic.woff2") format("woff2"); }
@font-face { font-family: 'iA Writer Duo S'; font-style: italic; font-weight: 700; font-display: swap; src: url("/static/fonts/ia-writer-duo-s-bold-italic.woff2") format("woff2"); }

/* Dark mode is a real toggle, not prefers-color-scheme: data-theme is set
 * server-side from a cookie (internal/platform/web/prefs.go, ThemeFrom)
 * which always returns an explicit "light" or "dark", defaulting to light
 * when no cookie exists yet. Because the server never leaves data-theme
 * unset, there is no need for a prefers-color-scheme fallback here — every
 * response already carries the attribute this selector keys off. */
:root[data-theme="dark"] {
	--c-bg:          #241d17;
	--c-bg-subtle:   #2b231b;
	--c-bg-inset:    #2e2621;
	--c-border:      #3d322a;
	--c-border-firm: #52443a;
	--c-text:        #efe6da;
	--c-text-dim:    #b8a996;
	--c-text-faint:  #998a7a; /* 4.5:1+ on --c-bg and --c-bg-subtle (#556) */
	--c-accent:      #64a5a0; /* 4.5:1+ for links on every dark background and on --c-accent-bg (#556) */
	--c-accent-bg:   #1e3532;
	--c-accent-attention:    #cf8357; /* 4.5:1+ as text on --c-accent-attention-bg; fills take dark text (#556) */
	--c-accent-attention-bg: #3d2a1c;
	--c-danger:      #f08c95;
	--c-danger-bg:   #3a1e22;
}

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

/* Off-screen but still in the layout and the accessibility tree — unlike
   display:none, which some UAs exclude from a form's implicit-submission
   default-button search (see outline.html's #57 append button). Also
   selectable, for JS that needs a real textarea to copy from
   (document.execCommand's fallback path), where display:none would make
   .select() a no-op. */
.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;
}

html { -webkit-text-size-adjust: 100%; }

body {
	margin: 0;
	background: var(--c-bg);
	color: var(--c-text);
	font: var(--fs-base)/1.6 var(--font-body);
}

/* Focus is restyled, never removed. This suite is meant to be usable
 * entirely from the keyboard. */
:focus-visible {
	outline: var(--ring);
	outline-offset: 2px;
	border-radius: var(--radius);
}

a { color: var(--c-accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { text-decoration-thickness: 2px; }

/* Wider steps than the 4px spacing scale on purpose: at the old h1=22px vs
 * body=15px (a 1.47x ratio), nothing on a page announced itself as more
 * important than anything else. */
h1, h2, h3 { margin: 0 0 var(--s-3); font-weight: 600; line-height: 1.25; letter-spacing: -0.01em; }
h1 { font-size: var(--fs-2xl); }
h2 { font-size: var(--fs-xl); }
h3 { font-size: var(--fs-lg); }

code, pre, kbd { font-family: var(--font-mono); font-size: var(--fs-sm); }

/* Filled, no border: a passive block of content to read, distinct from the
 * bordered treatment on things you act on (buttons, inputs, cards). Uniform
 * borders on everything is what made buttons, inputs, cards and code blocks
 * all look like the same box. */
pre {
	margin: 0;
	padding: var(--s-3);
	overflow-x: auto;
	background: var(--c-bg-subtle);
	border-radius: var(--radius);
}

kbd {
	padding: 1px var(--s-1);
	background: var(--c-bg-inset);
	border: 1px solid var(--c-border-firm);
	border-radius: var(--radius);
	font-size: 0.85em;
}

/* ---- Shell ------------------------------------------------------------- */

.shell-bar {
	display: flex;
	align-items: center;
	gap: var(--s-5);
	padding: var(--s-3) var(--s-5);
	background: var(--c-bg);
	border-bottom: var(--border);
}

.shell-logo {
	display: flex;
	align-items: center;
	gap: var(--s-2);
	color: var(--c-text);
	text-decoration: none;
	white-space: nowrap;
}

.shell-mark { font-weight: 600; letter-spacing: 0.01em; }

.shell-crumbs {
	display: flex;
	align-items: center;
	gap: var(--s-2);
	flex: 1;
	min-width: 0;
	font-size: var(--fs-sm);
	color: var(--c-text-dim);
	overflow-x: auto;
	white-space: nowrap;
}

.shell-crumbs a,
#shell-crumb-tail > span:not([aria-hidden]) {
	max-width: 200px;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}
.shell-crumbs a { color: var(--c-text-dim); text-decoration: none; }
.shell-crumbs a:hover { color: var(--c-accent); }
#shell-crumb-tail > span:last-child { color: var(--c-text); font-weight: 500; }

/* Invisible to layout, so its children (the app-name and title segments)
 * stay direct flex items of .shell-crumbs and keep its gap between them —
 * issue #205. Without this, wrapping them in a span for OOB-swap targeting
 * would collapse that gap, the same class of regression #outline-heading
 * hit without its own .stack class (see the zoom-transition PR). */
#shell-crumb-tail {
	display: contents;
}

.shell-user {
	display: flex;
	align-items: center;
	gap: var(--s-3);
	font-size: var(--fs-sm);
	color: var(--c-text-dim);
}

/* ---- User menu (#403) ---- */

/* One button in the header's top right: icon, username, chevron. It opens a
 * panel holding Account, the display settings and Log out. */
.shell-user-menu { position: relative; }
.shell-user-menu summary { list-style: none; cursor: pointer; }
.shell-user-menu summary::-webkit-details-marker { display: none; }

.shell-user-toggle {
	display: inline-flex;
	align-items: center;
	gap: var(--s-2);
	max-width: 14rem;
	color: var(--c-text);
}

.shell-user-toggle .toolbar-icon:last-child {
	width: 0.9rem;
	height: 0.9rem;
	color: var(--c-text-dim);
	transition: transform 120ms ease-out;
}

.shell-user-menu[open] .shell-user-toggle { background: var(--c-bg-inset); }
.shell-user-menu[open] .shell-user-toggle .toolbar-icon:last-child { transform: rotate(180deg); }

.shell-user-name {
	min-width: 0;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}

.shell-user-menu-panel {
	position: absolute;
	right: 0;
	top: calc(100% + var(--s-2));
	z-index: 10;
	display: flex;
	flex-direction: column;
	min-width: 14rem;
	padding: var(--s-1);
	background: var(--c-bg);
	border: var(--border);
	border-radius: var(--radius);
	box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
	color: var(--c-text);
}

.shell-user-menu-panel form { display: block; }

/* Account and Log out: full-width rows, a link and a button that look the
 * same. */
.shell-menu-item {
	display: flex;
	align-items: center;
	gap: var(--s-2);
	width: 100%;
	padding: var(--s-2);
	border: none;
	border-radius: var(--radius);
	background: none;
	color: var(--c-text);
	font: inherit;
	font-size: var(--fs-sm);
	text-align: left;
	text-decoration: none;
	cursor: pointer;
}

.shell-menu-item:hover { background: var(--c-bg-inset); }

.shell-menu-separator {
	margin: var(--s-1) 0;
	border: none;
	border-top: var(--border);
}

.shell-menu-settings { padding: var(--s-2); }

.shell-menu-heading {
	display: flex;
	align-items: center;
	gap: var(--s-2);
	margin-bottom: var(--s-2);
	font-size: var(--fs-sm);
	font-weight: 500;
}

.shell-menu-settings .field { margin-bottom: var(--s-2); }
.shell-menu-settings .field:last-child { margin-bottom: 0; }
.shell-menu-settings .row { flex-wrap: wrap; margin-top: var(--s-1); }
.shell-menu-settings button[data-theme-value].active,
.shell-menu-settings button[data-font-value].active {
	background: var(--c-accent-bg);
	color: var(--c-accent);
	border-color: var(--c-accent);
}

/* Online/offline dot — see internal/ui/static/connectivity.js, which owns
 * data-status on this element after page load. Initial markup ships
 * "online" so a page rendered before the script runs never flashes a
 * false "offline" reading. */
.conn-indicator {
	display: inline-flex;
	align-items: center;
}

.conn-dot {
	width: 0.5rem;
	height: 0.5rem;
	border-radius: 50%;
	background: #16a34a;
}

.conn-indicator[data-status="offline"] .conn-dot {
	background: var(--c-danger);
}

/* ---- App shell: sidebar + main ------------------------------------------ */

.app-shell {
	display: flex;
	align-items: stretch;
	min-height: calc(100vh - 8rem);
}

.app-sidebar {
	display: flex;
	flex-direction: column;
	flex-shrink: 0;
	width: 12rem;
	padding: var(--s-4) var(--s-2);
	border-right: var(--border);
	transition: width 0.15s ease;
}

.app-sidebar[data-collapsed] { width: 3.5rem; }

.sidebar-toggle {
	display: flex;
	width: 100%;
	justify-content: flex-start;
	margin-bottom: var(--s-3);
	transition: transform 0.15s ease;
}

/* Points the other way once collapsed, so the glyph itself shows which
 * way the next click will go rather than staying static regardless of
 * state. */
.app-sidebar[data-collapsed] .sidebar-toggle {
	transform: rotate(180deg);
}

/* flex: 1 1 auto (not the default 0 1 auto) lets this stretch to fill
 * .app-sidebar's full height below the toggle button — the same flex column
 * it now is — so .sidebar-separator's margin-top: auto below has leftover
 * height to absorb and pins Admin to the sidebar's bottom edge. */
.shell-nav {
	display: flex;
	flex-direction: column;
	flex: 1 1 auto;
	min-height: 0;
	gap: var(--s-1);
}

.sidebar-item {
	display: flex;
	align-items: center;
	gap: var(--s-2);
	padding: var(--s-2);
	border-radius: var(--radius);
	color: var(--c-text-dim);
	text-decoration: none;
	white-space: nowrap;
	overflow: hidden;
}

/* Each app's glyph in its own colour (spec 2026-10-13-app-identity-colours).
 * The fallbacks keep an item without a swatch class looking as it used to. */
.sidebar-item svg { flex: none; color: var(--swatch-line, var(--c-accent)); }

.sidebar-item:not(.sidebar-item-disabled):hover { background: var(--c-bg-inset); color: var(--c-text); }

/* The active app's row picks up its own soft tint; the label stays in the
 * normal text colour so the tint, not the text, carries the identity. */
.sidebar-item[aria-current="page"] {
	background: var(--swatch-soft, var(--c-accent-bg));
	color: var(--c-text);
	font-weight: 600;
}

.sidebar-item-disabled { opacity: 0.5; cursor: default; }

.app-sidebar[data-collapsed] .sidebar-label { display: none; }

/* main needs no rule here: it is a flex item inside .app-shell, and the
 * max-width + margin: 0 auto it already got in Step 3 is enough — CSS gives
 * a flex item's auto margins first claim on any free space in the flex
 * line, so main centers itself in whatever room the sidebar leaves without
 * needing flex-grow. */

/* ---- Footer -------------------------------------------------------------- */

.app-footer {
	padding: var(--s-3) var(--s-5);
	border-top: var(--border);
	color: var(--c-text-faint);
	font-size: var(--fs-sm);
	text-align: center;
}

/* min-width: 0 because main is a flex item in .app-shell: the default
 * min-width: auto stops it shrinking below its content, so one long snippet
 * line held it at the full 75rem and scrolled the page sideways whenever
 * the viewport was narrower than that plus the sidebar (#411). */
main { padding: var(--s-6) var(--s-5); max-width: var(--content-max); margin: 0 auto; width: 100%; min-width: 0; }
/* Reader's three-pane layout is the one page in the suite that wants edge-to-
 * edge width rather than a comfortable reading measure — the whole point of
 * the resizable layout is that the reading pane gets whatever room the
 * viewport actually has. main's own reading-width cap would otherwise clip
 * every pane to 75rem regardless of how wide the window is. */
main:has(.reader-panes) { max-width: none; padding: 0;}
.measure { max-width: var(--measure); }

/* ---- Forms and buttons ------------------------------------------------- */

label { display: block; margin-bottom: var(--s-1); font-size: var(--fs-sm); color: var(--c-text-dim); }

input[type="text"], input[type="password"], input[type="url"], input[type="number"], textarea, select {
	width: 100%;
	padding: var(--s-2);
	background: var(--c-bg);
	color: var(--c-text);
	border: 1px solid var(--c-border-firm);
	border-radius: var(--radius);
	font: inherit;
}

textarea { font-family: var(--font-mono); resize: vertical; }

.field { margin-bottom: var(--s-4); }

/* Title and Language side by side rather than full-width stacked fields —
 * a single-line text input and a short dropdown don't need a whole row to
 * themselves the way the snippet body textarea does. */
.field-row { align-items: flex-start; }
.field-row .field { flex: 1; margin-bottom: 0; }

button, .button {
	padding: var(--s-2) var(--s-4);
	background: var(--c-bg-inset);
	color: var(--c-text);
	border: 1px solid var(--c-border-firm);
	border-radius: var(--radius);
	font: inherit;
	text-decoration: none;
	cursor: pointer;
}

/* :where() drops this to (0,1,0) — just the :hover — so any component rule
 * that sets its own background (one class plus its own :hover, or one class
 * later in this file) wins without needing a second class (#375). Disabled
 * buttons are excluded inside the :where() so they get no hover at all. */
:where(button:not(:disabled), .button):hover { background: var(--c-border); }

/* A button that reads as a link: used for actions that must be POSTs, such as
 * logging out, without shouting like a primary button. Comes after
 * `button, .button` on purpose: `.quiet` and `.button` are equal
 * specificity, so an element carrying both classes (e.g. the settings-menu
 * gear `<summary class="quiet button">`, or the theme/font/sidebar toggle
 * buttons) needs `.quiet` to win the source-order tie-break. */
.quiet {
	padding: var(--s-1) var(--s-2);
	background: transparent;
	border-color: transparent;
	color: var(--c-text-dim);
	font-weight: 400;
}

.quiet:hover { background: var(--c-bg-inset); color: var(--c-text); }

/* Prominence is opt-in via `.primary`, never implied by <button type=submit">
 * on its own. A page should carry at most one of these — it is the single
 * action the page wants a visitor to take (Sign in, Save snippet) — so a
 * new form's submit button starts out neutral instead of accidentally
 * shouting the loudest thing on the page. */
.primary {
	background: var(--c-accent);
	border-color: var(--c-accent);
	color: #fff;
	font-weight: 600;
}

.primary:hover { background: var(--c-accent); border-color: var(--c-accent); filter: brightness(1.1); }

:root[data-theme="dark"] .primary { color: #10141a; }

/* ---- Notices ----------------------------------------------------------- */

/* Filled, no border — see the comment on pre above. */
.notice {
	padding: var(--s-2) var(--s-3);
	margin-bottom: var(--s-4);
	border-radius: var(--radius);
	background: var(--c-bg-subtle);
	font-size: var(--fs-sm);
}

.notice-error {
	background: var(--c-danger-bg);
	color: var(--c-danger);
}

/* ---- htmx request indicators ------------------------------------------ */

/* The rules htmx would otherwise inject as an inline <style> on load. The
 * CSP (style-src 'self') blocks that, so base.html turns the injection off
 * with includeIndicatorStyles:false and they live here instead (#400). An
 * .htmx-indicator stays hidden until its request is in flight, whether the
 * class lands on the indicator itself or on an ancestor. */
.htmx-indicator {
	opacity: 0;
	visibility: hidden;
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
	opacity: 1;
	visibility: visible;
	transition: opacity 200ms ease-in;
}

/* ---- Layout helpers used across apps ---------------------------------- */

.stack > * + * { margin-top: var(--s-3); }
.row { display: flex; align-items: center; gap: var(--s-2); }
.dim { color: var(--c-text-dim); }
.faint { color: var(--c-text-faint); font-size: var(--fs-sm); }
.centered { max-width: 22rem; margin: var(--s-6) auto; }

/* Wide content scrolls inside its own box rather than making the page
 * scroll sideways. */
.scroll-x { overflow-x: auto; }

/* ---- Home page card grid ------------------------------------------------ */

.home-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
	gap: var(--s-4);
}

.app-card {
	display: block;
	padding: var(--s-4);
	background: var(--c-bg);
	border: var(--border);
	border-radius: var(--radius);
	color: var(--c-text);
	text-decoration: none;
	transition: border-color 0.15s ease;
}

.app-card:hover { border-color: var(--c-border-firm); }

.app-card h3 { margin: var(--s-3) 0 var(--s-1); }
.app-card p { margin: 0; }

/* A soft badge in the app's own colour, so each tile is recognisable before
 * its name is read (spec 2026-10-13-app-identity-colours §4.1). The glyph is
 * still the thin line icon; only its colour and the tint behind it are per app. */
.app-card-icon {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 2.5rem;
	height: 2.5rem;
	border-radius: calc(var(--radius) + var(--s-1));
	background: var(--swatch-soft, var(--c-accent-bg));
	color: var(--swatch-line, var(--c-accent));
}

.app-card-icon svg { width: 1.625rem; height: 1.625rem; }

/* Equal card area for a shipped app and three that don't exist yet reads as
 * "this is a mockup." One muted line carries the same information without
 * competing with ON Paste for attention. */
.home-coming-soon { margin-top: var(--s-2); }

/* ---- Snippets ---------------------------------------------------------- */

.snippet-head h1 { margin-bottom: var(--s-1); }
.snippet-head p { margin: 0; }

.snippet-actions { flex-wrap: wrap; }
.snippet-actions form { display: inline; }
/* Delete is always the row's last form; pushing it to the far end keeps a
 * destructive action away from the everyday Raw/Share/Stop-sharing cluster. */
.snippet-actions form:last-child { margin-left: auto; }

button.danger { color: var(--c-danger); }
/* color too: .toolbar-btn:hover (0,2,0) would otherwise reset a danger
 * toolbar button's text to neutral on hover (#375). */
button.danger:hover { background: var(--c-danger-bg); color: var(--c-danger); }

.snippet-body {
	border-radius: var(--radius);
	background: var(--c-bg-subtle);
	padding: var(--s-2) var(--s-3);
}

/* Stacked title/metadata/toolbar for the detail pane's view mode (as
 * opposed to .notes-toolbar's side-by-side layout, shared with Notes and
 * with Paste's own edit/new forms): a long title has nowhere to push the
 * toolbar into, since there's no second column to crowd. */
.paste-detail-header {
	display: flex;
	flex-direction: column;
	gap: var(--s-2);
}
.paste-detail-header h1,
.paste-detail-header p {
	margin: 0;
}

.paste-toolbar-actions {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--s-2);
}

/* Delete alone on the right: every other action is a low-stakes read/edit
 * operation, so the one destructive control gets visual distance from an
 * accidental click near the rest. */
.paste-toolbar-actions-end {
	margin-left: auto;
}

.snippet-body pre { margin: 0; padding: 0; border: none; background: none; }

.notice code {
	word-break: break-all;
	background: var(--c-bg-inset);
	padding: 1px var(--s-1);
	border-radius: var(--radius);
}

.notice.row {
	flex-wrap: wrap;
	justify-content: space-between;
}

.notice.row span {
	flex: 1 1 auto;
	min-width: 0;
	word-break: break-all;
}

/* ---- Snippet list ------------------------------------------------------ */

.list-head { justify-content: space-between; margin-bottom: var(--s-2); }
.list-head h1, .list-head h2 { margin: 0; }

.snippet-list { margin: 0; padding: 0; list-style: none; }

/* Issue #393: rows used to be ~116px tall — the <li> and the .snippet-row
 * link inside it each carried their own padding, around three full-size
 * lines — so a large screen showed about six. The link's padding alone now
 * spaces a row; the <li>'s 1px keeps a hovered row's background off the
 * separator. */
.snippet-list li {
	padding: 1px 0;
	border-bottom: var(--border);
}

.snippet-list li:last-child { border-bottom: none; }
.snippet-list p { margin: 0; }

/* One line: the title takes whatever the pills and date leave, and is the
 * only part that truncates. */
.snippet-row-head {
	display: flex;
	align-items: center;
	gap: var(--s-1);
	min-width: 0;
}

.snippet-title {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-size: var(--fs-sm);
	font-weight: 600;
}

.snippet-pill {
	flex: none;
	display: inline-flex;
	align-items: center;
	padding: 0 var(--s-1);
	border: 1px solid var(--c-border);
	border-radius: 999px;
	background: var(--c-bg-subtle);
	color: var(--c-text-dim);
	font-size: var(--fs-2xs);
	font-weight: 400;
	line-height: 1.5;
	white-space: nowrap;
}
.snippet-pill-shared {
	border-color: var(--c-border-firm);
	background: var(--c-accent-attention-bg);
}
.snippet-pill .chip-icon {
	width: 0.9em;
	height: 0.9em;
}

.snippet-date {
	flex: none;
	margin-left: var(--s-1);
	color: var(--c-text-faint);
	font-size: var(--fs-xs);
	font-weight: 400;
	white-space: nowrap;
}

.snippet-preview {
	font-family: var(--font-mono);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	color: var(--c-text-dim);
	font-size: var(--fs-xs);
	font-weight: 400;
}

/* ---- ON Paste split view ------------------------------------------------ */

.paste-shell {
	display: flex;
	align-items: flex-start;
	gap: var(--s-5);
}

.paste-list-pane {
	flex: 0 0 20rem;
	min-width: 0;
}

.paste-detail-pane {
	flex: 1;
	min-width: 0;
}

.paste-detail-empty {
	padding: var(--s-4) 0;
}

.snippet-row {
	display: block;
	padding: var(--s-1) var(--s-2);
	margin: 0 calc(var(--s-2) * -1);
	border-radius: var(--radius);
	text-decoration: none;
	color: inherit;
}
.snippet-row:hover { background: var(--c-bg-subtle); }
.snippet-row-active,
.snippet-row-active:hover {
	background: var(--c-accent-bg);
}
.snippet-row-active .snippet-title { color: var(--c-accent); }
/* Faint is under 4.5:1 on --c-accent-bg (#556). */
.snippet-row-active .snippet-date { color: var(--c-text-dim); }

/* Doubled with .toolbar-btn so it beats that rule's display: inline-flex,
 * which comes later in this file (#412); the max-width:640px rule below
 * uses the same selector to show it again. */
.toolbar-btn.paste-back-btn { display: none; }

/* ---- Admin --------------------------------------------------------------- */

/* Sticky, because the page is long and the section list is how you move
 * around it. */
.admin-nav {
	position: sticky;
	top: 0;
	z-index: 1;
	display: flex;
	flex-wrap: wrap;
	gap: var(--s-2);
	padding: var(--s-2) 0;
	background: var(--c-bg);
}

.admin-nav a {
	padding: var(--s-1) var(--s-2);
	border: var(--border);
	border-radius: var(--radius);
	color: var(--c-text-dim);
	font-size: var(--fs-sm);
	text-decoration: none;
}

.admin-nav a:hover { color: var(--c-accent); border-color: var(--c-border-firm); }

.admin-section { margin-top: var(--s-6); }
.admin-section > p.faint { margin: 0 0 var(--s-3); }

.admin-facts {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
	gap: var(--s-3);
	margin: 0 0 var(--s-4);
}

.admin-fact {
	padding: var(--s-3);
	background: var(--c-bg-subtle);
	border-radius: var(--radius);
}

.admin-fact dt {
	color: var(--c-text-dim);
	font-size: var(--fs-xs);
	letter-spacing: 0.04em;
	text-transform: uppercase;
}

.admin-fact dd { margin: var(--s-1) 0 0; font-size: var(--fs-lg); font-weight: 600; }
.admin-fact dd.small { font-size: var(--fs-base); font-weight: 500; }

.admin-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }

.admin-table th,
.admin-table td {
	padding: var(--s-2) var(--s-3);
	border-bottom: var(--border);
	text-align: left;
	vertical-align: top;
}

.admin-table th { color: var(--c-text-dim); font-weight: 600; white-space: nowrap; }
.admin-table tr:last-child td { border-bottom: none; }

/* ---- User management (#310) ------------------------------------------- */

/* The row menus reuse .outline-menu (PATTERNS.md "No-JS <details>
 * disclosure menu"), whose toggle stays transparent until its .outline-row
 * is hovered. A table row is not an .outline-row, so show it outright. */
.usermgmt-menu .outline-menu-toggle { opacity: 1; }

.usermgmt-menu-link {
	display: block;
	padding: var(--s-1) var(--s-2);
	border-radius: var(--radius);
	color: var(--c-danger);
	text-decoration: none;
	line-height: 1.5;
}
.usermgmt-menu-link:hover { background: var(--c-danger-bg); }

.usermgmt-password code { font-size: var(--fs-base); user-select: all; }
.usermgmt-add { max-width: 22rem; }

.admin-tag {
	display: inline-block;
	padding: 0 var(--s-2);
	border-radius: var(--radius);
	font-size: var(--fs-xs);
	font-weight: 600;
	white-space: nowrap;
}

/* Loud for anything an operator should look at twice — a failing job, and
 * later a route reachable without signing in. Quiet for the normal case. */
.admin-tag-public,
.admin-tag-failed { background: var(--c-danger-bg); color: var(--c-danger); }
.admin-tag-ok,
.admin-tag-running,
.admin-tag-flag { background: var(--c-accent-bg); color: var(--c-accent); }
.admin-tag-off,
.admin-tag-default,
.admin-tag-environment,
.admin-tag-derived { background: var(--c-bg-inset); color: var(--c-text-dim); }

/* Keeps a per-row action button on one line in a narrow table column. */
.admin-table .nowrap { white-space: nowrap; }

/* The admin entry is not an app, so it sits below a separator at the very
 * bottom of the sidebar rather than in the app list. A plain <hr> rather than
 * a border on .sidebar-admin itself: that link shares .sidebar-item's
 * border-radius, which clipped a border-top into a visibly curved line at
 * both ends instead of a straight rule. margin-top: auto does the pinning —
 * it consumes .shell-nav's leftover height (see .shell-nav's flex: 1 1 auto
 * above), pushing the separator and the Admin link below it down to the
 * bottom edge together. */
.sidebar-separator {
	height: 0;
	margin: auto 0 var(--s-2);
	border: none;
	border-top: var(--border);
}

/* ---- Outline (ON Notes) ------------------------------------------------- */

/* Spec §16: a single centred column. The suite shell already carries the
 * navigation, so the outline gets no second sidebar.
 *
 * The toolbar is the one exception to the column width: its search box and
 * buttons need more than 46rem on one row. It is at least as wide as the
 * column, grows to fit its content, and wraps (see .notes-toolbar) only when
 * the page itself is too narrow — issue #392. */
.notes > :not(.notes-toolbar) { max-width: 46rem; }
.notes > .notes-toolbar {
	min-width: min(46rem, 100%);
	/* fit-content, not max-content: the shell's <main> is a flex item with
	 * min-width: auto, and a max-content child would stretch <main> past
	 * the viewport instead of wrapping. */
	width: fit-content;
	max-width: 100%;
}

/* Cross-fade the outline when zooming in/out (row dot, breadcrumb, "All
 * notes", and browser back/forward via hx-push-url) — issue-free everywhere
 * else, since only the three zoom links carry `transition:true` on their
 * hx-swap. This is the reusable pattern for Paste/Flash/Reader: name a
 * swapped region with view-transition-name, override its
 * ::view-transition-group/old/new rule for the desired effect (the group
 * pseudo carries the browser's default resize/position animation, which
 * needs the same override as old/new or it keeps animating on its own),
 * and add `transition:true` to just the hx-swap attributes that should
 * animate. */
#outline {
	view-transition-name: outline;
}
::view-transition-group(outline),
::view-transition-old(outline),
::view-transition-new(outline) {
	animation-duration: 0.18s;
	animation-timing-function: ease;
}
@media (prefers-reduced-motion: reduce) {
	::view-transition-group(outline),
	::view-transition-old(outline),
	::view-transition-new(outline) {
		animation-duration: 0.001s;
	}
}

.outline-crumbs {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--s-2);
	font-size: var(--fs-sm);
	color: var(--c-text-dim);
}

.outline-crumbs a,
.outline-crumb-current {
	max-width: 200px;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}
.outline-crumbs a { color: var(--c-text-dim); text-decoration: none; }
.outline-crumbs a:hover { color: var(--c-accent); }
.outline-crumb-current { color: var(--c-text); font-weight: 500; }

/* Shown only when the outline is zoomed into an archived node's own URL —
 * see the deviation documented on Store.Outline (store.go): rows stay
 * filtered, but the root itself is not, so this is the one visible cue that
 * what's on screen is still-editable content living under an archived
 * bullet. */
.notes-archived-banner {
	font-size: var(--fs-sm);
	color: var(--c-text-dim);
	background: var(--c-bg-subtle);
	border: var(--border);
	border-radius: var(--radius);
	padding: var(--s-2) var(--s-3);
	margin: 0 0 var(--s-3);
}
.notes-archived-banner a { color: var(--c-accent); }

/* Issue #75: every bullet at this level is done and hidden, which without
 * this reads as the outline having been emptied — spec §6's placeholder
 * below is for a genuinely empty outline, not this. */
.outline-all-done {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--s-3);
	font-size: var(--fs-sm);
	color: var(--c-text-dim);
	background: var(--c-bg-subtle);
	border: var(--border);
	border-radius: var(--radius);
	padding: var(--s-2) var(--s-3);
}
.outline-all-done p { margin: 0; }
.outline-all-done form { margin: 0; }

.outline-list { margin: 0; padding: 0; list-style: none; }

/* The nesting is the indentation. The CSP has no unsafe-inline, so a row
 * cannot carry its depth in a style attribute — see nest() in view.go — and
 * a nested list is what produces both the indent and the guide line.
 * margin-left is 3.625rem (.outline-menu 1.25rem + gap 0.25rem + .outline-chevron 1.25rem
 * + gap 0.25rem + half .outline-dot 0.625rem) so the guide line runs directly
 * under the parent bullet dot. */
.outline-item .outline-list {
	margin-left: 3.625rem;
	padding-left: 0.875rem;
	border-left: 1px solid var(--c-border);
}

.outline-row {
	position: relative;
	display: flex;
	align-items: flex-start;
	gap: var(--s-1);
	border-radius: var(--radius);
	/* Reserved width for the hover overlay (.outline-overlay), shared with
	 * the due chip's margin-right below so the two stay in sync — see the
	 * comment above .outline-due-chip. */
	--outline-overlay-w: 6rem;
}

.outline-row:hover { background: var(--c-bg); }

/* N10: drag-to-move (notes.js's "drag-to-move" section). The dragged row
 * dims rather than disappearing, so its own drop zones (e.g. dragging it
 * onto a sibling right next to where it started) stay visible and
 * clickable-looking throughout the gesture. */
.outline-dot { cursor: pointer; }
.outline-row-dragging { opacity: 0.4; }
body.outline-dragging-active { cursor: grabbing; user-select: none; }

/* Reordering: a thin line above/below the hovered row shows where the
 * bullet would land as a sibling. Nesting: highlighting the whole row
 * shows it would become that row's last child instead — the same
 * three-zone (top third / middle third / bottom third) split Workflowy
 * itself uses, named in updateDropTarget (notes.js). */
.outline-drop-before,
.outline-drop-after {
	position: relative;
}
.outline-drop-before::before,
.outline-drop-after::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	height: 2px;
	background: var(--c-accent);
	pointer-events: none;
}
.outline-drop-before::before { top: -1px; }
.outline-drop-after::after { bottom: -1px; }

.outline-drop-child {
	background: var(--c-bg-inset);
	border-radius: var(--radius);
}

.outline-main {
	display: flex;
	align-items: flex-start;
	gap: var(--s-1);
	flex: 1;
	min-width: 0;
}

.outline-chevron,
.outline-dot {
	flex: none;
	width: 1.25rem;
	height: 1.7rem;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: var(--c-text-faint);
}

button.outline-chevron {
	padding: 0;
	border: none;
	background: none;
	cursor: pointer;
}

/* A done bullet is struck through and faint everywhere its text appears — the
 * rendered overlay and the raw input both, since text-decoration on an
 * ancestor is not reliably painted through a form control in every
 * browser, unlike plain inline text. */
.outline-row-done .outline-title-rendered,
.outline-row-done .outline-note-rendered {
	color: var(--c-text-faint);
	text-decoration: line-through;
}
.outline-row-done input.outline-title,
.outline-row-done input.outline-note {
	text-decoration: line-through;
}
.outline-row-done .outline-field:has(input:focus) input.outline-title,
.outline-row-done .outline-field:has(input:focus) input.outline-note {
	color: var(--c-text-faint);
}

.outline-due-chip {
	display: inline-block;
	font-size: var(--fs-2xs);
	background: var(--c-accent-bg);
	color: var(--c-accent);
	padding: 0 0.5em;
	border-radius: var(--radius);
	text-decoration: none;
	white-space: nowrap;
}
.outline-due-chip:hover { color: var(--c-accent); }

/* Issue #78: font-weight, not just colour — WCAG 1.4.1. The chip's own
 * template markup adds a leading "!" alongside this, for the same reason:
 * a colour-blind or greyscale-display reader must still be able to tell an
 * overdue chip apart from a normal one. */
.outline-due-overdue {
	background: var(--c-danger-bg);
	color: var(--c-danger);
	font-weight: 700;
}
.outline-due-overdue:hover { color: var(--c-danger); }

.outline-children-badge {
	display: inline-block;
	font-size: var(--fs-2xs);
	background: var(--c-bg-subtle);
	color: var(--c-text-dim);
	padding: 0 0.4em;
	border-radius: var(--radius);
	white-space: nowrap;
	font-weight: 500;
}
.outline-children-all-done {
	background: var(--c-accent-bg);
	color: var(--c-accent);
}

.outline-share-chip {
	display: inline-flex;
	align-items: center;
	gap: 0.25em;
	font-size: var(--fs-xs);
	font-variant: small-caps;
	letter-spacing: 0.04em;
	background: var(--c-accent-attention-bg);
	color: var(--c-accent-attention);
	padding: 0 0.4em;
	border-radius: var(--radius);
	text-decoration: none;
	white-space: nowrap;
}
.outline-share-chip:hover {
	color: var(--c-accent);
	filter: brightness(0.95);
}
.outline-share-chip .chip-icon {
	width: 0.75em;
	height: 0.75em;
}

.outline-due-input {
	font: inherit;
	font-size: var(--fs-sm);
	color: var(--c-text-faint);
	background: none;
	border: none;
	width: 6.5rem;
}

/* flex-wrap on both: when the toolbar doesn't fit on one row it wraps onto
 * a second instead of pushing the whole page into horizontal scroll. This
 * used to apply only below phone width (issue #155), until issue #392's
 * Copy button overflowed it at 1024px with the sidebar expanded. On ON
 * Notes' own pages the toolbar may also grow past the outline column
 * before it wraps — see .notes above. */
.notes-toolbar {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: center;
	gap: var(--s-3);
	font-size: var(--fs-sm);
}

/* Groups the Due link and the show-completed toggle on the right of the
 * outline's toolbar, so N6's search box can sit alone on the left. */
.notes-toolbar-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--s-2);
}

.notes-import {
	display: inline-flex;
	align-items: center;
	gap: var(--s-1);
}

.toolbar-btn {
	display: inline-flex;
	align-items: center;
	gap: var(--s-1);
	padding: var(--s-1) var(--s-2);
	border: 1px solid transparent;
	border-radius: var(--radius);
	font-size: var(--fs-sm);
	font-weight: 400;
	color: var(--c-text-dim);
	text-decoration: none;
	background: none;
	cursor: pointer;
	line-height: 1.4;
	transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.toolbar-btn:hover {
	color: var(--c-text);
	background: var(--c-bg-subtle);
}

/* Active toggle state */
.toolbar-btn-active,
.notes-help-menu[open] .notes-help-toggle {
	color: var(--c-accent);
	background: var(--c-accent-bg);
	border-color: transparent;
	font-weight: 500;
}

.toolbar-btn-active:hover {
	color: var(--c-accent);
	background: var(--c-accent-bg);
}

/* Every toolbar and menu icon shares one size/weight, set once here rather than per
 * icon — the whole point of Task 2's redraw is that these no longer need
 * per-icon tuning. */
.toolbar-icon,
.menu-icon {
	width: 1rem;
	height: 1rem;
	flex-shrink: 0;
	stroke: currentColor;
	stroke-width: 1.5;
	stroke-linecap: round;
	stroke-linejoin: round;
	fill: none;
}

/* A thin divider between a toolbar's action groups — see outline.html's
 * .notes-toolbar-actions, which puts one between the view actions (Due,
 * Archive, Export) and the data action (Import). */
.toolbar-sep {
	width: 1px;
	align-self: stretch;
	background: var(--c-border);
	margin: 0 var(--s-1);
}

/* The Due button's due-count badge. Orange (--c-accent-attention) is used
 * nowhere else in the toolbar, so its appearance here stays meaningful.
 * :empty, not a Go-side conditional class, because the OOB-swapped copy
 * (renderOutlineFragment) and the full-page copy must look identical
 * whether or not there happen to be any due items right now. */
.toolbar-due-badge {
	display: inline-block;
	background: var(--c-accent-attention);
	color: #fff;
	border-radius: 999px;
	font-size: var(--fs-2xs);
	font-weight: 600;
	line-height: 1.5;
	padding: 0 0.45em;
	margin-left: 0.1em;
}
.toolbar-due-badge:empty {
	display: none;
}

/* Import's icon-button trigger for the hidden file input — see
 * outline.html and notes.js's initImportAutoSubmit. Shares .toolbar-btn's
 * look via that class on the <label> itself; this just makes the label
 * behave like the button it visually is. */
.notes-import-label {
	cursor: pointer;
	margin: 0;
}

/* The hidden file input (.visually-hidden clips, it doesn't remove from the
 * tab order) still gets keyboard focus, but with no box to show it. Mirror
 * that focus onto its label — the visible stand-in for the button — so a
 * keyboard user tabbing to the input sees where they landed. Scoped to the
 * file input's own :focus-visible (not the form's :focus-within) so tabbing
 * to or clicking the no-JS fallback submit button below doesn't also ring
 * this label, and so a mouse click doesn't ring it either. */
.notes-import:has(#notes-import-file:focus-visible) .notes-import-label {
	outline: var(--ring);
	outline-offset: 2px;
	border-radius: var(--radius);
}

/* No-JS fallback: outline.html keeps a real submit button in the import
 * form so choosing a file still works with JavaScript disabled (a lone
 * file input doesn't trigger implicit form submission). Once notes.js's
 * initJSClass confirms JS is running, hide that button — the icon+label
 * above already auto-submits on file choice, so a visible second "Import"
 * control would just be a duplicate. */
.js .notes-import button[type="submit"] {
	display: none;
}

.notes-due-group + .notes-due-group { margin-top: var(--s-4); }
.notes-due-group h2 {
	font-size: var(--fs-sm);
	color: var(--c-text-dim);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}
.notes-due-list { margin: 0; padding: 0; list-style: none; }
.notes-due-item {
	display: flex;
	align-items: baseline;
	gap: var(--s-2);
	padding: var(--s-1) 0;
	border-bottom: 1px solid var(--c-border);
}
.notes-due-crumbs,
.notes-archive-crumbs { font-size: var(--fs-sm); color: var(--c-text-faint); }
.notes-crumb-item {
	display: inline-block;
	max-width: 200px;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	vertical-align: bottom;
}
.notes-due-title { flex: 1; min-width: 0; color: var(--c-text); text-decoration: none; }
.notes-due-title:hover { color: var(--c-accent); }

.notes-search input[type="search"] {
	font: inherit;
	font-size: var(--fs-sm);
	color: var(--c-text);
	background: var(--c-bg-subtle);
	border: solid 1px var(--c-border);
	border-radius: var(--radius);
	padding: var(--s-1) var(--s-2);
	width: 12rem;
}

/* A match's highlight — issue #86 — uses the palette's own tinted-
 * background token (the same one active/selected chips already use, e.g.
 * .admin-tag-flag) rather than the browser's default yellow mark, so it
 * reads as "this app's own accent", not a generic <mark>. */
mark.notes-search-hit {
	background: var(--c-accent-bg);
	color: inherit;
	border-radius: 2px;
	padding: 0 0.15em;
}

.notes-search-snippet {
	margin: 0.25em 0 0;
	font-size: var(--fs-sm);
}

.notes-archive-list { margin: 0; padding: 0; list-style: none; }
.notes-archive-item {
	display: flex;
	align-items: baseline;
	gap: var(--s-2);
	padding: var(--s-1) 0;
	border-bottom: 1px solid var(--c-border);
}
.notes-archive-title { flex: 1; min-width: 0; color: var(--c-text); text-decoration: none; }
.notes-archive-title:hover { color: var(--c-accent); }
.notes-archive-restore { flex-shrink: 0; }

/* The public share page (notes/templates/shared.html) has no forms, no
 * dots and no menu, so it reuses none of .outline-list's flex layout —
 * that layout exists to align a row's menu, chevron and dot, none of
 * which this page has. Indentation and the nesting guide line reuse the
 * same margin/border-left trick as .outline-item .outline-list above,
 * simplified to one level since there is no dot to align a guide line
 * under. */
.notes-shared-list { margin: 0; padding: 0; list-style: none; }
.notes-shared-list .notes-shared-list {
	margin-left: var(--s-3);
	padding-left: var(--s-3);
	border-left: 1px solid var(--c-border);
}
.notes-shared-item { padding: var(--s-1) 0; }
.notes-shared-title { color: var(--c-text); }
.notes-shared-done .notes-shared-title { color: var(--c-text-dim); text-decoration: line-through; }
.notes-shared-due { font-size: var(--fs-sm); color: var(--c-text-dim); }
.notes-shared-note { font-size: var(--fs-sm); color: var(--c-text-dim); margin-top: 0.125rem; }

.outline-dot { text-decoration: none; }
.outline-dot:hover { color: var(--c-accent); }

/* Bullet dot save status indication */
.outline-row[data-status="dirty"] .outline-dot {
	color: var(--c-accent-attention);
}

.outline-row[data-status="saving"] .outline-dot {
	color: var(--c-accent-attention);
	animation: outline-dot-pulse 1.2s ease-in-out infinite;
}

.outline-row[data-status="saved"] .outline-dot {
	color: #16a34a;
	transition: color 0.3s ease;
}

.outline-row[data-status="error"] .outline-dot {
	color: var(--c-danger);
}

@keyframes outline-dot-pulse {
	0%, 100% {
		opacity: 1;
		transform: scale(1);
	}
	50% {
		opacity: 0.45;
		transform: scale(1.25);
	}
}

.icon-dot {
	transform-origin: center;
}

/* A collapsed bullet keeps its subtree out of the response entirely, so the
 * dot gets a halo: the bullet is not as empty as it looks. */
.icon-dot-halo {
	fill: var(--c-bg-inset);
}
.outline-dot:hover .icon-dot-halo {
	fill: var(--c-border-firm);
}

.outline-text { flex: 1; min-width: 0; }

/* The two inputs are the bullet — there is no separate display mode in this
 * chunk — so they are styled as text, not as fields.
 *
 * The element is written out because the platform's own field rule is
 * `input[type="text"], …`, whose attribute selector outweighs a bare class.
 * Without the `input` here every bullet keeps the border, the padding and the
 * body text colour of an ordinary form field, and the note stops being
 * quieter than its title. */
input.outline-title,
input.outline-note {
	width: 100%;
	padding: 0;
	border: none;
	border-radius: 0;
	background: none;
	font: inherit;
}

input.outline-title { color: var(--c-text); line-height: 1.7; }
input.outline-note { font-size: var(--fs-sm); color: var(--c-text-dim); }

/* Markdown (N4): each field is [rendered overlay, then the raw input] in
 * that order, sharing one box. The input's own text is transparent by
 * default — caret-color keeps the caret itself visible — and the overlay
 * sits on top showing the rendered form. :has(input:focus), not
 * :focus-within: a rendered link is itself focusable (see .outline-rendered
 * a below), and :focus-within would also fire when *it* is tabbed to,
 * hiding the very link just reached. */
.outline-field {
	display: block;
	width: 100%;
	position: relative;
	min-height: 1.4em;
}
.outline-field:has(input.outline-title) {
	min-height: 1.7em;
}

.outline-field input {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	color: transparent;
	caret-color: var(--c-text);
	z-index: 0;
}
.outline-field:has(input:focus) input {
	position: relative;
	color: inherit;
}
.outline-field:has(input:focus) input.outline-note { color: var(--c-text-dim); }
.outline-field:has(input:focus) .outline-rendered { display: none; }

/* The note placeholder ("note") is transparent by default, same as the
 * input's own text above — otherwise an empty note shows it under nearly
 * every bullet with a rendered overlay hiding nothing. It only needs to
 * appear while that specific bullet is being edited, so it keys off
 * .outline-text:focus-within (title or note) rather than the note input's
 * own :focus, which would hide it again the moment title editing shifted
 * focus there. */
input.outline-note::placeholder { color: transparent; }
.outline-text:focus-within input.outline-note::placeholder { color: var(--c-text-faint); }

.outline-rendered {
	display: block;
	width: 100%;
	position: relative;
	z-index: 1;
	pointer-events: none; /* let a click on blank rendered text reach the input beneath it */
	white-space: pre-wrap;
	overflow-wrap: break-word;
	word-break: break-word;
}

/* A rendered link must stay clickable even though the overlay it lives in
 * exists mainly to let other clicks fall through to the editable input. */
.outline-rendered a { pointer-events: auto; }

.outline-title-rendered { color: var(--c-text); line-height: 1.7; }
.outline-note-rendered { font-size: var(--fs-sm); color: var(--c-text-dim); }

.outline-tag {
	background: var(--c-bg-subtle);
	color: var(--c-text-dim);
	padding: 0 0.35em;
	border-radius: var(--radius);
	text-decoration: none;
	font-size: var(--fs-sm);
	padding: 0 0.5em;
}
.outline-tag:hover { color: var(--c-accent); }

/* The underline moves to the whole text column so the title and its note read
 * as one bullet while either is being edited. */
input.outline-title:focus,
input.outline-note:focus { outline: none; }
.outline-text:focus-within { box-shadow: inset 0 -1px 0 var(--c-accent); }

/* opacity, not visibility: a visibility:hidden control cannot take focus, so
 * :focus-within would never fire and these would be unreachable from the
 * keyboard. */
.outline-chevron,
.outline-menu-toggle { opacity: 0; }

.outline-row:hover .outline-chevron,
.outline-row:focus-within .outline-chevron,
.outline-row:hover .outline-menu-toggle,
.outline-row:focus-within .outline-menu-toggle,
.outline-menu[open] .outline-menu-toggle { opacity: 1; }

/* Issue #156: a touch device has no hover state, so the opacity-0 reveal
 * above leaves the chevron and the row's own "..." menu — its only path to
 * Archive, Share, Due, Delete, Move up/down, Indent/Outdent — invisible
 * with no affordance to tap in the first place. hover: none catches touch
 * without disturbing desktop's hover-reveal. */
@media (hover: none) {
	.outline-chevron,
	.outline-menu-toggle { opacity: 1; }
}

.outline-menu {
	position: relative;
	flex: none;
}

.outline-menu-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	width: 1.25rem;
	height: 1.7rem;
	color: var(--c-text-faint);
	list-style: none;
	cursor: pointer;
	border-radius: var(--radius);
}
.outline-menu-toggle::-webkit-details-marker { display: none; }
.outline-menu-toggle:hover { color: var(--c-text); }

.outline-menu-list {
	position: absolute;
	left: 0;
	top: calc(100% + var(--s-1));
	z-index: 10;
	display: flex;
	flex-direction: column;
	min-width: 16rem;
	padding: var(--s-1);
	background: var(--c-bg);
	border: var(--border);
	border-radius: var(--radius);
	box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
	font-size: var(--fs-xs);
}

.outline-menu-list button {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--s-2);
	width: 100%;
	padding: var(--s-1) var(--s-2);
	border: none;
	border-radius: var(--radius);
	background: none;
	color: var(--c-text);
	text-align: left;
	font-size: var(--fs-xs);
	line-height: 1.5;
	cursor: pointer;
}
.outline-menu-list button:hover:not([disabled]) { background: var(--c-bg-inset); }
.outline-menu-list button[disabled] { opacity: 0.4; cursor: default; }
/* Disabled items are .quiet, whose own :hover (0,2,0) would still paint
 * them (#375). */
.outline-menu-list button[disabled]:hover { background: none; color: var(--c-text); }

/* .outline-menu-list defaults to left:0 and a 16rem min-width, sized for
 * Notes' wide outline column. This modifier right-anchors it and lets it be
 * narrower, for a menu at the end of a row in a pane that can get narrow:
 * Reader's tree pane can be dragged much narrower than 16rem allows, and a
 * left-anchored menu forced the whole pane into horizontal scroll. Shared
 * rather than app-owned because platform pages (/admin/users) need it too
 * (#398). */
.outline-menu-list-end {
	left: auto;
	right: 0;
	min-width: 8rem;
}

.outline-menu-label { flex: 1; }

.outline-menu-shortcut {
	font-size: var(--fs-xs);
	color: var(--c-text-faint);
	font-family: var(--font-mono);
	margin-left: auto;
}

.outline-menu-due {
	display: flex;
	align-items: center;
	gap: var(--s-2);
	padding: var(--s-1) var(--s-2);
	font-size: var(--fs-xs);
	color: var(--c-text);
	white-space: nowrap;
}
.outline-menu-due > .outline-menu-label { flex: 1; }

/* Issue #74: the no-JS submit path for the due-date field. */
.outline-due-save {
	font-size: var(--fs-xs);
	padding: 0.15rem 0.5rem;
}

.outline-due-input {
	font: inherit;
	font-size: var(--fs-xs);
	color: var(--c-text);
	background: var(--c-bg-subtle);
	border: var(--border);
	border-radius: var(--radius);
	padding: 0.15rem 0.35rem;
	width: auto;
}
.outline-due-input:focus {
	outline: none;
	border-color: var(--c-accent);
}

/* Scoped to the list so it outranks the generic menu-item hover
 * (.outline-menu-list button:hover:not([disabled]), 0,3,1) — otherwise the
 * danger background never shows (#375). */
.outline-menu-list .outline-menu-delete:hover:not([disabled]) { background: var(--c-danger-bg); color: var(--c-danger); }

/* Shortcuts help popup panel */
.notes-help-menu {
	position: relative;
	display: inline-block;
}
.notes-help-toggle {
	cursor: pointer;
	list-style: none;
}
.notes-help-toggle::-webkit-details-marker { display: none; }

.notes-help-panel {
	position: absolute;
	right: 0;
	top: calc(100% + var(--s-1));
	z-index: 20;
	width: 18rem;
	padding: var(--s-3);
	background: var(--c-bg);
	border: var(--border);
	border-radius: var(--radius);
	box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
	font-size: var(--fs-sm);
}
.notes-help-header h3 {
	margin: 0 0 var(--s-2) 0;
	font-size: var(--fs-base);
	font-weight: 600;
	color: var(--c-text);
}
.notes-help-section {
	margin-top: var(--s-3);
}
.notes-help-section:first-of-type {
	margin-top: var(--s-1);
}
.notes-help-section h4 {
	margin: 0 0 var(--s-1) 0;
	font-size: var(--fs-xs);
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--c-text-faint);
}
.notes-help-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 0.2rem 0;
	color: var(--c-text-dim);
}
kbd {
	display: inline-block;
	padding: 0.1em 0.4em;
	font-size: var(--fs-xs);
	font-family: var(--font-mono);
	color: var(--c-text);
	background: var(--c-bg-subtle);
	border: var(--border);
	border-radius: 4px;
	box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
}

/* ---- Narrow viewports --------------------------------------------------- */

@media (max-width: 640px) {
	.shell-bar {
		flex-wrap: wrap;
		gap: var(--s-2) var(--s-4);
	}

	/* The user menu button keeps the name (it is the button's accessible
	 * name) but gives it less room. */
	.shell-user-toggle { max-width: 9rem; }

	/* The sidebar has no room to sit beside content at phone width, so it
	 * always renders in its existing icon-only collapsed state here,
	 * regardless of the data-collapsed cookie/attribute set by theme.js. */
	.app-sidebar {
		width: 3.5rem;
	}

	.sidebar-label {
		display: none;
	}

	main {
		padding: var(--s-4) var(--s-3);
		min-width: 0;
	}

	/* auto-fill + minmax(15rem, ...) can't produce a track narrower than
	 * 15rem, which overflows a phone-width viewport; force a single column
	 * instead. */
	.home-grid {
		grid-template-columns: 1fr;
	}

	/* Below this width the two panes can't sit side by side. #paste-detail-open
	 * is checked by the server whenever the detail pane has something to show
	 * (see index.html's "content" block) and replaced via hx-swap-oob whenever
	 * that changes, so this needs no JavaScript. */
	.paste-shell {
		flex-direction: column;
	}
	.paste-list-pane {
		flex: none;
		width: 100%;
	}
	.paste-detail-pane {
		flex: none;
		width: 100%;
		display: none;
	}
	#paste-detail-open:checked ~ .paste-list-pane {
		display: none;
	}
	#paste-detail-open:checked ~ .paste-detail-pane {
		display: block;
	}
	.toolbar-btn.paste-back-btn {
		display: inline-flex;
	}

	/* The accounts table can't sit in a .scroll-x wrapper: overflow-x: auto
	 * also computes overflow-y as auto, which clips each row's
	 * absolutely-positioned menu (#310). Hide the least essential columns
	 * instead so the table fits without an overflow container. */
	.usermgmt-table .usermgmt-optional {
		display: none;
	}

}

/* ---- ON Reader ---------------------------------------------------------- */

/* Three panes that scroll independently. A single page scroll is the thing
 * that makes a reader feel wrong: the tree should stay put while a long
 * article moves.
 *
 * The height has to be a definite length, not a percentage. Nothing in the
 * ancestor chain (#reader, main, .app-shell) has a definite height —
 * .app-shell only sets min-height — so height: 100% here resolved to auto:
 * the grid row grew to its tallest pane, the panes' own overflow-y: auto
 * never engaged, and the whole page scrolled with the tree sliding away under
 * a long article, which is precisely what this layout exists to prevent.
 * The offset is .app-shell's own 8rem of header/footer chrome plus main's
 * vertical padding, kept in a variable because that padding shrinks below
 * 640px. */
/* .reader-panes is now the outer vertical stack: the top toolbar, an
 * optional error/notice banner, then .reader-panes-row — the three-pane
 * flex row that used to be this element's own CSS grid. Splitting it this
 * way (rather than making .reader-panes itself the flex row) is what lets
 * the toolbar/banner sit above the resizable row without becoming grid/flex
 * items of it themselves. */
.reader-panes {
	--reader-chrome: calc(4rem + var(--s-6) * 2);
	display: flex;
	flex-direction: column;
	/* dvh tracks the visual viewport, so a mobile browser's address bar
	 * shrinking it doesn't leave overflow:hidden clipping a sliver off the
	 * bottom pane. The 100vh line is a fallback for browsers that don't know
	 * dvh — they discard the second declaration and keep this one. */
	height: calc(100vh - var(--reader-chrome));
	height: calc(100dvh - var(--reader-chrome));
	min-height: 0;
	overflow: hidden;
}

.reader-panes-row {
	display: flex;
	flex: 1 1 auto;
	min-height: 0;
	overflow: hidden;
}

.reader-tree {
	flex: 0 0 var(--reader-tree-w, 15rem);
}

.reader-list {
	flex: 0 0 var(--reader-list-w, 20rem);
}

.reader-article {
	flex: 1 1 auto;
}

/* The draggable divider between two panes. A thin hit target (8px) with a
 * narrower visible bar (2px) centered in it, the same "generous hit target,
 * modest visible line" trick a native OS window splitter uses. */
.pane-gutter {
	flex: 0 0 8px;
	position: relative;
	cursor: col-resize;
	background: transparent;
}

.pane-gutter::after {
	content: "";
	position: absolute;
	inset: 0 3px;
	border-radius: 2px;
	background: var(--c-bg);
}

.pane-gutter:hover::after,
.pane-gutter.is-dragging::after {
	background: var(--c-accent);
}

.pane-gutter:focus-visible {
	outline: var(--ring);
	outline-offset: -2px;
}

.reader-toolbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--s-2);
	padding: var(--s-2) var(--s-3);
	border-bottom: 1px solid var(--c-border);
}

.reader-filters {
	display: flex;
	gap: var(--s-1);
}

.reader-banner {
	padding: 0 var(--s-3);
	padding-top: var(--s-2);
}

.reader-banner .notice {
	margin-bottom: var(--s-2);
}

.reader-menu {
	position: relative;
}

.reader-menu-list {
	position: absolute;
	right: 0;
	top: calc(100% + var(--s-1));
	z-index: 20;
	display: flex;
	flex-direction: column;
	min-width: 12rem;
	padding: var(--s-1);
	background: var(--c-bg);
	border: var(--border);
	border-radius: var(--radius);
	box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}

.reader-menu-list[hidden] {
	display: none;
}

.reader-menu-item,
.reader-menu-item-form {
	display: block;
}

.reader-menu-item {
	display: flex;
	align-items: center;
	gap: var(--s-2);
	width: 100%;
	padding: var(--s-2);
	border: none;
	border-radius: var(--radius);
	background: none;
	color: var(--c-text);
	font-size: var(--fs-sm);
	text-decoration: none;
	text-align: left;
	cursor: pointer;
}

.reader-menu-item:hover {
	background: var(--c-bg-subtle);
}

/* The label and its "Refreshing…" status stack instead of running the menu
 * row wide enough to wrap. The shared .htmx-indicator rules only fade it in;
 * display: none on top of them keeps the hidden line from taking up room. */
.reader-refresh-text {
	display: flex;
	flex-direction: column;
}

.reader-refresh-wait {
	display: none;
	font-size: var(--fs-xs);
	color: var(--c-text-dim);
}

.reader-refresh-wait.htmx-request {
	display: block;
}

/* Sticky so the search box and "Mark all read" stay reachable while a long
 * article list scrolls underneath. .reader-list's own padding (var(--s-3))
 * would otherwise leave a gap above the toolbar once it sticks — top and
 * margin-top cancel that out, and the extra padding-top puts it back. */
.reader-list-toolbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--s-2);
	position: sticky;
	top: calc(-1 * var(--s-3));
	z-index: 1;
	margin-top: calc(-1 * var(--s-3));
	padding: calc(var(--s-3) + var(--s-2)) var(--s-3) var(--s-2);
	background: var(--c-bg);
	border-bottom: 1px solid var(--c-border);
	box-shadow: 0 10px 10px -10px rgba(100, 100, 100, 0.2);
}

/* Split button (#307): "Mark all read" and its chevron read as one control —
 * a shared outline, no gap, a hairline between the halves. */
.reader-mark-all {
	flex: none;
	display: flex;
	align-items: stretch;
}

.reader-mark-all button {
	white-space: nowrap;
}

.reader-mark-all-main {
	border-color: var(--c-border);
	border-top-right-radius: 0;
	border-bottom-right-radius: 0;
}

.reader-mark-all-toggle {
	height: 100%;
	padding: var(--s-1);
	border-color: var(--c-border);
	border-left: none;
	border-top-left-radius: 0;
	border-bottom-left-radius: 0;
	list-style: none;
}
.reader-mark-all-toggle::-webkit-details-marker { display: none; }
.reader-mark-all-menu[open] .reader-mark-all-toggle {
	color: var(--c-text);
	background: var(--c-bg-subtle);
}

.reader-hide-read-form {
	display: contents;
}

.reader-dialog {
	position: relative;
	width: min(28rem, calc(100vw - var(--s-6)));
	padding: var(--s-4);
	border: var(--border);
	border-radius: var(--radius);
	color: var(--c-text);
	background: var(--c-bg);
}

.reader-dialog::backdrop {
	background: rgba(0, 0, 0, 0.35);
}

.reader-dialog h2 {
	margin-top: 0;
}

.reader-dialog label:not(:first-of-type) {
	margin-top: var(--s-3);
}

.reader-dialog-close {
	position: absolute;
	top: var(--s-3);
	right: var(--s-3);
	padding: var(--s-1);
	border: none;
	background: none;
	color: var(--c-text-dim);
	cursor: pointer;
}
.reader-dialog-close:hover { background: var(--c-bg-inset); color: var(--c-text); }

.dialog-actions {
	display: flex;
	justify-content: flex-end;
	gap: var(--s-2);
	margin-top: var(--s-4);
}

.reader-tree,
.reader-list,
.reader-article {
	min-width: 0;
	min-height: 0;
	overflow-y: auto;
	padding: var(--s-3);
}

.reader-tree,
.reader-list {
	background: var(--c-bg);
	border-right: 1px solid var(--c-border);
}

.reader-article {
	background: var(--c-bg);
}

.reader-rows,
.reader-tree ul {
	list-style: none;
	margin: 0;
	padding: 0;
}

/* All/Starred moved out of the tree into a pair of toolbar-btn-nav buttons
 * (see .reader-tree-nav below) — same look as Notes' Due/Archive/Export row,
 * .toolbar-btn/.toolbar-btn-active/.toolbar-due-badge already do the rest. */
/* Sticky so All/Starred stay reachable while a long folder/feed tree scrolls
 * underneath — same top/margin-top cancellation as .reader-list-toolbar
 * above, to offset .reader-tree's own padding-top once this sticks. Top
 * padding matches .reader-list-toolbar's (var(--s-3) + var(--s-2)) so both
 * toolbars come out the same height. */
.reader-tree-nav {
	display: flex;
	gap: var(--s-1);
	position: sticky;
	top: calc(-1 * var(--s-3));
	z-index: 1;
	margin: calc(-1 * var(--s-3)) 0 var(--s-2);
	padding: calc(var(--s-3) + var(--s-2)) 0 var(--s-2);
	background: var(--c-bg);
	border-bottom: 1px solid var(--c-border);
	box-shadow: 0 10px 10px -10px rgba(100, 100, 100, 0.2);
}

/* Folder header row: position: relative anchors .reader-folder-menu
 * (below), and it shares the same plain-text/hover treatment as .reader-sub
 * via the selector list two rules down, since a <summary> is not an <a>. */
.reader-folder {
	position: relative;
}

.reader-folder > summary {
	border-radius: 3px;
	padding: var(--s-1) var(--s-2);
	cursor: pointer;
}

/* A count sits at the end of its row, quiet enough to scan past when it does
 * not matter. The per-row "..." menu (an .outline-menu — see PATTERNS.md)
 * comes last and stays out of the way until hovered/focused. center (not
 * baseline) keeps the favicon, count badge and menu button's boxes lined up
 * with the title text instead of each drifting off its own baseline. */
.reader-node,
.reader-sub {
	display: flex;
	align-items: center;
	gap: var(--s-2);
	border-radius: 3px;
	padding: var(--s-1) var(--s-2);
}

/* Nested under its folder's <summary>, not flush with it — the folder
   header (.reader-folder > summary, below) keeps the plain var(--s-2), so a
   feed indents one full --s-4 past that. */
.reader-folder .reader-sub {
	padding-left: calc(var(--s-2) + var(--s-4));
}

.reader-node a,
.reader-sub a {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	/* A bare <a> here (unlike .reader-row's, whose text lives in explicitly
	 * colored .title/.meta spans) has nothing else setting its color, so the
	 * browser default link blue/underline was showing through. */
	color: inherit;
	text-decoration: none;
}

/* The favicon sits before the title link. .toolbar-icon already sizes the
   "rss" fallback svg to 1rem (16px), matching the <img>'s own 16x16. */
.reader-favicon {
	flex: none;
	display: inline-flex;
	border-radius: 2px;
}

.reader-favicon[hidden] {
	display: none;
}

.reader-count {
	flex: none;
	color: var(--c-text-dim);
	font-size: var(--fs-xs);
	font-variant-numeric: tabular-nums;
}

.reader-node:hover,
.reader-sub:hover,
.reader-folder > summary:hover {
	background: var(--c-bg-subtle);
}

.reader-node.is-active,
.reader-sub.is-active {
	background: var(--c-accent-bg);
}

/* The per-row menu toggle stays invisible until its row is hovered/focused,
 * exactly like Notes' outline rows (.outline-row) — reader's own rows just
 * are not .outline-row, so that existing rule doesn't reach them. Touch
 * devices have no hover to reveal it with, so it stays visible there. */
.reader-node .outline-menu-toggle,
.reader-sub .outline-menu-toggle,
.reader-folder .outline-menu-toggle {
	opacity: 0;
}

.reader-node:hover .outline-menu-toggle,
.reader-node:focus-within .outline-menu-toggle,
.reader-sub:hover .outline-menu-toggle,
.reader-sub:focus-within .outline-menu-toggle,
.reader-folder > summary:hover ~ .reader-folder-menu .outline-menu-toggle,
.reader-folder > summary:focus-visible ~ .reader-folder-menu .outline-menu-toggle,
.reader-folder-menu:hover .outline-menu-toggle,
.reader-folder-menu:focus-within .outline-menu-toggle,
.reader-folder-menu[open] .outline-menu-toggle {
	opacity: 1;
}

@media (hover: none) {
	.reader-node .outline-menu-toggle,
	.reader-sub .outline-menu-toggle,
	.reader-folder .outline-menu-toggle {
		opacity: 1;
	}
}

/* The folder row's own menu is a sibling of <summary>, not nested inside it
 * (nesting it would make the menu's own click also toggle the folder open/
 * closed, since <summary> owns the native disclosure). Positioned over the
 * folder header's line instead, anchored by .reader-folder's own
 * position: relative above. */
.reader-folder-menu {
	position: absolute;
	top: var(--s-1);
	right: var(--s-2);
}

/* A failing feed is one of the few things that genuinely needs attention, so
 * it gets the orange that nothing else in this app is allowed to use. */
.reader-failing {
	color: var(--c-accent-attention);
}

.reader-list-head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--s-2) var(--s-3);
	margin-bottom: var(--s-3);
}

.reader-list-head h2 {
	flex: 1 1 auto;
	margin: 0;
	font-size: var(--fs-sm);
	color: var(--c-text-dim);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

/* Moved into .reader-list-toolbar, alongside Mark all read — narrowed to
 * Notes' fixed-width search box (.notes-search) instead of spanning the full
 * pane width it used when it was its own row under the (now hidden) title. */
/* flex: 1 1 auto plus min-width: 0 (not the flex default auto, which floors
 * at content width) is what lets this shrink on a narrow toolbar rather than
 * force .reader-mark-all's own label to wrap instead — confirmed at 375px:
 * without min-width: 0 here, "Mark all read" wrapped onto three lines
 * because the fixed-width search box refused to give up any space. */
.reader-search {
	flex: 1 1 auto;
	min-width: 0;
	margin: 0;
}

.reader-search input {
	font: inherit;
	font-size: var(--fs-sm);
	/* Matches .toolbar-btn's line-height (1.4) so the search box and the
	   "Mark all read" button next to it come out the same height — inputs
	   don't inherit the UA default line-height the way buttons/links do. */
	line-height: 1.4;
	color: var(--c-text);
	background: var(--c-bg-subtle);
	border: solid 1px var(--c-border);
	border-radius: var(--radius);
	padding: var(--s-1) var(--s-2);
	width: 100%;
	max-width: 12rem;
	box-sizing: border-box;
}

.reader-row a {
	display: block;
	padding: var(--s-2);
	border-radius: 3px;
	text-decoration: none;
}

.reader-row a:hover {
	background: var(--c-bg-subtle);
}

.reader-row.is-active a,
.reader-row.is-active a:hover {
	background: var(--c-accent-bg);
}

.reader-row .title {
	display: block;
	color: var(--c-text);
}

.reader-row .meta {
	display: block;
	color: var(--c-text-faint);
	font-size: var(--fs-xs);
}
/* Faint is under 4.5:1 on --c-accent-bg (#556). */
.reader-row.is-active .meta { color: var(--c-text-dim); }

/* Read rows dim. That contrast *is* the unread indicator — do not also add a
 * dot, or every row grows a second thing to read. */
.reader-row.is-read .title {
	color: var(--c-text-dim);
	font-weight: 400;
}

.reader-row .title {
	font-weight: 600;
}

.reader-star {
	color: var(--c-accent);
}

/* The narrow-viewport back control, hidden until a breakpoint actually
 * collapses a pane away — the same shape as .paste-back-btn. It is a <label>
 * for the drill-down checkbox, so clicking it unchecks that box and the pane
 * behind reappears with no script and no round trip. The margin reset is
 * because the global label rule gives every label bottom margin. */
.reader-back-btn {
	display: none;
	margin-bottom: 0;
}

/* Above 900px nothing reacts to #reader-list-open/#reader-article-open (see
 * the max-width:900px and max-width:640px rules below), so a sighted keyboard
 * user tabbing through would land on two controls that visibly do nothing.
 * Hiding them here removes that dead stop without touching their focusable,
 * unhidden state below the breakpoint (issue #221). */
@media (min-width: 901px) {
	.reader-pane-toggle {
		display: none;
	}
}

/* The checkbox itself is off-screen (.visually-hidden), so its own focus
 * ring would be invisible; this puts the ring on the back button its state
 * controls instead, the same sibling-combinator shape as
 * .reader-folder > summary:focus-visible below. */
#reader-list-open:focus-visible ~ .reader-list .reader-back-btn,
#reader-article-open:focus-visible ~ .reader-article .reader-back-btn {
	outline: var(--ring);
}

.reader-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--s-2);
	margin-bottom: var(--s-4);
}

.reader-actions form {
	margin: 0;
}

/* Prose measure. 38rem is roughly 70 characters at the body size, which is
 * where long-form reading stops being work. */
.reader-body {
	max-width: 38rem;
	font-family: var(--font-body);
	line-height: 1.6;
}

.reader-body img {
	max-width: 100%;
	height: auto;
}

.reader-body pre {
	overflow-x: auto;
}

/* Two panes: the tree stays, and the article takes the list's column once
 * something is open. */
@media (max-width: 900px) {
	.pane-gutter {
		display: none;
	}
	.reader-tree {
		flex: 0 0 12rem;
	}
	.reader-list {
		flex: 1 1 auto;
	}
	.reader-article {
		display: none;
	}
	#reader-article-open:checked ~ .reader-list {
		display: none;
	}
	#reader-article-open:checked ~ .reader-article {
		display: block;
	}
	/* Only reachable — and only needed — once the list is covered up. */
	#reader-article-open:checked ~ .reader-article .reader-back-btn {
		display: inline-flex;
	}
}

/* One pane. Drilling down: tree, then list, then article.
 *
 * The collapse itself needs no JavaScript: the checkboxes are plain sibling
 * inputs, the server sets them, and the back labels inside each pane uncheck
 * them again. What does need JavaScript is htmx's out-of-band swapping — the
 * unread counts, read/star state and mark-all-read updates all arrive that
 * way — so this is a layout that degrades without script, not an app that
 * runs without it. Same mechanism as #paste-detail-open above. */
@media (max-width: 640px) {
	.reader-panes {
		/* main's padding drops to --s-4 at this width. */
		--reader-chrome: calc(8rem + var(--s-4) * 2);
	}
	.reader-tree {
		flex: 1 1 100%;
	}
	/* One pane means the list is hidden until it is drilled into. Without
	 * this the "back to the feed tree" state showed the tree with the list
	 * still stacked under it. */
	.reader-list {
		display: none;
	}
	#reader-list-open:checked ~ .reader-tree {
		display: none;
	}
	#reader-list-open:checked ~ .reader-list {
		display: block;
	}
	#reader-list-open:checked ~ .reader-list .reader-back-btn {
		display: inline-flex;
	}
	#reader-article-open:checked ~ .reader-list {
		display: none;
	}
}

/* Reading stats page. */
.reader-stats-head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--s-3);
}

.reader-chart { margin: 0 0 var(--s-5); }
.reader-chart figcaption {
	font-size: var(--fs-sm);
	color: var(--c-text-dim);
	margin-bottom: var(--s-2);
}
.reader-chart svg {
	width: 100%;
	height: 120px;
	display: block;
	overflow: visible;
}
/* One series, one hue. --c-accent-attention is reserved for things needing
 * attention and is deliberately not used here. */
.reader-chart rect { fill: var(--c-accent); }
.reader-chart rect:hover { fill: var(--c-text); }
.reader-line { fill: none; stroke: var(--c-accent); stroke-width: 2; }

.reader-stat-tiles {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
	gap: var(--s-3);
	margin-bottom: var(--s-5);
}
.reader-stat-tile { background: var(--c-bg-subtle); border-radius: 4px; padding: var(--s-3); }
.reader-stat-tile .value { font-size: var(--fs-xl); }
.reader-stat-tile .label { color: var(--c-text-dim); font-size: var(--fs-xs); }

.reader-table-scroll { overflow-x: auto; }
.reader-stats-table {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--fs-sm);
}
.reader-stats-table th,
.reader-stats-table td {
	text-align: left;
	padding: var(--s-2) var(--s-3);
	border-bottom: 1px solid var(--c-border);
	white-space: nowrap;
}

/* The ratio bar is an inline SVG, not a styled span: its width varies per
 * row, and a style="width:37%" attribute is exactly what this app's CSP
 * forbids — it would be dropped silently and every bar would look full. */
.reader-ratio { width: 4rem; height: 4px; vertical-align: middle; }
.reader-ratio .track { fill: var(--c-bg-inset); }
.reader-ratio .fill { fill: var(--c-accent); }

/* Flash stats page — same layout as the reading stats page above, under its
 * own prefix since apps don't share CSS classes any more than they share
 * Go code. */
.flash-chart { margin: 0 0 var(--s-5); }
.flash-chart figcaption {
	font-size: var(--fs-sm);
	color: var(--c-text-dim);
	margin-bottom: var(--s-2);
}
.flash-chart svg {
	width: 100%;
	height: 120px;
	display: block;
	overflow: visible;
}
.flash-chart rect { fill: var(--c-accent); }
.flash-chart rect:hover { fill: var(--c-text); }

.flash-stat-tiles {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
	gap: var(--s-3);
	margin-bottom: var(--s-5);
}
.flash-stat-tile { background: var(--c-bg-subtle); border-radius: 4px; padding: var(--s-3); }
.flash-stat-tile .value { font-size: var(--fs-xl); }
.flash-stat-tile .label { color: var(--c-text-dim); font-size: var(--fs-xs); }

.flash-table-scroll { overflow-x: auto; }
.flash-stats-table {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--fs-sm);
}
.flash-stats-table th,
.flash-stats-table td {
	text-align: left;
	padding: var(--s-2) var(--s-3);
	border-bottom: 1px solid var(--c-border);
	white-space: nowrap;
}
.flash-snoozed-marker { color: var(--c-text-dim); font-size: var(--fs-xs); }

/* ---- Colour swatches (shared: ON Flash decks, ON Focus timers) ----------
 * The CSP forbids style="", so a user-picked colour arrives as a
 * swatch-c-<name> class that sets --swatch/--swatch-soft for everything
 * inside it. Apps keep their own copy of the name list (they never import
 * each other); these classes are the shared part.
 * --swatch-line is the glyph colour for app icons (home badge, sidebar):
 * at least 3:1 on --swatch-soft and on the page/sidebar surfaces in both
 * themes. See docs/superpowers/specs/2026-10-13-app-identity-colours-design.md §6. */

.swatch-c-teal   { --swatch: #1D9E75; --swatch-soft: #E1F5EE; --swatch-line: #188D6A; }
.swatch-c-blue   { --swatch: #378ADD; --swatch-soft: #E6F1FB; --swatch-line: #2C7BC9; }
.swatch-c-purple { --swatch: #7F77DD; --swatch-soft: #EEEDFE; --swatch-line: #7067D0; }
.swatch-c-pink   { --swatch: #D4537E; --swatch-soft: #FBEAF0; --swatch-line: #BF4870; }
.swatch-c-coral  { --swatch: #D85A30; --swatch-soft: #FAECE7; --swatch-line: #C25029; }
.swatch-c-amber  { --swatch: #BA7517; --swatch-soft: #FAEEDA; --swatch-line: #A76813; }
.swatch-c-green  { --swatch: #639922; --swatch-soft: #EAF3DE; --swatch-line: #558A1C; }
.swatch-c-gray   { --swatch: #888780; --swatch-soft: #F1EFE8; --swatch-line: #7A7973; }
:root[data-theme="dark"] .swatch-c-teal   { --swatch-soft: #085041; --swatch-line: #5DCAA5; }
:root[data-theme="dark"] .swatch-c-blue   { --swatch-soft: #0C447C; --swatch-line: #85B7EB; }
:root[data-theme="dark"] .swatch-c-purple { --swatch-soft: #3C3489; --swatch-line: #AFA9EC; }
:root[data-theme="dark"] .swatch-c-pink   { --swatch-soft: #72243E; --swatch-line: #ED93B1; }
:root[data-theme="dark"] .swatch-c-coral  { --swatch-soft: #712B13; --swatch-line: #F0997B; }
:root[data-theme="dark"] .swatch-c-amber  { --swatch-soft: #633806; --swatch-line: #EF9F27; }
:root[data-theme="dark"] .swatch-c-green  { --swatch-soft: #27500A; --swatch-line: #97C459; }
:root[data-theme="dark"] .swatch-c-gray   { --swatch-soft: #444441; --swatch-line: #B4B2A9; }

/* ---- ON Flash: home screen (UI overhaul U1) ------------------------------
 * Spec: docs/superpowers/specs/2026-09-23-on-flash-ui-overhaul-design.md. */

/* A deck drawn as three stacked cards. */
.flash-stack {
	position: relative;
	display: inline-block;
	flex: none;
}
.flash-stack-sm { width: 1.9rem; height: 2.3rem; }
.flash-stack-lg { width: 5.5rem; height: 6.5rem; }
.flash-stack > span {
	position: absolute;
	inset: 0;
	border-radius: 5px;
	border: 1px solid color-mix(in srgb, var(--swatch, #888780) 60%, #000);
}
.flash-stack-lg > span { border-radius: 10px; }
.flash-stack > span:nth-child(1) {
	background: color-mix(in srgb, var(--swatch, #888780) 45%, var(--c-bg));
	transform: rotate(-8deg);
}
.flash-stack > span:nth-child(2) {
	background: color-mix(in srgb, var(--swatch, #888780) 70%, var(--c-bg));
	transform: rotate(4deg);
}
.flash-stack > span:nth-child(3) { background: var(--swatch, #888780); }

.flash-due-badge {
	display: inline-block;
	min-width: 1.5rem;
	padding: 0 var(--s-2);
	border-radius: 999px;
	background: var(--c-accent-attention-bg);
	color: var(--c-accent-attention);
	font-size: var(--fs-xs);
	font-weight: 600;
	text-align: center;
	line-height: 1.5;
}

/* Layout: toolbar above a two-pane split, like ON Paste. */
.flash-home-toolbar { margin-bottom: var(--s-4); justify-content: flex-start; }

.flash-shell {
	display: flex;
	align-items: flex-start;
	gap: var(--s-5);
}
.flash-list-pane { flex: 0 0 17rem; min-width: 0; }
.flash-detail-pane { flex: 1; min-width: 0; }

.flash-list-title {
	margin: 0 0 var(--s-2);
	font-size: var(--fs-sm);
	font-weight: 500;
	color: var(--c-text-dim);
}

/* Deck list rows. Tall enough to be a comfortable touch target (44px+). */
.deck-list { margin: 0; padding: 0; list-style: none; }
.deck-row {
	display: flex;
	align-items: center;
	gap: var(--s-3);
	min-height: 3.25rem;
	padding: var(--s-2) var(--s-3);
	margin-bottom: var(--s-1);
	border-radius: 8px;
	color: inherit;
	text-decoration: none;
}
.deck-row:hover { background: var(--c-bg-subtle); }
.deck-row-active,
.deck-row-active:hover {
	background: var(--c-bg-subtle);
	box-shadow: inset 0 0 0 2px var(--c-accent);
}
.deck-row-snoozed { opacity: 0.6; }
.deck-row-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.deck-row-name {
	font-weight: 500;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.deck-row-status { font-size: var(--fs-xs); color: var(--c-text-dim); }

/* The narrow-screen back button only exists below the breakpoint. */
.flash-back-btn { display: none; }

.flash-deck-toolbar { margin-bottom: var(--s-4); }
.flash-deck-toolbar .notes-toolbar-actions { margin-left: auto; }
.flash-deck-toolbar form { margin: 0; }

.flash-deck-hero {
	display: flex;
	align-items: center;
	gap: var(--s-5);
	margin-bottom: var(--s-5);
}
.flash-deck-hero-text { min-width: 0; }
.flash-deck-hero-text h1 { margin: 0 0 var(--s-1); }
.flash-deck-hero-text p { margin: 0; }

/* The one primary action on the deck pane. */
.flash-review-cta {
	display: inline-flex;
	align-items: center;
	gap: var(--s-2);
	margin-top: var(--s-4);
	padding: var(--s-3) var(--s-5);
	min-height: 2.75rem;
	border-radius: 10px;
	background: var(--c-accent-attention);
	color: #fff;
	font-size: var(--fs-lg);
	font-weight: 600;
	text-decoration: none;
}
.flash-review-cta:hover { filter: brightness(1.08); }
/* Dark mode's lighter orange takes dark text, as .primary does (#556). */
:root[data-theme="dark"] .toolbar-due-badge,
:root[data-theme="dark"] .flash-review-cta { color: #10141a; }
.flash-review-cta:focus-visible { outline: var(--ring); outline-offset: 2px; }

.flash-all-done {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--s-2);
	margin-top: var(--s-4);
	padding: var(--s-3) var(--s-4);
	border-radius: 10px;
	background: var(--swatch-soft, var(--c-bg-subtle));
}

/* Big, friendly buttons for the welcome and empty states. */
.flash-big-btn {
	display: inline-flex;
	align-items: center;
	gap: var(--s-2);
	min-height: 2.75rem;
	padding: var(--s-2) var(--s-4);
	border-radius: 10px;
	border: 1px solid var(--c-border-firm);
	background: var(--c-bg);
	color: var(--c-text);
	font-weight: 500;
	text-decoration: none;
}
.flash-big-btn:hover { background: var(--c-bg-subtle); }
.flash-big-btn-primary {
	background: var(--c-accent);
	border-color: var(--c-accent);
	color: #fff;
}
.flash-big-btn-primary:hover { background: var(--c-accent); filter: brightness(1.1); }
:root[data-theme="dark"] .flash-big-btn-primary { color: #10141a; }

.flash-welcome {
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	gap: var(--s-3);
	padding: var(--s-6) var(--s-4);
}
.flash-welcome h1 { margin: var(--s-3) 0 0; }
.flash-welcome p { max-width: 28rem; margin: 0; }
.flash-welcome-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-3); margin-top: var(--s-2); }

.flash-pane-hint { padding: var(--s-4) 0; }

.flash-deck-tiles { margin-bottom: var(--s-5); }

.flash-deck-section { margin-top: var(--s-5); }
.flash-section-title {
	margin: 0 0 var(--s-2);
	font-size: var(--fs-base);
	font-weight: 500;
}
.flash-share-list { margin: var(--s-2) 0 0; padding: 0; list-style: none; }
.flash-share-list li { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s-2); padding: var(--s-1) 0; }
.flash-share-list form { margin: 0; }

/* Colour swatches: the radio is visually hidden, its label is the swatch. */
.flash-color-field { border: none; padding: 0; margin: 0 0 var(--s-4); }
.flash-color-field legend { margin-bottom: var(--s-2); font-size: var(--fs-sm); color: var(--c-text-dim); padding: 0; }
.flash-swatches { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.flash-swatch {
	width: 2.75rem;
	height: 2.75rem;
	margin: 0;
	border-radius: 50%;
	background: var(--swatch);
	border: 3px solid var(--c-bg);
	box-shadow: 0 0 0 1px var(--c-border-firm);
	cursor: pointer;
}
.flash-swatch-input:checked + .flash-swatch { box-shadow: 0 0 0 3px var(--c-text); }
.flash-swatch-input:focus-visible + .flash-swatch { outline: var(--ring); outline-offset: 3px; }

.flash-deck-form { max-width: var(--measure); }
/* Wraps the edit form and the Take-a-break section (decks.html) so the
   section lines up with the form instead of stretching wider (#345). The
   form's own max-width above still applies inside, so this isn't a double
   constraint — both settle on the same width. */
.flash-deck-edit { max-width: var(--measure); }

/* Below 900px the list and the pane can't sit side by side: show one at a
 * time, driven by #flash-detail-open (checked by the server whenever the
 * pane has something to show). Same mechanism as #paste-detail-open. */
@media (max-width: 900px) {
	.flash-shell { flex-direction: column; }
	.flash-list-pane,
	.flash-detail-pane { flex: none; width: 100%; }
	.flash-detail-pane { display: none; }
	#flash-detail-open:checked ~ .flash-list-pane { display: none; }
	#flash-detail-open:checked ~ .flash-detail-pane { display: block; }
	.flash-back-btn { display: inline-flex; }
	.flash-deck-hero { flex-direction: column; align-items: flex-start; }
}

/* ---- ON Flash: cards (UI overhaul U2) ---------------------------------- */

:root { --flash-card-bg: #ffffff; }
:root[data-theme="dark"] { --flash-card-bg: var(--c-bg-inset); }

.flash-pane-title { margin: 0 0 var(--s-3); font-size: var(--fs-xl); }

/* Search and pills above the grid. */
.flash-card-filter { margin-bottom: var(--s-3); }
.flash-card-filter input[type="search"] {
	width: 100%;
	max-width: 24rem;
	padding: var(--s-2) var(--s-3);
	border: 1px solid var(--c-border-firm);
	border-radius: var(--radius);
	background: var(--c-bg);
	color: var(--c-text);
	font: inherit;
}
.flash-tag-pills { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); margin-bottom: var(--s-3); }
.flash-tag-pills:empty { display: none; }

.flash-pill {
	display: inline-flex;
	align-items: center;
	min-height: 2rem;
	padding: 0 var(--s-3);
	border-radius: 999px;
	border: 1px solid transparent;
	background: var(--c-bg-subtle);
	color: var(--c-text-dim);
	font-size: var(--fs-sm);
	text-decoration: none;
}
a.flash-pill:hover { color: var(--c-text); border-color: var(--c-border-firm); }
.flash-pill-active {
	background: var(--c-accent-bg);
	color: var(--c-accent);
	border-color: var(--c-accent);
}
.flash-all-decks-link { font-size: var(--fs-sm); margin-left: var(--s-2); }

/* The grid of mini cards. */
.flash-card-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
	gap: var(--s-3);
	margin: 0;
	padding: 0;
	list-style: none;
}
.flash-mini-card {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--s-1);
	min-height: 5.5rem;
	padding: var(--s-4) var(--s-2) var(--s-3);
	overflow: hidden;
	border-radius: 10px;
	border: 1px solid var(--c-border);
	background: var(--flash-card-bg);
	color: var(--c-text);
	text-align: center;
	text-decoration: none;
	font-size: var(--fs-sm);
	transition: transform 0.12s, box-shadow 0.12s;
}
.flash-mini-card::before {
	content: "";
	position: absolute;
	top: 0; left: 0; right: 0;
	height: 5px;
	background: var(--swatch, var(--c-border-firm));
}
/* Only the linked case (cards grid, tag page) is clickable — the gift
   preview's sample cards render as plain <span>s and must not look
   interactive (#344). */
a.flash-mini-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgb(0 0 0 / 0.08); }
a.flash-mini-card:focus-visible { outline: var(--ring); outline-offset: 2px; }
.flash-mini-text {
	display: -webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
	white-space: pre-line;
}
.flash-mini-status,
.flash-mini-deck {
	position: absolute;
	bottom: var(--s-1);
	font-size: var(--fs-2xs);
	color: var(--c-text-dim); /* faint is under 4.5:1 on the card's inset background (#556) */
}
.flash-mini-status { right: var(--s-2); }
.flash-mini-deck { left: var(--s-2); }
.flash-mini-new {
	border-style: dashed;
	border-color: var(--c-border-firm);
	background: transparent;
	color: var(--c-text-dim);
}
.flash-mini-new::before { display: none; }
.flash-grid-empty { grid-column: 1 / -1; }

/* The large, flippable card. The checkbox before it holds the state. */
.flash-viewer {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--s-3);
	margin: var(--s-4) 0 var(--s-2);
}
.flash-viewer-card { flex: 0 1 26rem; min-width: 0; }
.flash-nav-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: none;
	width: 2.75rem;
	height: 2.75rem;
	border-radius: 50%;
	border: 1px solid var(--c-border-firm);
	color: var(--c-text-dim);
	font-size: 1.5rem;
	text-decoration: none;
}
.flash-nav-btn:hover { background: var(--c-bg-subtle); color: var(--c-text); }
.flash-nav-btn-empty { visibility: hidden; }
.flash-viewer-keys { text-align: center; }

.flash-card {
	display: block;
	margin: 0;
	perspective: 1000px;
	cursor: pointer;
	color: var(--c-text);
	font-size: var(--fs-base);
}
.flash-card-inner {
	position: relative;
	display: block;
	min-height: 15rem;
	transition: transform 0.55s cubic-bezier(0.3, 0.7, 0.3, 1.2);
	transform-style: preserve-3d;
}
.flash-flip:checked + .flash-card .flash-card-inner { transform: rotateY(180deg); }
.flash-card-face {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--s-3);
	padding: var(--s-6) var(--s-4) var(--s-4);
	overflow: auto;
	border-radius: 14px;
	border: 1px solid var(--c-border);
	background: var(--flash-card-bg);
	text-align: center;
	backface-visibility: hidden;
	-webkit-backface-visibility: hidden;
}
.flash-card-face::before {
	content: "";
	position: absolute;
	top: 0; left: 0; right: 0;
	height: 8px;
	background: var(--swatch, var(--c-border-firm));
}
.flash-card-back { transform: rotateY(180deg); }
.flash-card-text { font-size: var(--fs-xl); font-weight: 500; white-space: pre-line; overflow-wrap: anywhere; }
.flash-card-notes { color: var(--c-text-dim); font-size: var(--fs-sm); white-space: pre-line; }
.flash-card-hint { color: var(--c-text-faint); font-size: var(--fs-xs); }
.flash-card-image { max-width: 100%; max-height: 8rem; border-radius: var(--radius); }
.flash-card-tags { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-1); }
.flash-flip:focus-visible + .flash-card .flash-card-face { outline: var(--ring); outline-offset: 3px; }

/* Links and audio sit outside the <label> so clicking them can't also flip
 * the card; they appear once it has been flipped. */
.flash-card-extras { display: none; margin-top: var(--s-3); text-align: center; }
.flash-flip:checked ~ .flash-card-extras { display: block; }
.flash-tag-links { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-2); margin: var(--s-2) 0 0; }

/* Cloze blanks and fills. */
.flash-blank {
	display: inline-block;
	min-width: 4em;
	border-bottom: 3px solid var(--swatch, var(--c-accent));
	color: var(--c-text-faint);
	font-size: 0.7em;
	vertical-align: baseline;
}
.flash-blank-hint {
	font-size: 0.6em;
	font-style: italic;
	color: var(--c-text-faint);
}
.flash-fill {
	padding: 0 0.2em;
	border-radius: 4px;
	background: var(--swatch-soft, var(--c-accent-bg));
	color: inherit;
}

@media (prefers-reduced-motion: reduce) {
	.flash-card-inner { transition: none; }
	.flash-flip:checked + .flash-card .flash-card-inner { transform: none; }
	.flash-card-back { transform: none; opacity: 0; transition: opacity 0.2s; }
	.flash-card-front { transition: opacity 0.2s; }
	.flash-flip:checked + .flash-card .flash-card-back { opacity: 1; }
	.flash-flip:checked + .flash-card .flash-card-front { opacity: 0; }
	.flash-mini-card { transition: none; }
	a.flash-mini-card:hover { transform: none; }
}

/* ---- ON Flash: card editor (UI overhaul U3) ----------------------------- */

.flash-editor { max-width: 52rem; }
.flash-editor h1 { margin: 0 0 var(--s-3); }

/* Type toggle: two radios drawn as one segmented control. */
.flash-type-toggle {
	display: inline-flex;
	margin: 0 0 var(--s-4);
	padding: 0;
	border: 1px solid var(--c-border-firm);
	border-radius: 8px;
	overflow: hidden;
}
.flash-type-toggle label {
	margin: 0;
	padding: var(--s-2) var(--s-4);
	min-height: 2.75rem;
	display: inline-flex;
	align-items: center;
	color: var(--c-text);
	font-size: var(--fs-sm);
	cursor: pointer;
}
.flash-type-toggle input:checked + label { background: var(--c-accent); color: #fff; }
:root[data-theme="dark"] .flash-type-toggle input:checked + label { color: #10141a; }
.flash-type-toggle input:focus-visible + label { outline: var(--ring); outline-offset: -3px; }

/* The two faces, drawn as cards. */
.flash-editor-faces {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--s-4);
	margin-bottom: var(--s-3);
}
.flash-editor-face {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: var(--s-2);
	padding: var(--s-5) var(--s-4) var(--s-4);
	border-radius: 14px;
	border: 1px solid var(--c-border);
	background: var(--flash-card-bg);
	overflow: hidden;
}
.flash-editor-face::before {
	content: "";
	position: absolute;
	top: 0; left: 0; right: 0;
	height: 8px;
	background: var(--swatch, var(--c-border-firm));
}
.flash-editor-back { background: color-mix(in srgb, var(--flash-card-bg) 92%, var(--c-bg-subtle)); }
.flash-editor-face label { margin: 0; font-size: var(--fs-xs); color: var(--c-text-faint); }
.flash-editor-face textarea {
	flex: 1;
	min-height: 7rem;
	border: none;
	background: transparent;
	font-family: var(--font-ui);
	font-size: var(--fs-lg);
	text-align: center;
	resize: vertical;
}
.flash-editor-face textarea:focus-visible { outline: var(--ring); border-radius: var(--radius); }
.flash-make-blank { align-self: center; }
.flash-make-blank[hidden] { display: none; }

/* Question and answer vs fill in the blank, with no JS: :has() reads the
 * checked radio. */
.flash-when-cloze { display: none; }
.flash-editor:has(input[name="card_type"][value="cloze"]:checked) .flash-when-cloze { display: inline; }
.flash-editor:has(input[name="card_type"][value="cloze"]:checked) p.flash-when-cloze { display: block; }
.flash-editor:has(input[name="card_type"][value="cloze"]:checked) .flash-when-basic { display: none; }
.flash-editor:has(input[name="card_type"][value="cloze"]:checked) .flash-editor-back { display: none; }
.flash-editor:has(input[name="card_type"][value="cloze"]:checked) .flash-editor-faces { grid-template-columns: minmax(0, 1fr); }
.flash-editor:not(:has(input[name="card_type"][value="cloze"]:checked)) .flash-make-blank { display: none; }

/* Tag pills editor (flash.js). */
.flash-tag-editor {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--s-1);
	padding: var(--s-1) var(--s-2);
	border: 1px solid var(--c-border-firm);
	border-radius: var(--radius);
	background: var(--c-bg);
}
.flash-tag-editor:focus-within { outline: var(--ring); }
.flash-tag-pill { gap: var(--s-1); background: var(--c-accent-bg); color: var(--c-accent); }
.flash-tag-remove {
	padding: 0 var(--s-1);
	border: none;
	background: none;
	color: inherit;
	font-size: var(--fs-base);
	line-height: 1;
	cursor: pointer;
}
/* The × turns red, no background block inside the teal pill (#375). */
.flash-tag-remove:hover { color: var(--c-danger); }
input.flash-tag-entry {
	flex: 1;
	min-width: 8rem;
	width: auto;
	padding: var(--s-1);
	border: none;
	background: transparent;
}
input.flash-tag-entry:focus { outline: none; }

/* Media drop zones. */
.flash-drops {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--s-4);
	margin-bottom: var(--s-4);
}
.flash-drop-field { display: flex; flex-direction: column; gap: var(--s-2); }
.flash-drop {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--s-2);
	min-height: 5.5rem;
	margin: 0;
	padding: var(--s-3);
	border: 2px dashed var(--c-border-firm);
	border-radius: 10px;
	color: var(--c-text-dim);
	text-align: center;
	cursor: pointer;
}
.flash-drop:hover,
.flash-drop-over { border-color: var(--c-accent); background: var(--c-accent-bg); }
.flash-drop:focus-within { outline: var(--ring); outline-offset: 2px; }
.flash-drop-chosen { border-style: solid; border-color: var(--c-accent); }
.flash-drop-file { font-weight: 500; color: var(--c-text); }
.flash-drop-file:empty { display: none; }
.flash-drop-preview { max-height: 5rem; max-width: 100%; border-radius: var(--radius); }
.flash-drop-remove { display: flex; align-items: center; gap: var(--s-1); margin: 0; }
.flash-drop-field audio { width: 100%; }

.flash-editor-actions { flex-wrap: wrap; }

@media (max-width: 640px) {
	.flash-editor-faces,
	.flash-drops { grid-template-columns: minmax(0, 1fr); }
}

/* ---- ON Flash: review (UI overhaul U4) --------------------------------- */

:root {
	--flash-grade-again: #E24B4A; --flash-grade-again-bg: #FCEBEB; --flash-grade-again-text: #791F1F;
	--flash-grade-hard:  #EF9F27; --flash-grade-hard-bg:  #FAEEDA; --flash-grade-hard-text:  #633806;
	--flash-grade-good:  #1D9E75; --flash-grade-good-bg:  #E1F5EE; --flash-grade-good-text:  #085041;
	--flash-grade-easy:  #378ADD; --flash-grade-easy-bg:  #E6F1FB; --flash-grade-easy-text:  #0C447C;
}
:root[data-theme="dark"] {
	--flash-grade-again-bg: #501313; --flash-grade-again-text: #F7C1C1;
	--flash-grade-hard-bg:  #412402; --flash-grade-hard-text:  #FAC775;
	--flash-grade-good-bg:  #04342C; --flash-grade-good-text:  #9FE1CB;
	--flash-grade-easy-bg:  #042C53; --flash-grade-easy-text:  #B5D4F4;
	--flash-grade-hover: 1.3;
}

.flash-review { max-width: 40rem; margin: 0 auto; }

.flash-review-top {
	display: flex;
	align-items: center;
	gap: var(--s-3);
	margin-bottom: var(--s-4);
	font-size: var(--fs-sm);
	color: var(--c-text-dim);
}
.flash-review-scope { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 12rem; }
.flash-progress { flex: 1; height: 8px; min-width: 4rem; }
.flash-progress .track { fill: var(--c-bg-inset); }
.flash-progress .fill { fill: var(--swatch, var(--c-accent)); }
.flash-review-count { white-space: nowrap; }

.flash-review-card .flash-card { max-width: 28rem; margin: 0 auto; }
.flash-review-card .flash-card-inner { min-height: 16rem; }
.flash-card-corner {
	position: absolute;
	top: var(--s-4);
	left: var(--s-4);
	font-size: var(--fs-xs);
	color: var(--c-text-faint);
}

.flash-show-answer {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--s-2);
	width: fit-content;
	min-height: 2.75rem;
	margin: var(--s-4) auto 0;
	padding: var(--s-2) var(--s-6);
	border-radius: 10px;
	background: var(--c-accent);
	color: #fff;
	font-size: var(--fs-lg);
	font-weight: 500;
	cursor: pointer;
}
:root[data-theme="dark"] .flash-show-answer { color: #10141a; }
.flash-show-answer kbd,
.flash-grade kbd,
.flash-undo-btn kbd {
	font-family: var(--font-ui);
	font-size: var(--fs-2xs);
	opacity: 0.75;
}

/* Grade buttons: hidden until the card is flipped (CSS only). */
.flash-grades {
	display: none;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: var(--s-3);
	max-width: 34rem;
	margin: var(--s-4) auto 0;
}
.flash-flip:checked ~ .flash-show-answer { display: none; }
.flash-flip:checked ~ .flash-grades { display: grid; }
.flash-grade {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 2px;
	min-height: 3.5rem;
	padding: var(--s-2);
	border-radius: 10px;
	border: 2px solid;
	font-size: var(--fs-base);
	font-weight: 600;
	cursor: pointer;
}
/* Two classes (0,2,0): since #375 the generic button hover is only (0,1,0)
   and one class would do, but the pair keeps each grade's colour safe from
   any single-class .flash-grade rule (#337). */
.flash-grade.flash-grade-again { border-color: var(--flash-grade-again); background: var(--flash-grade-again-bg); color: var(--flash-grade-again-text); }
.flash-grade.flash-grade-hard  { border-color: var(--flash-grade-hard);  background: var(--flash-grade-hard-bg);  color: var(--flash-grade-hard-text); }
.flash-grade.flash-grade-good  { border-color: var(--flash-grade-good);  background: var(--flash-grade-good-bg);  color: var(--flash-grade-good-text); }
.flash-grade.flash-grade-easy  { border-color: var(--flash-grade-easy);  background: var(--flash-grade-easy-bg);  color: var(--flash-grade-easy-text); }
/* Hover keeps each grade's own colour and nudges it: darker on the pale
   light-theme fills, lighter on the deep dark-theme ones (a darken there
   is invisible). */
.flash-grade:hover { filter: brightness(var(--flash-grade-hover, 0.94)); }
.flash-grade:focus-visible { outline: var(--ring); outline-offset: 2px; }

.flash-undo { display: flex; justify-content: center; margin-top: var(--s-4); }

/* End-of-session summary. */
.flash-review-summary,
.flash-review-break {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--s-3);
	padding: var(--s-6) var(--s-4);
	text-align: center;
}
/* These are <h2>s (the page's one <h1> is the visually-hidden scope name
   outside #review-body, see review.html's "content"), but they keep the
   bigger heading size they had as <h1>s so the panel looks unchanged. */
.flash-review-summary h2,
.flash-review-break h2 { margin: 0; font-size: var(--fs-2xl); }
.flash-summary-lead { margin: 0; font-size: var(--fs-lg); }
.flash-breakdown { width: 100%; max-width: 24rem; height: 12px; border-radius: 6px; overflow: hidden; }
.flash-seg-good  { fill: var(--flash-grade-good); }
.flash-seg-easy  { fill: var(--flash-grade-easy); }
.flash-seg-hard  { fill: var(--flash-grade-hard); }
.flash-seg-again { fill: var(--flash-grade-again); }
.flash-breakdown-legend { margin: 0; color: var(--c-text-dim); font-size: var(--fs-sm); }
.flash-streak { margin: 0; font-weight: 500; }

/* A short burst of little cards, played once when the summary appears. */
.flash-celebrate {
	position: absolute;
	top: var(--s-5);
	left: 50%;
	width: 0;
	height: 0;
	pointer-events: none;
}
.flash-celebrate span {
	position: absolute;
	width: 14px;
	height: 18px;
	border-radius: 3px;
	background: var(--swatch);
	opacity: 0;
	animation: flash-burst 1s ease-out forwards;
}
/* Each span is one card in the celebration burst; --dx/--dy are the pixel
   offset its animation travels to (left/up is negative) and --rot is the
   degrees it spins by, together fanning six cards out in a rough arc above
   the trigger point. The six pairs were picked by eye for an even spread,
   not derived from a formula — adjust to taste if the count changes. */
.flash-celebrate span:nth-child(1) { --dx: -110px; --dy: -30px; --rot: -40deg; }
.flash-celebrate span:nth-child(2) { --dx: -60px;  --dy: -70px; --rot: -15deg; animation-delay: 0.05s; }
.flash-celebrate span:nth-child(3) { --dx: -10px;  --dy: -90px; --rot: 10deg;  animation-delay: 0.1s; }
.flash-celebrate span:nth-child(4) { --dx: 40px;   --dy: -80px; --rot: 25deg;  animation-delay: 0.05s; }
.flash-celebrate span:nth-child(5) { --dx: 90px;   --dy: -50px; --rot: 45deg;  animation-delay: 0.1s; }
.flash-celebrate span:nth-child(6) { --dx: 120px;  --dy: -10px; --rot: 70deg;  animation-delay: 0.15s; }
@keyframes flash-burst {
	0%   { opacity: 0; transform: translate(0, 0) rotate(0) scale(0.4); }
	20%  { opacity: 1; }
	100% { opacity: 0; transform: translate(var(--dx), var(--dy)) rotate(var(--rot)) scale(1); }
}

@media (max-width: 640px) {
	.flash-grades { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (prefers-reduced-motion: reduce) {
	.flash-celebrate { display: none; }
}

/* ---- ON Flash: sharing and breaks (UI overhaul U5) ----------------------- */

/* Gift decks: a dashed, see-through stack, like a present not yet opened. */
.deck-row-gift { border: 1px dashed var(--c-border-firm); }
.deck-row-gift .flash-stack > span {
	background: color-mix(in srgb, var(--swatch) 18%, var(--c-bg));
	border-style: dashed;
	border-color: var(--swatch);
}
.flash-gift .flash-stack > span { border-style: dashed; border-color: var(--swatch); }
.flash-gift-badge {
	padding: 0 var(--s-2);
	border-radius: 999px;
	background: var(--swatch-soft);
	color: var(--c-text);
	font-size: var(--fs-xs);
	font-weight: 600;
}
.flash-gift-from { margin: 0 0 var(--s-1); color: var(--c-text-dim); font-size: var(--fs-sm); }
.flash-gift-actions { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-4); }
.flash-gift-actions form { margin: 0; }
.flash-gift .flash-card-grid { margin-top: var(--s-2); }

/* Share popover. */
.flash-share-menu { position: relative; }
.flash-share-menu > summary { list-style: none; }
.flash-share-menu > summary::-webkit-details-marker { display: none; }
.flash-share-menu[open] > summary { color: var(--c-accent); background: var(--c-accent-bg); }
.flash-share-panel {
	position: absolute;
	right: 0;
	top: calc(100% + var(--s-1));
	z-index: 5;
	width: min(20rem, 80vw);
	padding: var(--s-3);
	border: 1px solid var(--c-border);
	border-radius: 10px;
	background: var(--c-bg);
	box-shadow: 0 8px 24px rgb(0 0 0 / 0.12);
}
.flash-share-form { margin: 0; }
.flash-share-form .row { align-items: stretch; }
.flash-share-form select { flex: 1; }
.flash-share-panel .flash-share-list li { justify-content: flex-start; }
.flash-share-panel .flash-share-list form { margin-left: auto; }
.flash-status-pill {
	padding: 0 var(--s-2);
	border-radius: 999px;
	background: var(--c-bg-subtle);
	color: var(--c-text-dim);
	font-size: var(--fs-xs);
}
.flash-status-adopted { background: var(--c-accent-bg); color: var(--c-accent); }

/* Taking a break: calm, not a warning. */
.flash-break-banner {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--s-3);
	margin-top: var(--s-4);
	padding: var(--s-3) var(--s-4);
	border-radius: 10px;
	background: var(--swatch-soft, var(--c-bg-subtle));
}
.flash-break-banner form { margin: 0; }

.flash-notice { margin-bottom: var(--s-4); background: var(--c-accent-bg); color: var(--c-accent); }

/* ---- ON Flash: import and stats (UI overhaul U6) ------------------------ */

.flash-import { max-width: var(--measure); }
.flash-import-steps {
	display: grid;
	gap: var(--s-3);
	margin: 0 0 var(--s-4);
	padding: 0;
	list-style: none;
	counter-reset: flash-step;
}
.flash-import-steps li {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--s-1);
	padding: var(--s-3) var(--s-4) var(--s-3) 3.25rem;
	border-radius: 10px;
	background: var(--c-bg-subtle);
	counter-increment: flash-step;
}
.flash-import-steps li::before {
	content: counter(flash-step);
	position: absolute;
	left: var(--s-3);
	top: var(--s-3);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.75rem;
	height: 1.75rem;
	border-radius: 50%;
	background: var(--c-accent);
	color: #fff;
	font-weight: 600;
	font-size: var(--fs-sm);
}
:root[data-theme="dark"] .flash-import-steps li::before { color: #10141a; }
.flash-copy-prompt { margin-top: var(--s-2); }
.flash-copy-prompt[hidden] { display: none; }
.flash-import-prompt summary,
.flash-import-format summary,
.flash-import-help summary { cursor: pointer; color: var(--c-text-dim); font-size: var(--fs-sm); }
.flash-import-prompt textarea { margin-top: var(--s-2); font-size: var(--fs-sm); }
.flash-import-help p { margin: var(--s-2) 0; }
.flash-import-help pre {
	margin: 0;
	padding: var(--s-2) var(--s-3);
	background: var(--c-bg-inset);
	border-radius: 8px;
	overflow-x: auto;
}

.flash-stats h1 { margin: 0 0 var(--s-4); }
.flash-load-list { margin: 0; padding: 0; list-style: none; }
.flash-load-row {
	display: flex;
	align-items: center;
	gap: var(--s-3);
	min-height: 3.5rem;
	padding: var(--s-2) var(--s-3);
	margin-bottom: var(--s-1);
	border-radius: 8px;
	color: inherit;
	text-decoration: none;
}
.flash-load-row:hover { background: var(--c-bg-subtle); }
.flash-load-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.flash-load-name { font-weight: 500; }
.flash-load-mastery { display: flex; align-items: center; gap: var(--s-2); font-size: var(--fs-xs); color: var(--c-text-dim); }
.flash-mastery-bar { width: 5rem; height: 6px; flex: none; }
.flash-mastery-bar .track { fill: var(--c-bg-inset); }
.flash-mastery-bar .fill { fill: var(--swatch, var(--c-accent)); }
.flash-load-due { font-size: var(--fs-xs); color: var(--c-text-dim); white-space: nowrap; }
.flash-load-due.flash-due-badge { color: var(--c-accent-attention); }

/* ---- ON Later ---------------------------------------------------------- */

.later-hl { background: var(--c-accent-attention-bg); color: inherit; border-radius: 2px; scroll-margin-top: 5rem; }
.later-page { max-width: var(--measure); margin-inline: auto; }
.later-save { display: flex; gap: .5rem; }
.later-save-input { flex: 1; min-width: 0; }
.later-save-tags { flex: 0 1 12rem; min-width: 0; }
.later-tags-form input { width: 100%; }
.later-article-tags { display: flex; flex-wrap: wrap; gap: .4rem; margin: 0; }
.later-form-error { color: var(--c-danger); margin: 0; }
.later-saved { color: var(--c-accent); margin: 0; }
.later-chip { display: inline-flex; align-items: center; gap: .4rem; font-size: var(--fs-sm); color: var(--c-accent); }
.later-chip-error { color: var(--c-danger); }
.later-tabs { display: inline-flex; border: var(--border); border-radius: var(--radius); overflow: hidden; align-self: flex-start; }
.later-tab { padding: .25rem .75rem; color: var(--c-text-dim); text-decoration: none; }
.later-tab[aria-current="page"] { background: var(--c-accent-bg); color: var(--c-accent); font-weight: 500; }
.later-tab-count { font-size: var(--fs-xs); }
.later-tags { display: flex; flex-wrap: wrap; gap: .4rem; }
.later-tag-chip { border: var(--border); border-radius: 999px; padding: .05rem .6rem; font-size: var(--fs-sm); color: var(--c-text-dim); text-decoration: none; }
.later-tag-chip[aria-current="true"] { background: var(--c-accent-bg); color: var(--c-accent); border-color: var(--c-accent); }
.later-pill-tag { background: var(--c-accent-bg); color: var(--c-accent); text-decoration: none; }
.later-rows { list-style: none; margin: 0; padding: 0; }
.later-row { display: flex; align-items: center; gap: .75rem; border-bottom: var(--border); }
.later-row-link { flex: 1; min-width: 0; display: flex; gap: .6rem; align-items: flex-start; padding: .6rem .25rem; color: inherit; text-decoration: none; }
.later-row-text { flex: 1; min-width: 0; }
.later-favicon-wrap { flex: none; width: 1rem; height: 1rem; margin-top: .2rem; }
.later-favicon { display: block; width: 1rem; height: 1rem; border-radius: 3px; }
.later-favicon-badge { display: inline-flex; align-items: center; justify-content: center; width: 1rem; height: 1rem; border-radius: 3px; background: var(--c-bg-inset); color: var(--c-text-dim); font-size: .625rem; font-weight: 600; }
.later-favicon-badge[hidden] { display: none; }
.later-row-title { display: block; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.later-row-meta { display: flex; flex-wrap: wrap; gap: .5rem; font-size: var(--fs-sm); color: var(--c-text-faint); }
.later-pill { border-radius: 999px; padding: 0 .5rem; font-size: var(--fs-xs); background: var(--c-bg-inset); color: var(--c-text-dim); }
.later-pill-linkonly { background: var(--c-accent-attention-bg); color: var(--c-accent-attention); }
/* Search results' Unread / Reading / Archived: outlined rather than filled,
 * so it reads apart from the grey highlight-count pill but stays quiet (#526). */
.later-pill-state { background: none; box-shadow: inset 0 0 0 1px var(--c-border-firm); }
.later-row-progress { width: 3rem; height: .25rem; }
.later-more { list-style: none; padding: .75rem 0; text-align: center; }
.later-empty { color: var(--c-text-dim); }
.later-search input[type="search"] {
	width: 100%;
	padding: var(--s-2);
	background: var(--c-bg);
	color: var(--c-text);
	border: 1px solid var(--c-border-firm);
	border-radius: var(--radius);
	font: inherit;
}
.later-search-head { display: flex; flex-wrap: wrap; gap: .75rem; align-items: baseline; margin: 0; color: var(--c-text-dim); font-size: var(--fs-sm); }
.later-row-snippet { display: block; margin-top: .15rem; font-size: var(--fs-sm); color: var(--c-text-dim); overflow-wrap: anywhere; }
.later-row-snippet mark { background: var(--c-accent-attention-bg); color: inherit; border-radius: 2px; }
.later-snippet-in { color: var(--c-text-faint); }
/* The bookmarklet popup is a small window: hide the suite chrome too. */
body:has(.later-popup) .shell-bar,
body:has(.later-popup) .app-sidebar,
body:has(.later-popup) .app-footer { display: none; }
.later-popup { max-width: 26rem; margin: 1.5rem auto; padding: 0 1rem; }
.later-popup-url { color: var(--c-text-faint); font-size: var(--fs-sm); overflow-wrap: anywhere; }
.later-hint { font-size: var(--fs-sm); color: var(--c-text-faint); text-align: center; }
.later-bookmarklet { display: inline-block; padding: .15rem .6rem; border: 1px dashed var(--c-border-firm); border-radius: var(--radius); cursor: grab; }
/* The reading view takes the whole window: hide the suite chrome. */
body:has(.later-reader) .shell-bar,
body:has(.later-reader) .app-sidebar,
body:has(.later-reader) .app-footer { display: none; }
main:has(.later-reader) { max-width: none; padding: 0; }
.later-sticky { position: sticky; top: 0; z-index: 2; background: var(--c-bg); }
.later-topbar { display: flex; align-items: center; gap: .75rem;
  padding: .5rem 1rem; background: var(--c-bg); border-bottom: var(--border); font-size: var(--fs-sm); color: var(--c-text-dim); }
.later-topbar form { margin: 0; }
.later-topbar-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--c-text); font-weight: 500; }
.later-left { white-space: nowrap; }
.later-progress { display: block; width: 100%; height: 3px; appearance: none; border: 0; background: var(--c-bg-subtle); }
.later-progress::-webkit-progress-bar { background: var(--c-bg-subtle); }
.later-progress::-webkit-progress-value { background: var(--c-accent); }
.later-progress::-moz-progress-bar { background: var(--c-accent); }
.later-menu { position: relative; }
.later-menu > summary { list-style: none; cursor: pointer; padding: .2rem .5rem; border: var(--border); border-radius: var(--radius); }
.later-menu > summary::-webkit-details-marker { display: none; }
.later-menu-panel { position: absolute; right: 0; top: calc(100% + .25rem); min-width: 13rem; padding: .75rem; background: var(--c-bg); border: var(--border); border-radius: var(--radius); box-shadow: 0 6px 20px rgba(0,0,0,.12); }
.later-menu-panel > a { display: block; }
@media (max-width: 40rem) {
  .later-menu { position: static; }
  .later-save { flex-wrap: wrap; }
  .later-save-input { flex-basis: 100%; }
  .later-topbar { gap: .5rem; }
  .later-left { display: none; }
  .later-back, .later-notes-toggle { white-space: nowrap; }
  .later-menu-panel { left: 1rem; right: 1rem; top: 100%; min-width: 0; }
}
.later-row-menu > summary { border: 0; }
.later-row-menu .later-menu-panel { z-index: 2; }
@media (max-width: 40rem) {
  .later-row { position: relative; }
}
.later-pref-row { display: flex; align-items: center; gap: .5rem; justify-content: space-between; }
.later-pref-row form { margin: 0; }
.later-pref-row button[aria-pressed="true"] { background: var(--c-accent-bg); color: var(--c-accent); }
.later-reader .later-article { margin: 2rem auto; padding-inline: 1rem; position: relative; }
.later-width-narrow .later-article { max-width: 56ch; }
.later-width-medium .later-article { max-width: 68ch; }
.later-width-wide .later-article { max-width: 84ch; }
.later-margin { display: none; }
.later-has-margin .later-margin { display: block; position: absolute; top: 0; margin: 0; left: calc(100% + 2rem); width: 14rem; }
.later-margin-note { position: absolute; left: 0; right: 0; margin: 0; padding-left: .5rem; border-left: 2px solid var(--c-accent-attention);
  font-size: var(--fs-sm); color: var(--c-text-dim); white-space: pre-wrap; cursor: pointer; }
.later-margin-note[hidden] { display: none; }
/* The 💬 marker for a commented highlight when there is no margin. CSS
   content is not part of textContent, so offsets don't move. */
.later-hl-commented.later-hl-end::after { content: "💬"; font-size: .9em; margin-left: .15em; }
.later-has-margin .later-hl-commented.later-hl-end::after { content: none; }
.later-font-serif .later-article-body { font-family: 'Source Serif 4', Georgia, serif; }
.later-font-sans .later-article-body { font-family: var(--font-ui); }
.later-size-1 .later-article-body { font-size: .95rem; }
.later-size-2 .later-article-body { font-size: 1.05rem; }
.later-size-3 .later-article-body { font-size: 1.125rem; }
.later-size-4 .later-article-body { font-size: 1.25rem; }
.later-size-5 .later-article-body { font-size: 1.4rem; }
.later-article-title { font-size: 2rem; line-height: 1.2; margin: 0; }
.later-article-meta { color: var(--c-text-faint); font-size: var(--fs-sm); margin: 0; }
.later-article-body { line-height: 1.65; overflow-wrap: anywhere; }
/* A calmer right edge (#559): hyphenate long words and let the browser
   balance the line breaks. Prose only — headings, code and tables keep
   their words whole. Hyphenation follows the page's lang. */
.later-article-body :is(p, li) { hyphens: auto; hyphenate-limit-chars: 6 3 3; text-wrap: pretty; }
.later-article-body code { hyphens: manual; }
.later-align-justify .later-article-body :is(p, li) { text-align: justify; }
.later-article-body img { max-width: 100%; height: auto; }
.later-article-body pre { overflow-x: auto; }
/* A code block's fill is close to the highlight tint, so a highlight in
 * code gets a stronger tint and an underline (#517). */
.later-article-body pre .later-hl { background: color-mix(in srgb, var(--c-accent-attention) 25%, transparent);
  box-shadow: inset 0 -2px var(--c-accent-attention); }
.later-article-body table { display: block; max-width: 100%; overflow-x: auto; }
.later-linkonly { border: var(--border); border-radius: var(--radius); padding: 1rem; background: var(--c-bg-subtle); }
.later-linkonly textarea { width: 100%; }
.later-dialog { width: min(28rem, calc(100vw - 2rem)); padding: 1rem; border: var(--border); border-radius: var(--radius); color: var(--c-text); background: var(--c-bg); }
.later-dialog::backdrop { background: rgba(0, 0, 0, 0.35); }
.later-notes-toggle { cursor: pointer; padding: .2rem .5rem; border: var(--border); border-radius: var(--radius); white-space: nowrap; }
.later-notes-open:focus-visible ~ .later-sticky .later-notes-toggle { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.later-notes { position: fixed; inset: 0 0 0 auto; z-index: 3; width: min(24rem, 100vw); overflow-y: auto; padding: 1rem;
  background: var(--c-bg); border-left: var(--border); box-shadow: -6px 0 20px rgba(0,0,0,.12);
  transform: translateX(100%); visibility: hidden; transition: transform .2s ease, visibility .2s; }
.later-notes-open:checked ~ .later-notes { transform: none; visibility: visible; }
.later-notes-head { display: flex; align-items: center; justify-content: space-between; }
.later-notes-head h2 { margin: 0; font-size: var(--fs-lg); }
.later-notes h3 { margin: var(--s-5) 0 0; font-size: var(--fs-base); }
.later-notes-close { cursor: pointer; padding: .2rem .5rem; }
.later-notes-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .75rem; }
.later-popover { position: absolute; z-index: 4; width: min(20rem, calc(100vw - 1rem)); padding: .75rem;
  background: var(--c-bg); border: var(--border); border-radius: var(--radius); box-shadow: 0 6px 20px rgba(0,0,0,.15); }
.later-popover[hidden], .later-popover [hidden] { display: none; }
.later-popover textarea { width: 100%; }
.later-popover-actions { display: flex; flex-wrap: wrap; gap: .5rem; }
.later-hl-error { margin: 0; color: var(--c-danger); font-size: var(--fs-sm); }
.later-notes-quote { display: block; padding-left: .5rem; border-left: 3px solid var(--c-accent-attention); color: inherit; text-decoration: none; }
.later-notes-comment { margin: .25rem 0 0 .75rem; font-size: var(--fs-sm); color: var(--c-text-dim); white-space: pre-wrap; }
.later-hl { cursor: pointer; }
.later-hl-quote { margin: 0; padding-left: .5rem; border-left: 3px solid var(--c-accent-attention); font-size: var(--fs-sm);
  color: var(--c-text-dim); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.later-notes-item { position: relative; }
.later-notes-edit { margin-top: .25rem; font-size: var(--fs-sm); }
.later-notes-stale, .later-notes-empty { font-size: var(--fs-sm); color: var(--c-text-faint); }
.later-note-form textarea { width: 100%; }
.later-note-actions { display: flex; gap: .5rem; align-items: center; }
.later-note-status { font-size: var(--fs-sm); color: var(--c-text-faint); }
.later-note-status-error { color: var(--c-danger); }
.later-note-end { margin-top: 2.5rem; padding-top: 1.5rem; border-top: var(--border); }
@media (prefers-reduced-motion: reduce) {
  .later-notes { transition: none; }
}

/* ---- In-app help (#309) ------------------------------------------------ */

/* A page list beside the guide. It stands on its own when signed out, where
 * the shell has no app sidebar. The links look like .sidebar-item, and the
 * guide reads like .reader-body, so help feels part of the app. */
.help {
	display: grid;
	grid-template-columns: 12rem minmax(0, var(--measure));
	gap: var(--s-6);
	justify-content: center;
	align-items: start;
}

.help-nav { position: sticky; top: var(--s-4); }
.help-nav ul { display: flex; flex-direction: column; gap: var(--s-1); list-style: none; margin: 0; padding: 0; }
.help-nav a {
	display: block;
	padding: var(--s-2);
	border-radius: var(--radius);
	color: var(--c-text-dim);
	text-decoration: none;
}
.help-nav a:hover { background: var(--c-bg-inset); color: var(--c-text); }
.help-nav a[aria-current="page"] { background: var(--c-accent-bg); color: var(--c-accent); font-weight: 600; }
.help-nav a:focus-visible { outline: var(--ring); outline-offset: 2px; }

.help-body { font-family: var(--font-body); line-height: 1.6; color: var(--c-text); }
.help-body h1, .help-body h2, .help-body h3 { font-family: var(--font-ui); }
.help-body h2 { margin-top: var(--s-6); padding-top: var(--s-4); border-top: var(--border); }
.help-body h3 { margin-top: var(--s-5); }
.help-body p, .help-body ul, .help-body ol, .help-body table, .help-body pre { margin: 0 0 var(--s-4); }
.help-body li + li { margin-top: var(--s-1); }
.help-body img { display: block; max-width: 100%; height: auto; border: var(--border); border-radius: var(--radius); }
.help-body :not(pre) > code { padding: 0.1em 0.35em; background: var(--c-bg-inset); border-radius: var(--radius); }
.help-body table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); line-height: 1.45; }
.help-body th, .help-body td { padding: var(--s-2) var(--s-3); border-bottom: var(--border); text-align: left; vertical-align: top; }
.help-body th { color: var(--c-text-dim); font-weight: 600; }
.help-body td[align="right"], .help-body th[align="right"] { text-align: right; }
.help-body td[align="center"], .help-body th[align="center"] { text-align: center; }

@media (max-width: 800px) {
	.help { grid-template-columns: minmax(0, 1fr); gap: var(--s-4); }
	.help-nav { position: static; }
	.help-nav ul { flex-direction: row; flex-wrap: wrap; }
	/* A wide table scrolls inside itself instead of widening the page. */
	.help-body table { display: block; overflow-x: auto; }
}

/* ---- ON Focus ------------------------------------------------------------
 * Spec: docs/superpowers/specs/2026-10-07-on-focus-design.md. A timer's
 * colour arrives as a swatch-c-<name> class (see "Colour swatches"). */

.focus-page { max-width: 64rem; }
.focus-toolbar { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); flex-wrap: wrap; }
.focus-toolbar .button { white-space: nowrap; }
.focus-toolbar h1 { margin: 0; }
.focus-toolbar-actions { display: flex; gap: var(--s-2); flex-wrap: wrap; }
.focus-today {
	margin: 0;
	padding: var(--s-2) var(--s-3);
	background: var(--c-bg-subtle);
	border-radius: var(--radius);
	color: var(--c-text-dim);
	font-size: var(--fs-sm);
	display: flex;
	flex-wrap: wrap;
	gap: var(--s-2) var(--s-5);
}
.focus-today b { color: var(--c-text); }
.focus-empty { padding: var(--s-5); background: var(--c-bg-subtle); border-radius: var(--radius); color: var(--c-text-dim); }

.focus-tiles {
	list-style: none;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
	gap: var(--s-3);
}
.focus-tile {
	display: flex;
	flex-direction: column;
	gap: var(--s-1);
	padding: var(--s-3);
	background: var(--c-bg);
	border: var(--border);
	border-top: 4px solid var(--swatch);
	border-radius: var(--radius);
	cursor: grab;
}
.focus-tile-dragging { opacity: .5; }
.focus-tile-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s-2); }
.focus-tile-name { font-weight: 600; color: var(--c-text); text-decoration: none; overflow-wrap: anywhere; }
.focus-tile-name:hover { text-decoration: underline; }
.focus-tile-summary { color: var(--c-text-dim); font-size: var(--fs-sm); }
.focus-tile-foot { display: flex; align-items: center; justify-content: space-between; margin-top: auto; padding-top: var(--s-2); }
/* Dim, not faint: chosen in the F4 dark-mode pass, before #556 brought faint to 4.5:1. */
.focus-pill { font-size: var(--fs-xs); color: var(--c-text-dim); border: var(--border); border-radius: 999px; padding: 0 var(--s-2); }
.focus-play {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.25rem;
	height: 2.25rem;
	border-radius: 50%;
	background: var(--swatch);
	color: #10141a;            /* 4.7–5.5:1 on every swatch; white is under 4.5 (F4 addendum) */
	text-decoration: none;
	font-size: var(--fs-sm);
}
.focus-play:hover { filter: brightness(1.1); }

.focus-menu { position: relative; }
.focus-menu > summary { list-style: none; cursor: pointer; padding: 0 var(--s-2); color: var(--c-text-faint); border-radius: var(--radius); }
.focus-menu > summary::-webkit-details-marker { display: none; }
.focus-menu > summary:hover { background: var(--c-bg-inset); color: var(--c-text); }
.focus-menu-panel {
	position: absolute;
	right: 0;
	top: calc(100% + .25rem);
	z-index: 2;
	min-width: 10rem;
	padding: var(--s-2);
	background: var(--c-bg);
	border: var(--border);
	border-radius: var(--radius);
	box-shadow: 0 6px 20px rgba(0,0,0,.12);
	cursor: default;
}
.focus-menu-panel form { margin: 0; }
.focus-menu-panel button, .focus-menu-panel a {
	display: block;
	width: 100%;
	padding: var(--s-1) var(--s-2);
	border: none;
	border-radius: var(--radius);
	background: none;
	color: var(--c-text);
	text-align: left;
	text-decoration: none;
	font-size: var(--fs-xs);
	line-height: 1.5;
	cursor: pointer;
}
.focus-menu-panel button:hover, .focus-menu-panel a:hover { background: var(--c-bg-inset); color: var(--c-text); text-decoration: none; }
.focus-menu-panel button.danger, .focus-menu-panel button.danger:hover { color: var(--c-danger); }

.focus-dialog { width: min(28rem, calc(100vw - 2rem)); padding: 1rem; border: var(--border); border-radius: var(--radius); color: var(--c-text); background: var(--c-bg); }
.focus-dialog::backdrop { background: rgba(0, 0, 0, 0.35); }

.focus-form { max-width: var(--measure); }
.focus-form fieldset { border: 0; padding: 0; margin-inline: 0; min-inline-size: 0; }
.focus-form legend { font-weight: 600; margin-bottom: var(--s-2); }
.focus-choice { display: flex; align-items: center; gap: var(--s-2); font-weight: 400; }
.focus-number input { width: 8rem; max-width: 100%; }
.focus-number-row { display: flex; flex-wrap: wrap; gap: var(--s-4); }
.focus-hint { margin: var(--s-1) 0 0; color: var(--c-text-dim); font-size: var(--fs-sm); }
.focus-field-error { margin: var(--s-1) 0 0; color: var(--c-danger); font-size: var(--fs-sm); }
.focus-form-actions { display: flex; align-items: center; gap: var(--s-3); }
/* Interval fields only matter for interval timers; no script needed. */
.focus-form:has(input[name="kind"][value="single"]:checked) .focus-intervals { display: none; }

.focus-swatches { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.focus-swatch {
	width: 2.75rem;
	height: 2.75rem;
	margin: 0;
	border-radius: 50%;
	background: var(--swatch);
	border: 3px solid var(--c-bg);
	box-shadow: 0 0 0 1px var(--c-border-firm);
	cursor: pointer;
}
.focus-swatch-input:checked + .focus-swatch { box-shadow: 0 0 0 3px var(--c-text); }
.focus-swatch-input:focus-visible + .focus-swatch { outline: var(--ring); outline-offset: 3px; }

.focus-chime-row { display: flex; align-items: center; gap: var(--s-2); }
.focus-chime-preview { padding: var(--s-1) var(--s-3); }
.focus-chime-preview[hidden] { display: none; }

/* Running page (spec: "Running page"). Focus mode hides the suite chrome,
 * exactly as ON Later's reading view does. */
body:has(.focus-runner) .shell-bar,
body:has(.focus-runner) .app-sidebar,
body:has(.focus-runner) .app-footer { display: none; }
main:has(.focus-runner) { max-width: none; padding: 0; }

.focus-runner {
	--focus-break: #a89a88;          /* breaks are a warm grey, never the timer's colour */
	--focus-ring: var(--swatch);
	position: relative;
	min-height: 100vh;
	min-height: 100dvh;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: var(--s-6) var(--s-4);
	background: var(--c-bg-subtle);
	color: var(--c-text);
}
/* 4.8:1 under the pills' dark text; the old #7a6c5d was 3.6. */
:root[data-theme="dark"] .focus-runner { --focus-break: #8f806e; }
.focus-runner-break { --focus-ring: var(--focus-break); }
.focus-runner [hidden] { display: none; }

.focus-runner-top {
	position: absolute;
	top: var(--s-3);
	left: var(--s-4);
	right: var(--s-4);
	display: flex;
	align-items: center;
}
.focus-runner-quiet {
	padding: var(--s-1) var(--s-2);
	border: 0;
	border-radius: var(--radius);
	background: none;
	color: var(--c-text-dim);
	font: inherit;
	font-size: var(--fs-sm);
	text-decoration: none;
	cursor: pointer;
}
.focus-runner-quiet:hover { background: var(--c-bg-inset); color: var(--c-text); text-decoration: none; }
.focus-runner-fullscreen { margin-left: auto; }

.focus-runner-main { display: flex; flex-direction: column; align-items: center; gap: var(--s-5); }
.focus-ring {
	position: relative;
	width: min(22rem, 80vw, 60vh);
	aspect-ratio: 1;
	display: flex;
	align-items: center;
	justify-content: center;
}
.focus-ring-svg { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }
.focus-ring-track, .focus-ring-bar { fill: none; stroke-width: 6; }
.focus-ring-track { stroke: var(--c-bg-inset); }
/* stroke-dashoffset is an SVG attribute focus.js sets; CSS must not set it,
 * or the stylesheet would win over the attribute. */
.focus-ring-bar { stroke: var(--focus-ring); stroke-linecap: round; stroke-dasharray: 1000; transition: stroke-dashoffset .5s linear; }
/* Nothing elapsed: a round cap would still draw a dot at 12 o'clock.
 * focus.js sets this attribute, so CSS can match it without a class. */
.focus-ring-bar[stroke-dashoffset="1000"] { stroke-linecap: butt; }

.focus-ring-inner { position: relative; display: flex; flex-direction: column; align-items: center; gap: var(--s-2); max-width: 75%; text-align: center; }
.focus-ring-inner p { margin: 0; }
.focus-runner-name { color: var(--c-text-dim); font-size: var(--fs-sm); letter-spacing: .04em; text-transform: uppercase; overflow-wrap: anywhere; }
.focus-digits { font-size: clamp(3rem, 12vmin, 4.5rem); font-weight: 300; font-variant-numeric: tabular-nums; letter-spacing: -.02em; line-height: 1; }
/* Dim text, not the timer's colour: every swatch is under 4.5:1 on the
 * runner's background. The ring and dots carry the colour (F4 dark-mode pass). */
.focus-phase { color: var(--c-text-dim); font-size: var(--fs-sm); font-weight: 600; }

.focus-dots { display: flex; gap: .4rem; margin: 0; padding: 0; list-style: none; }
.focus-dot { width: .5rem; height: .5rem; border: 1.5px solid var(--c-text-faint); border-radius: 50%; }
.focus-dot-done { background: var(--focus-ring); border-color: var(--focus-ring); }
.focus-dot-now { border-color: var(--swatch); box-shadow: 0 0 0 2px var(--swatch-soft); }

.focus-runner-paused .focus-ring-bar,
.focus-runner-paused .focus-digits { opacity: .45; }

/* Waiting for Start (auto-advance off): dimmed like paused, and Start
 * pulses gently so it's clear the timer needs you. */
.focus-runner-waiting .focus-ring-bar,
.focus-runner-waiting .focus-digits { opacity: .45; }
.focus-runner-waiting [data-focus-next] { animation: focus-pulse 2s ease-in-out infinite; }
@keyframes focus-pulse { 50% { transform: scale(1.06); } }
@media (prefers-reduced-motion: reduce) {
	.focus-runner-waiting [data-focus-next] { animation: none; }
}

.focus-controls { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-2); }
.focus-controls button,
.focus-controls .button { padding: var(--s-2) var(--s-4); border-radius: 999px; }
/* Dark text on the timer's colour in both themes: white is under 4.5:1 on
 * every swatch (F4 addendum). Same colour as the suite's dark .primary. */
.focus-controls .primary,
.focus-controls .primary:hover { background: var(--focus-ring); border-color: var(--focus-ring); color: #10141a; }

.focus-sound-hint { margin: 0; color: var(--c-text-dim); font-size: var(--fs-sm); text-align: center; }
.focus-done { display: flex; flex-direction: column; align-items: center; gap: var(--s-4); text-align: center; }
.focus-done-title { margin: 0; font-size: var(--fs-xl); }
.focus-done-status { margin: 0; color: var(--c-text-dim); min-height: 1.5em; }
/* Back to timers wears the timer's colour, even when the last phase was a
 * break (which turns --focus-ring grey on .focus-runner-break). */
.focus-done { --focus-ring: var(--swatch); }

.focus-resume {
	margin: 0;
	padding: var(--s-2) var(--s-3);
	border: var(--border);
	border-left: 4px solid var(--swatch, var(--c-accent));
	border-radius: var(--radius);
	color: var(--c-text-dim);
	font-size: var(--fs-sm);
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--s-2) var(--s-3);
}
.focus-resume[hidden], .focus-resume [hidden] { display: none; }
.focus-resume a { font-weight: 600; }
.focus-resume-text { flex: 1 1 16rem; }
.focus-resume-action { font-size: var(--fs-sm); padding: var(--s-1) var(--s-3); }

/* ON Focus: History (spec: "History"). Totals and chart mirror ON Flash's
   stats tiles and chart. */
.focus-totals {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
	gap: var(--s-3);
}
.focus-total { background: var(--c-bg-subtle); border-radius: var(--radius); padding: var(--s-3); }
.focus-total .value { font-size: var(--fs-xl); }
.focus-total .label { color: var(--c-text-dim); font-size: var(--fs-xs); }

.focus-chart { margin: 0; }
.focus-chart figcaption { font-size: var(--fs-sm); color: var(--c-text-dim); margin-bottom: var(--s-2); }
.focus-chart svg { width: 100%; height: 120px; display: block; overflow: visible; }
.focus-chart rect { fill: var(--c-accent); }
.focus-chart rect:hover { fill: var(--c-text); }
.focus-chart .empty, .focus-muted { color: var(--c-text-dim); }

.focus-by-timer { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--s-2); }
.focus-by-timer li {
	display: grid;
	grid-template-columns: minmax(6rem, 14rem) 1fr auto;
	align-items: center;
	gap: var(--s-3);
}
.focus-by-timer-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.focus-by-timer-bar { width: 100%; height: 0.5rem; display: block; }
.focus-by-timer-bar rect { fill: var(--swatch); }
.focus-by-timer-total { font-variant-numeric: tabular-nums; }

.focus-table-scroll { overflow-x: auto; } /* narrow screens scroll the table, not the page; mirrors .flash-table-scroll */
.focus-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.focus-table th, .focus-table td { text-align: left; padding: var(--s-2); border-bottom: var(--border); }
.focus-table th { color: var(--c-text-dim); font-weight: 500; }
.focus-sessions td:last-child { text-align: right; }
.focus-session-timer::before {
	content: "";
	display: inline-block;
	width: 0.6em;
	height: 0.6em;
	margin-right: 0.4em;
	border-radius: 50%;
	background: var(--swatch);
}
.focus-early {
	font-size: var(--fs-xs);
	color: var(--c-text-dim);
	border: var(--border);
	border-radius: 999px;
	padding: 0 var(--s-2);
	white-space: nowrap;
}
.focus-pager { display: flex; justify-content: space-between; }
.focus-pager a:only-child:last-child { margin-left: auto; }

@media (max-width: 640px) {
	.focus-by-timer li { grid-template-columns: 1fr auto; }
	.focus-by-timer-bar { grid-column: 1 / -1; grid-row: 2; }
	.focus-sessions th:nth-child(4), .focus-sessions td:nth-child(4) { display: none; }
}

/* ---- ON Books ----------------------------------------------------------
 * Two panes — the sidebar and a grid or list of books — and a book page of
 * its own (UI redesign spec,
 * docs/superpowers/specs/2026-10-12-on-books-ui-redesign-design.md). The
 * sidebar keeps Reader's .pane-gutter, its width a custom property on
 * <html> (books.js); up to 640px it is a drawer driven by #books-drawer
 * (PATTERNS.md, checkbox-driven CSS state). */
main:has(.books-home) { max-width: none; padding: 0; }

.books-home {
	--books-chrome: calc(4rem + var(--s-6) * 2);
	position: relative;
	display: flex;
	height: calc(100vh - var(--books-chrome));
	height: calc(100dvh - var(--books-chrome));
	min-height: 0;
	overflow: hidden;
}
.books-side { flex: 0 0 var(--books-side-w, 14rem); display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow-y: auto; padding: var(--s-3); background: var(--c-bg-subtle); border-right: 1px solid var(--c-border); }
.books-main { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow-y: auto; background: var(--c-bg); }
.books-drawer-dim,
.books-drawer-btn { display: none; }

/* Sidebar */
.books-shelves { list-style: none; margin: 0; padding: 0; }
.books-shelf { display: flex; justify-content: space-between; gap: var(--s-2); padding: var(--s-1) var(--s-2); border-radius: var(--radius); color: var(--c-text); text-decoration: none; }
.books-shelf:hover { background: var(--c-bg-inset); }
.books-shelf[aria-current="page"] { background: var(--c-accent-bg); color: var(--c-accent); font-weight: 600; }
.books-count { color: var(--c-text-dim); font-size: var(--fs-sm); }
.books-side-head { margin: var(--s-4) 0 var(--s-1); padding: 0 var(--s-2); font-size: var(--fs-xs); letter-spacing: 0.05em; text-transform: uppercase; color: var(--c-text-dim); }
.books-side-foot { margin-top: auto; padding-top: var(--s-3); border-top: 1px solid var(--c-border); }
.books-goal-card { display: block; margin-top: var(--s-3); padding: var(--s-2) var(--s-3); border: var(--border); border-radius: var(--radius); background: var(--c-bg); color: var(--c-text-dim); font-size: var(--fs-sm); text-decoration: none; }
.books-goal-card:hover { border-color: var(--c-accent); }
.books-goal-card-head { display: block; color: var(--c-text); font-weight: 600; }
.books-goal-card-text { display: block; }
.books-goal-bar { display: block; width: 100%; height: 4px; margin-top: var(--s-1); }

/* Toolbar */
.books-toolbar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); padding: var(--s-2) var(--s-4); border-bottom: 1px solid var(--c-border); background: var(--c-bg); }
.books-toolbar-head { display: flex; align-items: center; min-width: 0; }
.books-heading { margin: 0 var(--s-2) 0 0; font-size: var(--fs-lg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.books-heading .books-count { font-weight: 400; }
.books-filter { flex: 1 1 12rem; min-width: 0; margin: 0; }
/* The search box takes the global text-input look: the global rule above
 * covers text, url, number and select, not search. */
.books-filter input[type="search"] {
	width: 100%;
	padding: var(--s-1) var(--s-2);
	background: var(--c-bg);
	color: var(--c-text);
	border: 1px solid var(--c-border-firm);
	border-radius: var(--radius);
	font: inherit;
	font-size: var(--fs-sm);
	line-height: 1.4;
}
.books-prefs { display: flex; align-items: center; gap: var(--s-1); margin: 0; }
.books-prefs select { width: auto; }
/* One height for the toolbar's controls, about 30px: the sort select, the
 * search box, Add book and the icon buttons share a padding and a type size. */
.books-toolbar select,
.books-prefs .books-icon-btn {
	padding: var(--s-1) var(--s-2);
	font-size: var(--fs-sm);
	line-height: 1.4;
}
.books-toolbar .books-add { padding: var(--s-1) var(--s-3); font-size: var(--fs-sm); line-height: 1.4; }
.books-views { display: inline-flex; border: 1px solid var(--c-border-firm); border-radius: var(--radius); overflow: hidden; }
.books-views .toolbar-btn { border: 0; border-radius: 0; }
.books-icon-btn[aria-pressed="true"] { background: var(--c-accent-bg); color: var(--c-accent); }
.books-icon-btn:disabled { opacity: 0.5; cursor: default; }
.books-add { display: inline-flex; align-items: center; gap: var(--s-1); white-space: nowrap; }

/* Items: one markup, a tile in the grid or a row in the list */
.books-list { padding: var(--s-4); }
.books-items { list-style: none; margin: 0; padding: 0; }
.books-item-link { color: var(--c-text); text-decoration: none; }
.books-item-link:focus-visible { outline: var(--ring); outline-offset: 2px; border-radius: var(--radius); }
.books-item-cover { position: relative; display: block; flex: 0 0 auto; }
.books-item-cover img,
.books-item-spine { display: block; width: 100%; height: auto; aspect-ratio: 2 / 3; border-radius: 3px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); object-fit: cover; }
.books-item-spine { display: flex; align-items: flex-end; padding: var(--s-2); overflow: hidden; border-left: 6px solid var(--swatch); background: var(--swatch-soft); color: var(--c-text); font-size: var(--fs-xs); font-weight: 600; line-height: 1.2; overflow-wrap: anywhere; }
.books-item-initial { display: none; }
.books-item-cover-bar { position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: 4px; border-radius: 0 0 3px 3px; }
.books-item-title { font-weight: 600; }
.books-item-authors { color: var(--c-text-dim); font-size: var(--fs-sm); }
.books-item-tags { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.books-item-status { color: var(--c-text-dim); font-size: var(--fs-xs); font-weight: 600; }
.books-status-reading { color: var(--c-accent); }
.books-status-dnf { color: var(--c-accent-attention); }
.books-item-date { color: var(--c-text-faint); font-size: var(--fs-xs); }
.books-item-bar { display: block; width: 7rem; height: 4px; margin: var(--s-1) 0; }
/* A searched row's third line: where the search matched (B3), held to
 * two lines. */
.books-item-snippet { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; color: var(--c-text-dim); font-size: var(--fs-sm); overflow-wrap: anywhere; }
.books-item-snippet mark { background: var(--c-accent-attention-bg); color: inherit; border-radius: 2px; }
.books-snippet-in { color: var(--c-text-faint); }

/* Grid: tiles. The text and meta spans step aside (display: contents) so
 * the tile can order its lines as the mockup does. */
.books-list-grid .books-items { display: grid; grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr)); gap: var(--s-5) var(--s-4); }
.books-list-grid .books-item-link { display: flex; flex-direction: column; gap: 2px; }
.books-list-grid .books-item-text,
.books-list-grid .books-item-meta { display: contents; }
.books-list-grid .books-item-cover { margin-bottom: var(--s-1); }
.books-list-grid .books-item-title { order: 1; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; line-height: 1.25; }
.books-list-grid .books-item-authors { order: 2; }
.books-list-grid .books-item-status,
.books-list-grid .books-stars { order: 3; }
.books-list-grid .books-item-tags { order: 4; margin-top: 2px; }
.books-list-grid .books-item-date { order: 5; }
.books-list-grid .books-item-bar,
.books-list-grid .books-item-snippet { display: none; }
.books-list-grid .books-item-link:hover .books-item-title { color: var(--c-accent); }

/* List: rows */
.books-list-list .books-item-link { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-2); border-bottom: 1px solid var(--c-border); }
.books-list-list .books-item-link:hover { background: var(--c-bg-subtle); }
.books-list-list .books-item-cover { width: 2.5rem; }
.books-list-list .books-item-spine { justify-content: center; align-items: center; padding: 0; border-left-width: 4px; }
.books-list-list .books-item-spine-title,
.books-list-list .books-item-cover-bar { display: none; }
.books-list-list .books-item-initial { display: inline; color: var(--swatch); font-size: var(--fs-base); font-weight: 700; }
.books-list-list .books-item-text { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0; }
.books-list-list .books-item-title,
.books-list-list .books-item-authors { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.books-list-list .books-item-meta { display: flex; flex-direction: column; align-items: flex-end; flex: 0 0 auto; max-width: 40%; text-align: right; }

/* Phone: the sidebar is a drawer over the books. On a wider screen the
   checkbox that drives it has nothing to do, so it leaves the tab order. */
@media (min-width: 641px) {
	.books-drawer-toggle { display: none; }
}
#books-drawer:focus-visible ~ .books-main .books-drawer-btn { outline: var(--ring); }
@media (max-width: 640px) {
	.books-home { --books-chrome: calc(8rem + var(--s-4) * 2); }
	.books-home > .pane-gutter { display: none; }
	/* The drawer button names the list; the <h1> stays for screen readers
	   (same properties as .visually-hidden). */
	.books-heading { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
	/* visibility keeps the closed drawer's links out of the tab order; it
	   flips after the slide-out ends, and at once when the drawer opens. */
	.books-side { position: absolute; z-index: 3; top: 0; bottom: 0; left: 0; width: min(18rem, 85%); transform: translateX(-105%); visibility: hidden; transition: transform 0.2s ease, visibility 0s linear 0.2s; box-shadow: 4px 0 16px rgba(0, 0, 0, 0.2); }
	.books-drawer-toggle:checked ~ .books-side { transform: none; visibility: visible; transition-delay: 0s; }
	.books-drawer-toggle:checked ~ .books-drawer-dim { display: block; position: absolute; inset: 0; z-index: 2; background: rgba(0, 0, 0, 0.35); }
	.books-drawer-btn { display: inline-flex; max-width: 100%; }
	.books-drawer-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
	.books-toolbar { padding: var(--s-2) var(--s-3); }
	.books-filter { order: 5; flex-basis: 100%; }
	.books-list { padding: var(--s-3); }
	.books-add-label { display: none; }
	/* Icon-only, so it doesn't need the label's side padding (#594). */
	.books-toolbar .books-add { padding: var(--s-1) var(--s-2); }
	.books-list-grid .books-items { grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr)); gap: var(--s-4) var(--s-3); }
}
@media (prefers-reduced-motion: reduce) {
	.books-side { transition: none; }
}

/* The book page: one centred column (spec "Book details page") */
.books-page { max-width: 48rem; margin: 0 auto; }
.books-book { padding-bottom: var(--s-6); }
.books-page-bar { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); margin-bottom: var(--s-4); padding: var(--s-2) 0; border-bottom: 1px solid var(--c-border); }
.books-back { display: inline-flex; align-items: center; gap: var(--s-1); color: var(--c-accent); font-size: var(--fs-sm); text-decoration: none; }
.books-back:hover { text-decoration: underline; }
.books-page-actions { display: flex; align-items: center; gap: var(--s-1); }
.books-page-actions form { margin: 0; }
.books-delete { color: var(--c-danger); }
.books-delete:hover { color: var(--c-danger); background: var(--c-danger-bg); }
.books-banner { margin: 0 0 var(--s-3); }

/* Book */
.books-book-head { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s-4); margin-bottom: var(--s-4); }
.books-spine { flex: 0 0 auto; display: flex; align-items: flex-end; width: 6rem; height: 9rem; padding: var(--s-2); overflow: hidden; border-left: 8px solid var(--swatch); border-radius: 3px; background: var(--swatch-soft); color: var(--c-text); font-size: var(--fs-xs); font-weight: 600; line-height: 1.2; overflow-wrap: anywhere; }
.books-book-titles { flex: 1 1 12rem; min-width: 0; }
.books-book-titles h1 { margin: 0; font-size: var(--fs-xl); }
.books-subtitle { margin: var(--s-1) 0 0; color: var(--c-text-dim); }
.books-authors { margin: var(--s-2) 0 0; font-weight: 600; }
.books-series,
.books-facts { margin: var(--s-1) 0 0; color: var(--c-text-dim); font-size: var(--fs-sm); }
.books-pills { display: flex; flex-wrap: wrap; gap: var(--s-1); margin: var(--s-2) 0 0; }
.books-pill { padding: 0 var(--s-2); border-radius: 999px; background: var(--c-bg-subtle); color: var(--c-text-dim); font-size: var(--fs-xs); }
.books-pill-shelf { background: var(--c-accent-bg); color: var(--c-accent); }
/* The header's tag pills are read-only links to each tag's list (spec
 * "Header"). */
.books-pill-link { text-decoration: none; }
.books-pill-link:hover { background: var(--c-bg-inset); color: var(--c-text); }
.books-about { color: var(--c-text-dim); }
.books-about .books-description-text { max-width: 38rem; }
/* A long description is held to four lines until More is ticked (#566). */
.books-description-text { margin: 0; white-space: pre-line; }
.books-description-text.is-clamped { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; overflow: hidden; }
.books-description-toggle:checked ~ .books-description-text { display: block; -webkit-line-clamp: none; overflow: visible; }
.books-description-more { display: inline-block; margin: var(--s-1) 0 0; color: var(--c-accent); font-size: var(--fs-sm); cursor: pointer; }
.books-less-label,
.books-description-toggle:checked ~ .books-description-more .books-more-label { display: none; }
.books-description-toggle:checked ~ .books-description-more .books-less-label { display: inline; }
.books-description-toggle:focus-visible ~ .books-description-more { outline: var(--ring); }

/* Add / edit form */
.books-form { max-width: var(--measure); padding: var(--s-4) 0; }
.books-form fieldset { border: 0; padding: 0; margin-inline: 0; min-inline-size: 0; }
.books-form fieldset { padding-top: var(--s-3); border-top: 1px solid var(--c-border); }
.books-form legend { float: left; width: 100%; margin-bottom: var(--s-2); font-size: var(--fs-xs); font-weight: 600; letter-spacing: 0.07em; text-transform: uppercase; color: var(--c-text-faint); }
.books-form legend + * { clear: both; }
.books-form-row { display: flex; flex-wrap: wrap; gap: var(--s-3); }
.books-form-row .field { flex: 1 1 8rem; }
.books-choice { display: flex; align-items: center; gap: var(--s-2); margin-bottom: var(--s-2); font-size: var(--fs-base); color: var(--c-text); }
.books-field-error { margin: var(--s-1) 0 0; color: var(--c-danger); font-size: var(--fs-sm); }
.books-finished { display: none; }
.books-form:has(input[name="add_to"][value="read"]:checked) .books-finished { display: block; }
.books-form-actions { display: flex; align-items: center; gap: var(--s-2); }
/* Sections (UI redesign spec "Sections"): a hairline, a small uppercase
 * heading with its count, and at most one action on the heading's right.
 * That action is the summary of the section's own <details>, lifted into
 * the heading row, so every disclosure works without JavaScript
 * (PATTERNS.md). */
.books-section { position: relative; margin-top: var(--s-5); padding-top: var(--s-3); border-top: 1px solid var(--c-border); }
.books-section-head { margin: 0 0 var(--s-2); min-height: 1.75rem; font-size: var(--fs-xs); font-weight: 600; line-height: 1.75rem; letter-spacing: 0.07em; text-transform: uppercase; color: var(--c-text-faint); }
.books-section-count { font-weight: 400; }
.books-section-action { position: absolute; top: var(--s-3); right: 0; justify-content: center; width: 1.75rem; height: 1.75rem; padding: 0; list-style: none; }
.books-section-action::-webkit-details-marker { display: none; }
.books-section-add { border-color: var(--c-border-firm); border-radius: 999px; color: var(--c-accent); }
.books-section-add:hover { color: var(--c-accent); }
.books-icon-btn:focus-visible { outline: var(--ring); outline-offset: 1px; }
.books-small-btn { padding: var(--s-1) var(--s-3); font-size: var(--fs-sm); line-height: 1.4; }

/* Reading */
.books-reading-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); margin-bottom: var(--s-2); color: var(--c-text-dim); font-size: var(--fs-sm); }
.books-format { display: flex; align-items: center; gap: var(--s-1); margin: 0; }
/* A select, not a tag: the format's icon, the select and its chevron in
 * one outlined control. */
.books-format-control { display: inline-flex; align-items: center; gap: var(--s-1); padding-left: var(--s-2); border: 1px solid var(--c-border-firm); border-radius: var(--radius); background: var(--c-bg); color: var(--c-text-dim); }
.books-format-control:focus-within { outline: var(--ring); }
.books-format-control select { width: auto; padding: var(--s-1) var(--s-2) var(--s-1) 0; border: 0; background: var(--c-bg); color: var(--c-text); font-size: var(--fs-sm); }
.books-format-control select:focus { outline: none; }
.books-format-save { padding: var(--s-1) var(--s-2); font-size: var(--fs-sm); }
.books-progress { margin: 0; }
.books-progress-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-3); }
.books-progress-form { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1) var(--s-2); margin: 0; font-size: var(--fs-sm); }
.books-progress-form label { margin: 0; color: var(--c-text); font-size: var(--fs-sm); }
.books-progress-form input[type="number"] { width: 4.5rem; padding: var(--s-1) var(--s-2); }
.books-progress-of,
.books-progress-note { color: var(--c-text-dim); }
.books-progress-note::before { content: "· "; }
.books-progress-save { border-color: var(--c-border-firm); }
.books-progress-form .books-field-error { flex-basis: 100%; }
/* On a phone the note gets a line of its own, under the box, rather than
 * wrapping so a line starts with its "·" (#603). */
@media (max-width: 640px) {
	.books-progress-note { order: 1; flex-basis: 100%; }
	.books-progress-note::before { content: none; }
	.books-progress-form .books-field-error { order: 2; }
}
/* Finish… and Did not finish… sit at the row's end. Their popovers are
 * anchored to the pair rather than to each button, so on a phone neither
 * runs off the left edge. */
.books-reading-actions { position: relative; display: flex; flex-wrap: wrap; gap: var(--s-2); margin-left: auto; }
.books-close > summary { display: inline-flex; list-style: none; cursor: pointer; }
.books-close > summary::-webkit-details-marker { display: none; }
.books-close-form { position: absolute; top: calc(100% + var(--s-1)); right: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s-2); width: max-content; max-width: min(20rem, calc(100vw - 2rem)); padding: var(--s-3); border: var(--border); border-radius: var(--radius); background: var(--c-bg); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); }
.books-close-form label { margin: 0; flex-basis: 100%; }
/* Progress bars: the Reading section's, the goal's and a tile's */
.books-progress-bar,
.books-bar { appearance: none; border: 0; border-radius: 999px; overflow: hidden; background: var(--c-bg-inset); }
.books-progress-bar { display: block; width: 100%; max-width: 24rem; height: 6px; margin-top: var(--s-2); }
.books-reading .books-progress-bar { max-width: none; height: 4px; margin: 0 0 var(--s-2); }
.books-progress-bar::-webkit-progress-bar,
.books-bar::-webkit-progress-bar { background: var(--c-bg-inset); }
.books-progress-bar::-webkit-progress-value,
.books-bar::-webkit-progress-value { background: var(--c-accent); }
.books-progress-bar::-moz-progress-bar,
.books-bar::-moz-progress-bar { background: var(--c-accent); }
.books-stars { color: var(--c-accent); letter-spacing: 0.05em; }

/* Your take */
.books-rating { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1); margin: 0 0 var(--s-2); }
/* The five stars wrap as one, never one by one (#579). */
.books-star-row { display: inline-flex; flex-wrap: nowrap; gap: var(--s-1); }
.books-star { display: inline-flex; padding: var(--s-1); border: 0; background: none; color: var(--c-text-faint); cursor: pointer; }
.books-star:hover,
.books-star.is-on { color: var(--c-accent); }
.books-review-text { max-width: 38rem; }
.books-review-text p { margin: 0 0 var(--s-2); }
/* ✎ turns the review into its textarea in place. */
.books-take:has(> .books-review-edit[open]) > .books-review-text { display: none; }
.books-review-edit > summary::-webkit-details-marker { display: none; }
.books-review-write { display: inline-block; list-style: none; color: var(--c-accent); font-size: var(--fs-sm); cursor: pointer; }
.books-review-write::-webkit-details-marker { display: none; }
.books-review-write:hover { text-decoration: underline; }
.books-review-form { display: flex; flex-direction: column; gap: var(--s-2); max-width: 38rem; margin-top: var(--s-2); }
.books-review-form label { margin: 0; }
.books-review-form textarea { width: 100%; }
.books-review-form button { align-self: flex-start; }
.books-hint { margin: 0; color: var(--c-text-dim); font-size: var(--fs-sm); }

/* Entries: notes, quotes and readings. ✎ and 🗑 sit at an entry's top
 * right. They show on hover and on keyboard focus, and always on a touch
 * screen, which has no hover (spec "Sections"). They're hidden with
 * opacity, so they stay in the tab order. On a touch screen they're 44px,
 * the usual touch target (#603). The entry floats a box their size at its
 * top right, so only the first line or two wraps around them, not the
 * whole entry; an open form clears it and has the full width. */
.books-entries { list-style: none; margin: 0; padding: 0; }
.books-entry { --books-entry-icon: 1.75rem; position: relative; display: flow-root; padding: var(--s-2) 0; border-bottom: 1px solid var(--c-border); }
.books-entry::before { content: ""; float: right; width: calc(2 * var(--books-entry-icon) + 0.25rem); height: var(--books-entry-icon); }
.books-entry:last-child { border-bottom: 0; }
.books-entry-edit > summary,
.books-entry-delete { position: absolute; top: var(--s-2); margin: 0; opacity: 0; transition: opacity 0.15s ease; }
.books-entry-edit > summary { right: calc(var(--books-entry-icon) + 0.25rem); list-style: none; }
.books-entry-edit > summary::-webkit-details-marker { display: none; }
.books-entry-delete { right: 0; }
.books-entry-icon { justify-content: center; width: var(--books-entry-icon); height: var(--books-entry-icon); padding: 0; }
.books-entry-edit[open] > form { clear: right; }
.books-entry-icon.is-danger { color: var(--c-danger); }
.books-entry-icon.is-danger:hover { background: var(--c-danger-bg); color: var(--c-danger); }
.books-entry:hover > .books-entry-edit > summary,
.books-entry:hover > .books-entry-delete,
.books-entry:focus-within > .books-entry-edit > summary,
.books-entry:focus-within > .books-entry-delete,
.books-entry-edit[open] > summary { opacity: 1; }
@media (hover: none) {
	.books-entry { --books-entry-icon: 2.75rem; }
	.books-entry-edit > summary,
	.books-entry-delete { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
	.books-entry-edit > summary,
	.books-entry-delete { transition: none; }
}
/* ✎ turns the entry into its form in place. */
.books-entry:has(> .books-entry-edit[open]) > :is(.books-entry-meta, .books-entry-text, .books-quote-text, .books-history-line) { display: none; }
.books-entry-meta { margin: 0; color: var(--c-text-dim); font-size: var(--fs-sm); }
.books-entry-text p { margin: var(--s-1) 0; }
.books-entry-form { display: flex; flex-direction: column; gap: var(--s-2); max-width: 38rem; margin: var(--s-2) 0; }
.books-entry-form label { margin: 0; }
.books-entry-form textarea { width: 100%; }
.books-entry-form input[type="number"] { width: 6rem; }
.books-entry-form button { align-self: flex-start; }
/* A quote keeps its blockquote, and the line breaks it was typed with. */
.books-quote-text { margin: 0 0 var(--s-1); padding-left: var(--s-3); border-left: 3px solid var(--c-accent); font-style: italic; white-space: pre-line; overflow-wrap: anywhere; }
.books-quote-comment { margin-top: var(--s-1); }

/* Reading history: a disclosure, collapsed until opened. Its toggle is a
 * chevron in the heading row (a lifted summary, PATTERNS.md), which turns
 * over while it is open. */
.books-history-section:not(:has(> .books-history[open])) > .books-section-head { margin-bottom: 0; }
.books-history-toggle { cursor: pointer; }
.books-history-toggle .toolbar-icon { transition: transform 0.15s ease; }
.books-history[open] > .books-history-toggle .toolbar-icon { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
	.books-history-toggle .toolbar-icon { transition: none; }
}
.books-history-line { margin: 0; }
.books-reading-edit-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s-2); margin-top: var(--s-2); }
.books-reading-edit-form label { margin: 0; flex-basis: 100%; }
.books-reading-edit-form select { width: auto; }
/* The global input rule leaves out type="date", which would render as a
 * white native box in dark mode. */
.books-close-form input[type="date"],
.books-reading-edit-form input[type="date"] { padding: var(--s-1) var(--s-2); background: var(--c-bg); color: var(--c-text); border: 1px solid var(--c-border-firm); border-radius: var(--radius); font: inherit; }

/* The one confirm dialog */
.books-dialog { width: min(28rem, calc(100vw - 2rem)); padding: 1rem; border: var(--border); border-radius: var(--radius); color: var(--c-text); background: var(--c-bg); }
.books-dialog::backdrop { background: rgba(0, 0, 0, 0.35); }

/* The header's cover and titles stack on a phone (spec "Book details
 * page"); the sections are one column at every width already. */
@media (max-width: 640px) {
	.books-book-head { flex-direction: column; }
	/* In a column the 12rem basis would be a height. */
	.books-book-titles { flex-basis: auto; }
}

/* The Add and Edit pages: the book page's column and page bar. */
.books-form-page { max-width: 48rem; margin: 0 auto; padding: 0 0 var(--s-6); }
.books-form-page > h1 { margin: 0; }
.books-form-page .books-form { padding: 0; }

/* Add book page: Open Library search */
.books-search-row { display: flex; gap: var(--s-2); }
.books-search-row input { flex: 1 1 auto; min-width: 0; }
.books-results { list-style: none; margin: 0; padding: 0; }
.books-result { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-2) 0; border-bottom: 1px solid var(--c-border); }
.books-result-cover { flex: 0 0 auto; display: block; width: 2.5rem; height: 3.75rem; object-fit: cover; border-radius: 2px; background: var(--c-bg-subtle); }
.books-result-text { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
.books-result-title { font-weight: 600; }
.books-result-byline { color: var(--c-text-dim); font-size: var(--fs-sm); }
.books-result-use { flex: 0 0 auto; }
.books-form-sub { margin: var(--s-2) 0 0; font-size: var(--fs-base); }
.books-picked { display: flex; align-items: center; gap: var(--s-3); }
.books-picked p { margin: 0; color: var(--c-text-dim); font-size: var(--fs-sm); }

/* Covers: the same footprint as the spines they replace */
.books-cover { flex: 0 0 auto; display: block; width: 6rem; height: auto; max-height: 9rem; object-fit: contain; border-radius: 3px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); }

/* Edit page: the cover */
.books-cover-edit { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s-4); }
.books-cover-edit > .stack { flex: 1 1 14rem; min-width: 0; }
.books-cover-edit input[type="url"] { width: 100%; }

/* Stats (B4): a page of its own, as Reader's is; tiles and charts mirror
 * ON Focus's History. */
.books-stats { max-width: 48rem; }
.books-stats-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--s-3); }
.books-stats-head h1 { margin: 0; }
.books-years { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.books-year { padding: var(--s-1) var(--s-2); border-radius: var(--radius); color: var(--c-text); text-decoration: none; font-variant-numeric: tabular-nums; }
.books-year:hover { background: var(--c-bg-subtle); }
.books-year[aria-current="page"] { background: var(--c-accent-bg); color: var(--c-accent); font-weight: 600; }
.books-stats-section h2 { margin: 0 0 var(--s-3); font-size: var(--fs-lg); }
.books-stat-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: var(--s-3); margin-bottom: var(--s-3); }
.books-stat-tile { background: var(--c-bg-subtle); border-radius: var(--radius); padding: var(--s-3); }
.books-stat-tile .value { font-size: var(--fs-xl); }
.books-stat-tile .label { color: var(--c-text-dim); font-size: var(--fs-xs); }
.books-stat-line { margin: 0 0 var(--s-2); overflow-wrap: anywhere; }
.books-stat-label { color: var(--c-text-dim); font-size: var(--fs-sm); margin-right: var(--s-1); }
.books-chart { margin: var(--s-4) 0 0; }
.books-chart figcaption { font-size: var(--fs-sm); color: var(--c-text-dim); margin-bottom: var(--s-2); }
.books-chart svg { width: 100%; height: 120px; display: block; overflow: visible; }
.books-chart rect { fill: var(--c-accent); }
.books-chart rect:hover { fill: var(--c-text); }
.books-chart .empty { color: var(--c-text-dim); }
.books-chart-ticks { display: flex; list-style: none; margin: var(--s-1) 0 0; padding: 0; }
.books-chart-ticks li { flex: 1 1 0; min-width: 0; overflow: hidden; text-align: center; color: var(--c-text-dim); font-size: var(--fs-xs); }
.books-chart-figures { margin-top: var(--s-2); font-size: var(--fs-sm); }
.books-stats-table { width: 100%; border-collapse: collapse; }
.books-stats-table th,
.books-stats-table td { text-align: left; padding: var(--s-1) var(--s-2); border-bottom: 1px solid var(--c-border); }
.books-stats-table th { color: var(--c-text-dim); font-weight: 500; }

/* The goal on the Stats page, where it is set and changed. */
.books-goal { padding: var(--s-3); border: var(--border); border-radius: var(--radius); background: var(--c-bg-subtle); }
.books-goal h2 { margin: 0 0 var(--s-2); font-size: var(--fs-lg); }
.books-goal-text { margin: 0; }
.books-goal-count { font-size: var(--fs-xl); font-weight: 600; }
.books-goal .books-progress-bar { margin: var(--s-2) 0 var(--s-3); }
.books-goal-actions { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s-2); }
.books-goal-edit > summary { display: inline-flex; list-style: none; cursor: pointer; }
.books-goal-edit > summary::-webkit-details-marker { display: none; }
.books-goal-edit[open] { flex-basis: 100%; }
.books-goal-form { margin-top: var(--s-2); }
.books-goal-form label { margin: 0 0 var(--s-1); }
.books-goal-row { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.books-goal-row input { width: 6rem; }

/* Import (B5): a page of its own, as Stats is, linked under Stats in the
 * sidebar. */
.books-side-link { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-1) var(--s-2); border-radius: var(--radius); color: var(--c-text); text-decoration: none; }
.books-side-link:hover { background: var(--c-bg-subtle); }
.books-import { max-width: var(--measure); }
.books-import-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--s-3); }
.books-import-head h1 { margin: 0; }
.books-import-result { padding: var(--s-3); border: var(--border); border-radius: var(--radius); background: var(--c-bg-subtle); }
.books-import-result p { margin: 0 0 var(--s-2); }
.books-import-result p:last-child { margin-bottom: 0; }
.books-import-count { font-weight: 600; }
.books-import-skipped { margin: 0 0 var(--s-2); padding-left: var(--s-4); overflow-wrap: anywhere; }
.books-import-form input[type="file"] { max-width: 100%; }
