/* ==========================================================================
   Vital Articles reader
   Ink-on-paper, tuned for long-form reading. Cool neutrals with a single
   teal accent -- a reference tool, not an editorial magazine.
   ========================================================================== */

:root {
  --bg:          #f6f6f4;
  --surface:     #ffffff;
  --surface-2:   #f0f0ed;
  --ink:         #16181d;
  --ink-soft:    #3d424c;
  --muted:       #5b6270;   /* 5.5:1 on --bg; the byline sits at .85rem */
  --line:        #e0e1de;
  --line-strong: #c9cbc7;
  --accent:      #1a5f59;
  --accent-ink:  #ffffff;
  --accent-soft: #e4efee;
  --gold:        #9a6a1f;
  --danger:      #9b2c2c;

  --measure: 40rem;
  --radius: 10px;
  --shadow: 0 1px 2px rgba(20,22,26,.05), 0 6px 20px -8px rgba(20,22,26,.14);

  --ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", sans-serif;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Charter, Georgia, "Times New Roman", serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* Sepia. Warm paper with brown ink -- easier on the eyes for a long session
   than either the white or the black extreme, and the reason people reach for
   this mode in e-readers. */
:root[data-theme="sepia"] {
  --bg:          #f2e9d5;
  --surface:     #faf3e2;
  --surface-2:   #eae0c6;
  --ink:         #33291b;
  --ink-soft:    #473a28;
  --muted:       #6b5c41;
  --line:        #dcd0b2;
  --line-strong: #c2b18f;
  --accent:      #7a4413;
  --accent-ink:  #fdf8ee;
  --accent-soft: #ecdcbe;
  --gold:        #8a6414;
  --danger:      #8f2f22;
  --shadow: 0 1px 2px rgba(70,52,26,.07), 0 6px 20px -8px rgba(70,52,26,.18);
}

/* Classic. The feeling of opening a printed encyclopaedia, built from
   typography rather than texture: aged paper, letterpress ink, an oxblood
   binding and antique gold used as *ink*, never as metal. No leather bitmaps,
   no gradients, no embossing -- those are what make this look like a theme
   park. See the typographic block further down, which is where the actual
   work happens. */
:root[data-theme="classic"] {
  --bg:          #ece4d0;
  --surface:     #f5efdf;
  --surface-2:   #e0d7bf;
  --ink:         #221d16;
  --ink-soft:    #372f24;
  --muted:       #635a48;
  --line:        #cfc4a8;
  --line-strong: #ab9d7c;
  --accent:      #6d2233;
  --accent-ink:  #f5efdf;
  --accent-soft: #e6d8d0;
  --gold:        #8a6a1f;
  --danger:      #8c2b20;
  --radius: 3px;              /* books have square corners */
  --shadow: none;             /* and they do not float */
  --serif: Baskerville, "Libre Baskerville", "Palatino Linotype", Palatino,
           "Book Antiqua", "Iowan Old Style", Georgia, serif;
}

:root[data-theme="dark"] {
  --bg:          #101319;
  --surface:     #171b22;
  --surface-2:   #1e232c;
  --ink:         #e7e9ed;
  --ink-soft:    #c2c7d0;
  --muted:       #939bab;
  --line:        #262c37;
  --line-strong: #39414f;
  --accent:      #63b5ab;
  --accent-ink:  #0c1114;
  --accent-soft: #14302d;
  --gold:        #d8ab5c;
  --danger:      #e08585;
  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px -10px rgba(0,0,0,.6);
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: 5rem; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ui);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

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

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--accent); color: var(--accent-ink); padding: .6rem 1rem;
}
.skip:focus { left: .5rem; top: .5rem; }

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

/* --- top bar ------------------------------------------------------------ */

.topbar {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(1.5) blur(10px);
  border-bottom: 1px solid var(--line);
}
.topbar-inner {
  max-width: 78rem; margin: 0 auto; padding: .6rem 1.25rem;
  display: flex; align-items: center; gap: 1.25rem;
}
/* The wordmark takes each theme's reading serif, so the bar is set in the same
   voice as the page under it. Weight comes down from 650: a serif at that
   weight next to the mark reads as shouting, and the letterforms carry the
   emphasis on their own. */
.brand {
  display: flex; align-items: center; gap: .55rem;
  text-decoration: none; color: var(--ink);
  font-family: var(--serif);
  font-weight: 550;
  font-size: 1.06rem;
  letter-spacing: 0;
}
/* The mark: a champ initial, gold V on an oxblood ground. Fixed colours rather
   than theme variables, because it is an object on the page -- a stamp on a
   binding -- not a piece of interface that should restyle itself. Dark is the
   one exception: full-strength oxblood glares against a near-black bar. */
.mark-v { width: 1.85rem; height: 1.85rem; flex: none; display: block; border-radius: 5px; }
.mark-ground { fill: #6d2233; }
.mark-ink { fill: #c9a15a; }

:root[data-theme="dark"] .mark-ground { fill: #2a1116; }
:root[data-theme="dark"] .mark-ink { fill: var(--gold); }

/* In classic the topbar *is* the binding, so the ground is dropped and the
   letter sits directly on the leather -- which is where a real one would be
   stamped. Works whichever binding colour is chosen. */
:root[data-theme="classic"] .topbar .mark-ground { fill: transparent; }
:root[data-theme="classic"] .topbar .mark-ink {
  fill: var(--gold);
  /* With the ground gone the letter is the only thing left, and it only fills
     about half its tile -- the rest was margin around the pigment block. So it
     grows into the space the ground vacated, or it reads a size smaller than
     the mark does on every other theme. */
  transform: scale(1.32);
  transform-box: fill-box;
  transform-origin: center;
}
.nav { display: flex; gap: .3rem; margin-left: .5rem; }
.nav a {
  color: var(--muted); text-decoration: none; font-size: .9rem; font-weight: 550;
  padding: .35rem .7rem; border-radius: 7px;
}
.nav a:hover { background: var(--surface-2); color: var(--ink); }
.nav a[aria-current="page"] { background: var(--accent-soft); color: var(--accent); }

.topbar-right { margin-left: auto; display: flex; align-items: center; gap: .35rem; }

/* The bar sheds contents in two stages, both measured rather than guessed.
   Laid out in full -- wordmark, three nav links, Aa, Account and Sign out --
   it needs about 685px. The wordmark therefore has to go well before a phone:
   at 30rem it was still on screen through the whole 480-620px band, pushing
   every page sideways on a tablet held upright. */
@media (max-width: 44rem) {
  .brand-text { display: none; }
  .nav { margin-left: 0; }
}

/* Second stage. Without the wordmark the bar still wants ~420px, so a small
   phone needs more taken out of it. Sign out goes, because it is the one item
   with another home -- see the Session card on the account page. */
@media (max-width: 30rem) {
  .topbar-inner { gap: .4rem; padding: .55rem .6rem; }
  .brand { gap: .4rem; }
  /* Held back from its full 1.85rem: with the wordmark gone the mark is the
     brand, so it stays large, but not so large that it costs a device width. */
  .mark-v { width: 1.6rem; height: 1.6rem; }
  .nav a { padding: .35rem .5rem; }
  .icon-btn, .link-btn { padding: .35rem .5rem; }
  /* Scoped to the bar so it outranks `.inline-form { display: inline }`, which
     is declared further down the file at equal specificity and would otherwise
     win on source order and quietly keep the button on screen. */
  .topbar-right .signout { display: none; }
}

/* Third stage, for the narrowest phones still in use. Nothing else can be
   dropped without losing a destination, so what is left just gets tighter. */
@media (max-width: 23rem) {
  .topbar-inner { gap: .25rem; padding: .5rem .45rem; }
  .nav { gap: .1rem; }
  .nav a { padding: .35rem .38rem; font-size: .84rem; }
  .link-btn { padding: .35rem .38rem; font-size: .84rem; }
  .icon-btn { padding: .35rem .4rem; }
}
.inline-form { display: inline; }
.icon-btn, .link-btn {
  background: none; border: 1px solid transparent; color: var(--muted);
  font: inherit; font-size: .875rem; cursor: pointer;
  padding: .35rem .7rem; border-radius: 7px;
}
.icon-btn:hover, .link-btn:hover { background: var(--surface-2); color: var(--ink); }
/* Theme selection now lives in the reading-settings panel. */

/* Scroll progress, painted into the bottom edge of the sticky bar. */
/* Reading progress. The track is drawn, not transparent: without it the bar
   says how far you have come but nothing about how far that is out of, and at
   2px on a tinted topbar it read as a stray hairline. */
.readbar { height: 6px; background: color-mix(in srgb, var(--ink) 16%, transparent); }
.readbar > i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .08s linear; }

/* --- layout primitives -------------------------------------------------- */

.wrap { max-width: 78rem; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.wrap-narrow { max-width: 52rem; }
.stack > * + * { margin-top: 1rem; }

.page-head { margin-bottom: 1.75rem; }
/* Page headings take the serif like the article's own title does. Left as
   the UI sans they made every screen outside the reader look like a different
   application, which is the opposite of what the themes are for. */
.page-head h1 {
  font-family: var(--serif);
  font-size: 1.9rem;
  line-height: 1.15;
  letter-spacing: -.015em;
  margin: 0 0 .3rem;
}
.page-head p { color: var(--muted); margin: 0; }

.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1.25rem;
}
.muted { color: var(--muted); }
.small { font-size: .875rem; }
.tiny { font-size: .78rem; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
  background: var(--accent); color: var(--accent-ink);
  border: 1px solid transparent; border-radius: 8px;
  padding: .6rem 1.1rem; font: inherit; font-weight: 600; font-size: .925rem;
  text-decoration: none; cursor: pointer;
}
.btn:hover { filter: brightness(1.08); text-decoration: none; }
.btn-ghost { background: var(--surface); color: var(--ink); border-color: var(--line-strong); }
.btn-ghost:hover { background: var(--surface-2); filter: none; }

.pill {
  display: inline-flex; align-items: center; gap: .3rem;
  font-size: .72rem; font-weight: 650; letter-spacing: .04em; text-transform: uppercase;
  padding: .2rem .5rem; border-radius: 999px;
  background: var(--surface-2); color: var(--muted); border: 1px solid var(--line);
}
.pill-done { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.pill-level { background: transparent; }

/* --- dashboard ---------------------------------------------------------- */

.hero {
  display: grid; grid-template-columns: 1fr; gap: 1.5rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; padding: 1.75rem; box-shadow: var(--shadow);
}
@media (min-width: 46rem) { .hero { grid-template-columns: 1fr auto; align-items: center; } }
.hero-eyebrow { font-size: .74rem; font-weight: 700; letter-spacing: .11em; text-transform: uppercase; color: var(--accent); margin: 0 0 .5rem; }
.hero h2 { font-family: var(--serif); font-size: clamp(1.7rem, 4vw, 2.4rem); line-height: 1.15; letter-spacing: -.02em; margin: 0 0 .6rem; }
.hero h2 a { color: inherit; text-decoration: none; }
.hero h2 a:hover { color: var(--accent); }
.hero-summary { color: var(--ink-soft); margin: 0 0 1rem; max-width: 46ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: .6rem; }
.hero-meta { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }

.levels { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); margin-top: 1.5rem; }
.level-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.1rem; }
.level-card h3 { margin: 0; font-size: .95rem; letter-spacing: -.01em; }
.level-num { font-family: var(--serif); font-size: 2rem; line-height: 1; letter-spacing: -.03em; }
.level-num small { font-size: .95rem; color: var(--muted); font-family: var(--ui); letter-spacing: 0; }
.meter { height: 6px; border-radius: 999px; background: var(--surface-2); overflow: hidden; margin: .7rem 0 .4rem; }
.meter > i { display: block; height: 100%; background: var(--accent); border-radius: inherit; }

.tile-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.tile {
  display: flex; align-items: center; gap: .75rem;
  padding: .7rem .9rem; border: 1px solid var(--line); border-radius: 9px;
  background: var(--surface); text-decoration: none; color: var(--ink);
}
.tile:hover { border-color: var(--line-strong); background: var(--surface-2); }
.tile-title { font-weight: 550; }
.tile-meta { margin-left: auto; }

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

.filters { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; margin-bottom: 1.5rem; }
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--surface); }
.seg a { padding: .4rem .85rem; font-size: .875rem; font-weight: 550; color: var(--muted); text-decoration: none; border-right: 1px solid var(--line); }
.seg a:last-child { border-right: 0; }
.seg a[aria-current="true"] { background: var(--accent); color: var(--accent-ink); }
input[type="search"], select {
  font: inherit; font-size: .9rem; color: var(--ink); background: var(--surface);
  border: 1px solid var(--line-strong); border-radius: 8px; padding: .42rem .7rem;
}
.cat-block { margin-bottom: 2rem; }
.cat-head { display: flex; align-items: baseline; gap: .6rem; margin: 0 0 .75rem; padding-bottom: .4rem; border-bottom: 1px solid var(--line); }
.cat-head h2 { font-size: 1rem; margin: 0; letter-spacing: -.01em; }
.grid-articles { display: grid; gap: .4rem; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); }
.art-link {
  display: flex; align-items: center; gap: .6rem; padding: .5rem .7rem;
  border-radius: 7px; text-decoration: none; color: var(--ink); font-size: .925rem;
  border: 1px solid transparent;
}
.art-link:hover { background: var(--surface); border-color: var(--line); }
.check { width: 1.05rem; height: 1.05rem; border-radius: 50%; border: 1.5px solid var(--line-strong); flex: none; display: grid; place-items: center; font-size: .65rem; color: transparent; }
.art-link.is-done .check { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.art-link.is-done .art-name { color: var(--muted); }
.art-sub { margin-left: auto; color: var(--muted); font-size: .74rem; }

/* --- reader ------------------------------------------------------------- */

/* One column of markup, three possible layouts.

   narrow      everything stacked
   >= 62rem    contents | article        (notes fall under the contents)
   >= 86rem    contents | article | notes

   The rail moves between column 1 and column 3 by grid placement alone, so
   the panel exists once in the DOM and htmx keeps swapping the same node. */
.reader {
  max-width: 78rem; margin: 0 auto; padding: 0 1.25rem 5rem;
  display: grid; grid-template-columns: 1fr; gap: 2.5rem;
}
@media (min-width: 62rem) {
  .reader {
    grid-template-columns: 15rem minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    padding-top: 1rem;
  }
  .reader > .toc     { grid-column: 1; grid-row: 1; }
  .reader > .rail    { grid-column: 1; grid-row: 2; }
  .reader > article  { grid-column: 2; grid-row: 1 / span 2; }
}
@media (min-width: 86rem) {
  /* Three rails need the width for it; below this the measure would be
     squeezed under the reader's own comfortable line length. */
  .reader {
    max-width: 90rem;
    grid-template-columns: 14rem minmax(0, 1fr) 16rem;
    grid-template-rows: 1fr;
    column-gap: 2rem;
  }
  .reader > .toc     { grid-column: 1; grid-row: 1; }
  .reader > article  { grid-column: 2; grid-row: 1; }
  .reader > .rail    { grid-column: 3; grid-row: 1; position: relative; }
}

/* Table of contents rail */
.toc { display: none; }
@media (min-width: 62rem) {
  .toc {
    display: block; position: sticky; top: 4.5rem; align-self: start;
    max-height: calc(100vh - 6rem); overflow-y: auto; padding-right: .5rem;
    font-size: .84rem; scrollbar-width: thin;
  }
}
.toc h2 { font-size: .7rem; letter-spacing: .11em; text-transform: uppercase; color: var(--muted); margin: 0 0 .6rem; }
.toc ol { list-style: none; margin: 0; padding: 0; border-left: 2px solid var(--line); }
.toc li a {
  display: block; padding: .28rem .7rem; color: var(--muted); text-decoration: none;
  border-left: 2px solid transparent; margin-left: -2px; line-height: 1.35;
}
.toc li a:hover { color: var(--ink); }
.toc li a.is-active { color: var(--accent); border-left-color: var(--accent); font-weight: 600; }
.toc .lvl-3 a { padding-left: 1.5rem; font-size: .8rem; }
.toc .lvl-4 a { padding-left: 2.3rem; font-size: .78rem; }

/* Narrow screens lose the sticky rail, so long articles need some other way
   to jump between sections. Hidden once the rail appears. */
.toc-mobile { margin: 0 0 2rem; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); }
.toc-mobile > summary {
  cursor: pointer; padding: .7rem .9rem; font-size: .82rem; font-weight: 650;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted); list-style: none;
}
.toc-mobile > summary::-webkit-details-marker { display: none; }
.toc-mobile > summary::after { content: " ▾"; }
.toc-mobile[open] > summary { border-bottom: 1px solid var(--line); }
.toc-mobile[open] > summary::after { content: " ▴"; }
.toc-mobile ol { list-style: none; margin: 0; padding: .5rem 0; max-height: 55vh; overflow-y: auto; }
.toc-mobile li a { display: block; padding: .4rem 1rem; color: var(--ink-soft); text-decoration: none; font-size: .9rem; }
.toc-mobile li a:hover { background: var(--surface-2); }
.toc-mobile .lvl-3 a { padding-left: 2rem; font-size: .85rem; color: var(--muted); }
.toc-mobile .lvl-4 a { padding-left: 3rem; font-size: .82rem; color: var(--muted); }
@media (min-width: 62rem) { .toc-mobile { display: none; } }

.article-head { margin: 1.5rem 0 2rem; }
.crumbs { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; margin-bottom: .9rem; }
.article-head h1 {
  font-family: var(--serif); font-size: clamp(2rem, 5vw, 3rem); line-height: 1.08;
  letter-spacing: -.025em; margin: 0 0 .7rem; font-weight: 600;
}
.byline { display: flex; flex-wrap: wrap; gap: 1rem; color: var(--muted); font-size: .85rem; }

/* The article body itself. Size, measure and leading are driven by custom
   properties so the reading-settings panel can change them without any of the
   rules below needing to know about it. */
.prose {
  font-family: var(--serif);
  font-size: calc(1.15rem * var(--reader-scale, 1));
  line-height: var(--reader-leading, 1.72);
  color: var(--ink-soft);
  max-width: var(--reader-measure, var(--measure));
}
:root[data-face="sans"]  .prose { font-family: var(--ui); }
:root[data-face="serif"] .prose { font-family: var(--serif); }
/* Lead paragraph only -- `> section >` alone hit the opening paragraph of
   every section, which emphasised thirty-odd paragraphs instead of one. */
.prose > section:first-of-type > p:first-of-type { font-size: 1.24rem; line-height: 1.65; color: var(--ink); }
.prose p { margin: 0 0 1.25em; }
.prose h2, .prose h3, .prose h4 {
  font-family: var(--ui); color: var(--ink);
  letter-spacing: -.015em; line-height: 1.25;
  margin: 2.4em 0 .7em; scroll-margin-top: 5rem;
}
.prose h2 { font-size: 1.35rem; padding-bottom: .35rem; border-bottom: 1px solid var(--line); }
.prose h3 { font-size: 1.1rem; }
.prose h4 { font-size: .97rem; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }
.prose ul, .prose ol { margin: 0 0 1.25em; padding-left: 1.4em; }
.prose li { margin-bottom: .4em; }
.prose blockquote {
  margin: 1.5em 0; padding: .2em 0 .2em 1.2em;
  border-left: 3px solid var(--accent); color: var(--muted); font-style: italic;
}
.prose code, .prose kbd { font-family: var(--mono); font-size: .86em; background: var(--surface-2); padding: .1em .35em; border-radius: 4px; }
.prose hr { border: 0; border-top: 1px solid var(--line); margin: 2.5em 0; }
/* Links.

   A vital article is roughly 90% outbound links -- Earth carries 696 that
   leave the site against 95 that stay -- so underlining them all made the
   dominant texture of the page a dense field of marks pointing at the exit.
   Internal links keep a visible mark because they are the path through the
   challenge. External ones get the faintest hint that still survives: no
   colour shift at all, and a hairline at 8% of the surrounding ink -- gone
   while you are reading, there if you look for it. Hover brings them up.
   Mixed against currentColor rather than --ink so it stays proportionate in
   every theme, including dark where the ink is the light value. */
.prose a.internal {
  color: var(--accent);
  text-decoration-color: color-mix(in srgb, var(--accent) 38%, transparent);
  text-decoration-thickness: 1px;
  text-underline-offset: .16em;
}
.prose a.internal:hover { text-decoration-color: var(--accent); }

.prose a.external {
  color: inherit;
  text-decoration-color: color-mix(in srgb, currentColor 8%, transparent);
  text-decoration-thickness: 1px;
  text-underline-offset: .16em;
}
.prose a.external:hover {
  color: var(--accent);
  text-decoration-color: color-mix(in srgb, var(--accent) 55%, transparent);
}
.prose a.plain { color: inherit; text-decoration: none; }

/* --------------------------------------------------------------------------
   The drop cap

   Every article opens on a real Lombardic initial. The font is Missaali (SIL
   OFL), a textura recreated glyph by glyph from a facsimile of the Missale
   Aboense that Bartholomeus Ghotan printed in Lubeck in 1488. Its OpenType
   feature `ss20` swaps a capital for the initial form, so the letter in the
   markup stays an ordinary letter: nothing is substituted server side, the
   text is still selectable and a screen reader still reads the word.

   Subset to the 26 initials and the digits, which takes it from 135 KB to 16 KB.

   The selector deliberately targets the first *paragraph*, not the section:
   a Wikipedia lead usually opens with a hatnote, and that is a <div>, so
   `p:first-of-type` steps over it. Worth knowing: the letter this lands on is
   the first letter of the lead sentence, not of the title -- "Abstract
   algebra" opens "In mathematics...", so it gets an I.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Missaali Initials";
  src: url("../fonts/missaali-initials.woff2") format("woff2");
  font-display: swap;
  /* No synthesised bold or italic; there is only one weight and faking it on
     a 15th century textura looks like a smudge. */
  font-weight: 400;
  font-style: normal;
}

/* Gated on data-initial, which the reading settings panel toggles and which is
   also written statically on <html> so the initial survives with no JS. Off
   leaves the letter as ordinary body text -- nothing is substituted in the
   markup either way, so the word stays selectable and readable aloud. */
:root[data-initial="on"] .prose > section:first-of-type > p:first-of-type::first-letter {
  font-family: "Missaali Initials", var(--serif);
  font-feature-settings: "ss20" 1;
  float: left;
  font-size: 3.4em;
  line-height: .78;
  /* Lombardic initials carry their own flourishes well outside the letter box,
     so the right-hand padding is what keeps the first line off the ornament,
     and the top nudge sits the cap on the baseline of the third line. */
  padding: .04em .16em 0 0;
  margin-top: .04em;
  color: var(--accent);
  font-weight: 400;
}
:root[data-theme="classic"][data-initial="on"] .prose > section:first-of-type > p:first-of-type::first-letter {
  color: var(--gold);
}

/* Hatnotes are navigation chrome: "For other uses, see ...". Set as body text
   they meant every article opened on a disambiguation notice instead of on its
   first sentence. Kept, because the in-section "Main article:" ones are
   genuinely useful, but demoted to an aside. */
.prose .hatnote {
  font-family: var(--ui);
  font-size: .82rem;
  line-height: 1.5;
  font-style: italic;
  color: var(--muted);
  margin: 0 0 1.4em;
  padding-left: .85rem;
  border-left: 2px solid var(--line);
  text-align: left;
  hyphens: none;
}
.prose .hatnote a { color: var(--muted); text-decoration-color: transparent; }
.prose .hatnote a:hover { color: var(--accent); text-decoration-color: currentColor; }

/* Figures */
.prose .fig { margin: 2em 0; }
.prose .fig img { max-width: 100%; height: auto; border-radius: 8px; background: var(--surface-2); display: block; }
.prose .fig figcaption {
  font-family: var(--ui); font-size: .82rem; line-height: 1.45;
  color: var(--muted); margin-top: .6rem; padding-left: .1rem;
  border-left: 2px solid var(--line); padding-left: .7rem;
}
.prose video { max-width: 100%; height: auto; border-radius: 8px; }

/* Tables inside the body scroll rather than blowing out the page width. */
.prose table { border-collapse: collapse; font-family: var(--ui); font-size: .86rem; width: 100%; }
.prose .table-scroll { overflow-x: auto; margin: 1.75em 0; border: 1px solid var(--line); border-radius: 8px; }
.prose th, .prose td { padding: .5rem .7rem; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
.prose th { background: var(--surface-2); font-weight: 650; color: var(--ink); }
.prose tr:last-child td { border-bottom: 0; }

/* Reference markers and popovers */
sup.ref { line-height: 0; }
sup.ref a {
  font-family: var(--ui); font-size: .68em; font-weight: 650;
  text-decoration: none; color: var(--accent);
  background: var(--accent-soft); border-radius: 4px;
  padding: .12em .3em; margin-left: .1em; white-space: nowrap;
}
sup.ref a:hover { background: var(--accent); color: var(--accent-ink); }

#note-pop {
  position: absolute; z-index: 60; max-width: min(30rem, calc(100vw - 2rem));
  background: var(--surface); border: 1px solid var(--line-strong);
  border-radius: 9px; box-shadow: var(--shadow);
  padding: .8rem .95rem; font-family: var(--ui); font-size: .84rem; line-height: 1.5;
  color: var(--ink-soft);
}
#note-pop[hidden] { display: none; }
#note-pop a { word-break: break-word; }

/* Infobox, lifted out of the text flow */
.infobox-card { margin: 0 0 2rem; }
@media (min-width: 62rem) {
  .infobox-card {
    float: right; width: 20rem; margin: .3rem 0 1.5rem 2rem;
    /* Kept from when .prose-wrap was positioned and its box overlapped this
       float, swallowing every click on the summary. The notes moved to their
       own column and prose-wrap is a plain wrapper again, so the conflict is
       gone -- this stays as cheap insurance against it coming back. */
    position: relative;
    z-index: 1;
  }
}
.infobox-card > summary {
  cursor: pointer; font-size: .8rem; font-weight: 650; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted); padding: .6rem .9rem;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 8px;
  list-style: none;
}
.infobox-card > summary::-webkit-details-marker { display: none; }
.infobox-card > summary::after { content: " ▾"; }
.infobox-card[open] > summary { border-radius: 8px 8px 0 0; }
.infobox-card[open] > summary::after { content: " ▴"; }
.infobox-wrap { border: 1px solid var(--line); border-top: 0; border-radius: 0 0 8px 8px; overflow-x: auto; background: var(--surface); }
table.infobox { width: 100%; border-collapse: collapse; font-family: var(--ui); font-size: .8rem; }
table.infobox caption { padding: .7rem; font-weight: 650; color: var(--ink); }
table.infobox th, table.infobox td { padding: .4rem .6rem; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
table.infobox th { color: var(--muted); font-weight: 600; width: 40%; }
table.infobox img { max-width: 100%; height: auto; }

/* Footnotes at the end */
.notes { margin-top: 3.5rem; padding-top: 1.5rem; border-top: 1px solid var(--line); clear: both; }
.notes > summary { cursor: pointer; font-weight: 650; font-size: .9rem; color: var(--muted); }
.notes-list { list-style: none; margin: 1rem 0 0; padding: 0; font-size: .82rem; line-height: 1.5; color: var(--muted); }
.note { display: grid; grid-template-columns: 2rem 1fr; gap: .5rem; padding: .45rem 0; border-bottom: 1px solid var(--line); scroll-margin-top: 6rem; }
.note:target { background: var(--accent-soft); border-radius: 6px; }
.note-num { color: var(--accent); font-weight: 650; font-variant-numeric: tabular-nums; }
.note-body a { word-break: break-word; }

/* End-of-article call to action */
.cta {
  clear: both; margin-top: 3.5rem; padding: 1.75rem;
  background: var(--surface); border: 1px solid var(--line);
  border-left: 3px solid var(--accent); border-radius: var(--radius);
}
.cta h2 { margin: 0 0 .4rem; font-size: 1.15rem; letter-spacing: -.01em; }
.cta p { margin: 0 0 1.1rem; color: var(--muted); max-width: 52ch; }
.cta-done { border-left-color: var(--gold); }

/* Binding colours for the classic theme. All three are period-correct cloth
   and leather colours; each keeps the gold hairline, which is what actually
   reads as "book spine". Only meaningful when data-theme is classic. */
:root[data-theme="classic"][data-binding="green"] {
  --accent: #24472f; --accent-soft: #dbe3d7;
}
:root[data-theme="classic"][data-binding="navy"] {
  --accent: #21395c; --accent-soft: #d8dfea;
}

/* ==========================================================================
   Classic theme -- typographic treatment
   Scoped to the reading surface. The chrome only takes the palette; making
   buttons and forms imitate bookbinding is exactly what tips this into kitsch.
   ========================================================================== */

/* The binding: an oxblood spine with a hairline of gold. This is the only
   place "leather" is evoked, and it is done with colour alone. */
:root[data-theme="classic"] .topbar {
  background: var(--accent);
  border-bottom: 2px solid var(--gold);
  backdrop-filter: none;
}
:root[data-theme="classic"] .brand,
:root[data-theme="classic"] .nav a,
:root[data-theme="classic"] .icon-btn,
:root[data-theme="classic"] .link-btn { color: #e8dcc4; }
:root[data-theme="classic"] .brand { font-variant-caps: small-caps; letter-spacing: .06em; }
:root[data-theme="classic"] .nav a:hover,
:root[data-theme="classic"] .icon-btn:hover,
:root[data-theme="classic"] .link-btn:hover { background: rgba(255,255,255,.1); color: #fff; }
:root[data-theme="classic"] .nav a[aria-current="page"] { background: rgba(0,0,0,.22); color: #f0e4cc; }
/* Classic puts the bar in the hardest place it could be: the topbar is oxblood
   with a 2px gold bottom border, and the bar sits directly on top of that
   border. A 2px gold fill against a permanent gold rule is not a progress
   indicator, it is a slightly thicker line -- which is exactly how it read.
   So: taller, on a track darkened well below the oxblood, and filled with a
   gold lifted clear of the border's. */
/* The track was `--accent` mixed with black, which is the same hue as the
   topbar it sits on -- so on the green binding the unfilled part vanished into
   the board above it and the whole thing read as a hairline. A flat warm
   near-black instead, which is off-hue from all three bindings, plus a light
   inner edge so the channel is legible even where it is empty. */
:root[data-theme="classic"] .readbar {
  height: 9px;
  background: #17100a;
  /* Light edge on top, where the channel meets the board, dark below. The
     first version had these the other way round -- a dark hairline against a
     dark binding, which separated nothing. */
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .20), inset 0 -1px 0 rgb(0 0 0 / .5);
}
:root[data-theme="classic"] .readbar > i {
  background: linear-gradient(180deg, #e8c88a 0%, var(--gold) 55%, #6d4a10 100%);
}

/* Text figures throughout the reading surface: numerals that sit on the
   baseline with ascenders and descenders, as they do in a printed book. */
:root[data-theme="classic"] .prose,
:root[data-theme="classic"] .article-head,
:root[data-theme="classic"] .notes { font-variant-numeric: oldstyle-nums; }

/* Classic runs slightly larger and tighter-leaded than the default, but must
   still honour the reader's own size and leading choices. */
:root[data-theme="classic"] .prose {
  font-size: calc(1.18rem * var(--reader-scale, 1));
  line-height: var(--reader-leading, 1.62);
  color: var(--ink-soft);
}

/* Books indent; web pages space. Indenting only consecutive paragraphs keeps
   the rhythm without collapsing the space around figures and lists. */
:root[data-theme="classic"] .prose p { margin-bottom: .35em; }
:root[data-theme="classic"] .prose p + p { text-indent: 1.5em; }

/* Justify only where the measure is wide enough to avoid rivers. */
@media (min-width: 44rem) {
  :root[data-theme="classic"] .prose p { text-align: justify; hyphens: auto; }
}

/* The opening: a small-caps first line, the standard way a chapter or a long
   entry begins. The drop cap itself is no longer part of this block -- it is
   set in Missaali on every theme, further up the file. */
:root[data-theme="classic"] .prose > section:first-of-type > p:first-of-type::first-line {
  font-variant-caps: small-caps;
  letter-spacing: .015em;
}
:root[data-theme="classic"] .prose > section:first-of-type > p:first-of-type { text-indent: 0; }

/* Section heads: letterspaced small caps over a gold hairline. */
:root[data-theme="classic"] .prose h2,
:root[data-theme="classic"] .prose h3,
:root[data-theme="classic"] .prose h4 {
  font-family: var(--serif);
  font-variant-caps: small-caps;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: none;
  color: var(--ink);
}
:root[data-theme="classic"] .prose h2 {
  font-size: 1.28rem;
  border-bottom: 1px solid var(--gold);
  padding-bottom: .3rem;
  margin-top: 2.6em;
}
:root[data-theme="classic"] .prose h3 { font-size: 1.06rem; letter-spacing: .05em; }
:root[data-theme="classic"] .prose h4 { font-size: .95rem; color: var(--muted); }

/* One ornament, used once: a short centred gold rule for section breaks. */
:root[data-theme="classic"] .prose hr {
  border: 0; height: 1px; width: 28%;
  background: var(--gold); margin: 2.6em auto;
}

/* Article head reads as a title page rather than a web header. */
:root[data-theme="classic"] .article-head h1 {
  font-weight: 500;
  letter-spacing: -.005em;
}
:root[data-theme="classic"] .byline {
  font-family: var(--serif);
  font-variant-caps: small-caps;
  letter-spacing: .05em;
  font-size: .95rem;
  padding-top: .5rem;
  border-top: 1px solid var(--line);
}
/* Breadcrumbs become a running head: plain letterspaced small caps, no chips. */
:root[data-theme="classic"] .crumbs .pill {
  background: none; border: 0; padding: 0 .55rem 0 0;
  font-family: var(--serif); font-size: .82rem; font-weight: 500;
  font-variant-caps: small-caps; letter-spacing: .09em; text-transform: none;
  color: var(--muted);
}
:root[data-theme="classic"] .crumbs .pill-done { color: var(--accent); }

/* Rules, not cards. */
:root[data-theme="classic"] .cta,
:root[data-theme="classic"] .infobox-card > summary,
:root[data-theme="classic"] .toc-mobile { box-shadow: none; }
:root[data-theme="classic"] .toc h2,
:root[data-theme="classic"] .infobox-card > summary,
:root[data-theme="classic"] .notes > summary {
  font-family: var(--serif); font-variant-caps: small-caps;
  text-transform: none; letter-spacing: .08em; font-weight: 600;
}
:root[data-theme="classic"] .toc li a { font-family: var(--serif); }
:root[data-theme="classic"] .toc li a.is-active { color: var(--accent); border-left-color: var(--accent); }
:root[data-theme="classic"] sup.ref a {
  background: none; color: var(--accent); font-family: var(--serif);
  font-weight: 600; padding: 0 .1em;
}
:root[data-theme="classic"] sup.ref a:hover { color: var(--gold); background: none; text-decoration: underline; }

/* Quiz keeps the palette and the small caps, but stays a form, not a folio. */
:root[data-theme="classic"] .quiz-stem { font-weight: 500; }
:root[data-theme="classic"] .quiz-eyebrow,
:root[data-theme="classic"] .hero-eyebrow,
:root[data-theme="classic"] .cat-head h2 {
  font-family: var(--serif); font-variant-caps: small-caps;
  text-transform: none; letter-spacing: .09em;
}
:root[data-theme="classic"] .verdict-score,
:root[data-theme="classic"] .level-num { font-variant-numeric: oldstyle-nums; }

/* --- flash messages ----------------------------------------------------- */

.messages { max-width: 52rem; margin: 1.25rem auto 0; padding: 0 1.25rem; }
.flash { margin: 0 0 .5rem; padding: .7rem .9rem; border-radius: 8px; font-size: .88rem;
         background: var(--surface-2); border: 1px solid var(--line); color: var(--ink-soft); }
.flash-warning { background: color-mix(in srgb, var(--gold) 14%, transparent); border-color: transparent; }
.flash-error { background: color-mix(in srgb, var(--danger) 13%, transparent); border-color: transparent; color: var(--danger); }

/* --- quiz --------------------------------------------------------------- */

.quiz { padding-top: 2.5rem; }
.quiz-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1.5rem; }
.quiz-eyebrow { margin: 0; font-size: .8rem; font-weight: 650; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.quiz-dots { display: flex; align-items: center; gap: .35rem; }
.dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--line-strong); }
.dot-done { background: var(--accent); }
.dot-now { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.quiz-dots .tiny { margin-left: .4rem; }

.quiz-card { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 2rem; box-shadow: var(--shadow); }
.quiz-stem {
  font-family: var(--serif); font-size: clamp(1.3rem, 3.2vw, 1.7rem); line-height: 1.35;
  letter-spacing: -.015em; font-weight: 600; margin: 0 0 1.75rem; color: var(--ink);
}

.choices { display: grid; gap: .6rem; }
/* No transition on border-color/background here. With a `transition` declared,
   the `:has(input:checked)` state change below fails to repaint those two
   properties (box-shadow, which is not transitioned, still applies) -- so the
   selected option showed only its inner ring and kept an unselected border.
   Selection feedback should be instant on a quiz anyway. */
.choice {
  display: flex; align-items: flex-start; gap: .85rem; cursor: pointer;
  padding: .9rem 1rem; border: 1px solid var(--line-strong); border-radius: 10px;
  background: var(--surface);
}
.choice:hover { border-color: var(--accent); background: var(--accent-soft); }
.choice input { position: absolute; opacity: 0; width: 0; height: 0; }
.choice:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent); }
.choice:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.choice-key {
  flex: none; width: 1.6rem; height: 1.6rem; border-radius: 6px;
  display: grid; place-items: center; font-size: .78rem; font-weight: 700;
  background: var(--surface-2); color: var(--muted); border: 1px solid var(--line);
}
.choice:has(input:checked) .choice-key { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
.choice-text { line-height: 1.45; padding-top: .1rem; }

.quiz-actions { margin-top: 1.75rem; padding-top: 1.25rem; border-top: 1px solid var(--line); }
.quiz-actions .btn { width: 100%; padding: .8rem; }
.quiz-actions p { margin: .8rem 0 0; text-align: center; }

/* Result */
.verdict {
  display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--line);
  border-left: 4px solid var(--line-strong); border-radius: 14px;
  padding: 1.75rem; box-shadow: var(--shadow);
}
.verdict-pass { border-left-color: var(--accent); }
.verdict-fail { border-left-color: var(--danger); }
.verdict-score { font-family: var(--serif); font-size: 3.4rem; line-height: 1; margin: 0; letter-spacing: -.03em; }
.verdict-score small { font-size: 1.3rem; color: var(--muted); }
.verdict-pass .verdict-score { color: var(--accent); }
.verdict-fail .verdict-score { color: var(--danger); }
.verdict h1 { font-size: 1.35rem; margin: 0 0 .35rem; letter-spacing: -.015em; }
.verdict p { margin: 0; color: var(--muted); max-width: 46ch; }
.verdict-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin-top: 1.25rem; }

.review-head { font-size: 1rem; margin: 2.75rem 0 1rem; padding-bottom: .4rem; border-bottom: 1px solid var(--line); }
.review { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; }
.review-item {
  display: grid; grid-template-columns: 1.9rem 1fr; gap: .9rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 11px; padding: 1.1rem 1.25rem;
}
.review-mark { width: 1.6rem; height: 1.6rem; border-radius: 50%; display: grid; place-items: center; font-size: .82rem; font-weight: 700; }
.is-right .review-mark { background: var(--accent-soft); color: var(--accent); }
.is-wrong .review-mark { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }
.review-stem { font-weight: 600; margin: .1rem 0 .8rem; line-height: 1.4; }
.review-options { list-style: none; margin: 0 0 .8rem; padding: 0; display: grid; gap: .3rem; font-size: .88rem; }
.review-options li { display: flex; align-items: baseline; gap: .55rem; padding: .35rem .55rem; border-radius: 6px; color: var(--muted); }
.opt-key { font-size: .7rem; font-weight: 700; color: var(--muted); min-width: .9rem; }
.opt-correct { background: var(--accent-soft); color: var(--ink); }
.opt-correct .opt-key { color: var(--accent); }
.opt-chosen { background: color-mix(in srgb, var(--danger) 11%, transparent); color: var(--ink); }
.opt-tag { margin-left: auto; font-size: .68rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--accent); }
.opt-tag-wrong { color: var(--danger); }
.review-why { font-size: .88rem; line-height: 1.55; color: var(--ink-soft); margin: 0 0 .5rem;
              padding-left: .75rem; border-left: 2px solid var(--line); }

/* --- auth --------------------------------------------------------------- */

/* --- account pages ------------------------------------------------------ */

.card-head {
  font-size: .72rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 .75rem;
}
.facts { list-style: none; margin: 0; padding: 0; display: grid; gap: .35rem; font-size: .92rem; }
.facts li { display: flex; gap: .6rem; align-items: baseline; color: var(--ink-soft); }
.facts li span {
  min-width: 2.6rem; text-align: right; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.card .stack > * + *, .card.stack > * + * { margin-top: .9rem; }
.card label { display: block; font-size: .85rem; font-weight: 600; margin-bottom: .3rem; }
.card input[type="text"], .card input[type="email"], .card input[type="password"] {
  width: 100%; max-width: 22rem; font: inherit; padding: .5rem .7rem; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line-strong); border-radius: 8px;
}
.card .helptext { display: block; font-size: .78rem; color: var(--muted); margin-top: .25rem; }

.danger-zone { border-color: color-mix(in srgb, var(--danger) 40%, var(--line)); }
.danger-zone .card-head { color: var(--danger); }
.btn-danger { background: var(--danger); color: #fff; }
.btn-danger:hover { filter: brightness(1.1); }
.delete-actions { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; }

.auth { max-width: 24rem; margin: 4rem auto; }
.auth h1 { font-family: var(--serif); font-size: 1.8rem; margin: 0 0 .3rem; letter-spacing: -.02em; }
.auth form { margin-top: 1.5rem; }
.auth p { margin: 0 0 1rem; }
.auth label { display: block; font-size: .85rem; font-weight: 600; margin-bottom: .3rem; }
.auth input {
  width: 100%; font: inherit; padding: .55rem .7rem; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line-strong); border-radius: 8px;
}
.auth .helptext, .auth ul { font-size: .78rem; color: var(--muted); }
.auth ul { padding-left: 1.1rem; }
.auth .btn { width: 100%; margin-top: .5rem; }
.errorlist { list-style: none; padding: .6rem .8rem; margin: 0 0 1rem; background: color-mix(in srgb, var(--danger) 12%, transparent); border-radius: 8px; color: var(--danger); font-size: .84rem; }

/* --- footer ------------------------------------------------------------- */

.footsite { border-top: 1px solid var(--line); padding: 1.5rem 1.25rem 3rem; }
.footsite p { max-width: 78rem; margin: 0 auto; color: var(--muted); font-size: .8rem; }

.empty { text-align: center; padding: 3rem 1rem; color: var(--muted); }

/* ==========================================================================
   Reading settings panel
   ========================================================================== */

/* Justification is a three-state setting: "auto" lets the theme decide (only
   classic justifies), on/off override it. These must come after the classic
   block -- specificity is equal, so source order is what decides. */
@media (min-width: 44rem) {
  :root[data-justify="on"] .prose p { text-align: justify; hyphens: auto; }
}
:root[data-justify="off"] .prose p { text-align: start; hyphens: manual; }

.settings-wrap { position: relative; }

/* The panel lives outside <header> in the DOM. The topbar carries a
   backdrop-filter, and like transform and filter that establishes a containing
   block for fixed-position descendants -- anchored inside it, the panel
   positioned itself against the header box instead of the viewport and flew
   off the top of the screen. */
.settings {
  /* Align to the topbar's centred content column rather than the viewport
     edge, so the panel lines up under the button that opens it. Mirrors
     .topbar-inner's max-width and padding. */
  position: fixed; top: 3.4rem; z-index: 60;
  /* `%` rather than `vw`: on a fixed element percentages resolve against the
     viewport excluding the scrollbar, which is what the centred layout uses.
     With vw the panel sat half a scrollbar-width off. */
  right: max(1.25rem, calc((100% - 78rem) / 2 + 1.25rem));
  width: min(19rem, calc(100% - 2.5rem));
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: 10px;
  box-shadow: 0 1px 2px rgba(0,0,0,.06), 0 12px 32px -10px rgba(0,0,0,.28);
  padding: .75rem; font-family: var(--ui); font-size: .85rem;
  text-align: left;
  /* Seven groups is taller than a short laptop viewport, so the panel scrolls
     inside itself rather than running off the bottom of the screen. */
  max-height: calc(100vh - 4.25rem);
  max-height: calc(100dvh - 4.25rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.settings[hidden] { display: none; }
.set-group + .set-group { margin-top: .6rem; padding-top: .6rem; border-top: 1px solid var(--line); }
.set-label {
  display: block; font-size: .66rem; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted); margin-bottom: .35rem;
}
.set-row { display: flex; flex-wrap: wrap; gap: .25rem; }
.set-row button {
  flex: 1 1 auto; min-width: 2.5rem; font: inherit; font-size: .78rem; font-weight: 550;
  padding: .32rem .45rem; cursor: pointer; color: var(--ink-soft);
  background: var(--surface-2); border: 1px solid transparent; border-radius: 6px;
}

/* Narrow or short viewports get a bottom sheet instead: anchoring a 600px
   dropdown under a sticky bar leaves nowhere for it to go. */
@media (max-width: 34rem), (max-height: 38rem) {
  .settings {
    left: .6rem; right: .6rem; top: auto; bottom: .6rem;
    width: auto; max-height: min(78vh, 78dvh); border-radius: 12px;
  }
}
.set-row button:hover { border-color: var(--line-strong); }
.set-row button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }

/* Binding swatches show the actual colour, so the choice is visible. */
.set-row.swatches button { min-width: 0; height: 1.9rem; position: relative; }
.set-row.swatches button[data-value="oxblood"] { background: #6d2233; }
.set-row.swatches button[data-value="green"]   { background: #24472f; }
.set-row.swatches button[data-value="navy"]    { background: #21395c; }
.set-row.swatches button[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--gold); }
.set-binding[hidden] { display: none; }

.set-sample {
  margin: .6rem 0 0; padding-top: .6rem; border-top: 1px solid var(--line);
  font-family: var(--serif); color: var(--ink-soft);
  font-size: calc(.9rem * var(--reader-scale, 1));
  line-height: var(--reader-leading, 1.55);
}
:root[data-face="sans"] .set-sample { font-family: var(--ui); }

@media print {
  .topbar, .toc, .cta, .footsite, .notes > summary { display: none; }
  .prose { max-width: none; font-size: 11pt; }
}

/* --------------------------------------------------------------------------
   Reporting a bad question
   Quiet by default: a row of accusatory buttons under every question would
   invite noise, and most questions are fine. It opens into a form only when
   somebody actually has a complaint.
   -------------------------------------------------------------------------- */

.flagbox { margin-top: .6rem; }

.flag-details > summary {
  display: inline-block;
  font-size: .8rem;
  color: var(--muted);
  cursor: pointer;
  padding: .15rem 0;
  list-style: none;
}
.flag-details > summary::-webkit-details-marker { display: none; }
.flag-details > summary::before { content: "⚑"; margin-right: .35rem; opacity: .7; }
.flag-details > summary:hover { color: var(--danger); }
.flag-details[open] > summary { color: var(--ink-soft); margin-bottom: .5rem; }

.flag-details form {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: .75rem .85rem;
}

.flag-reasons { border: 0; margin: 0 0 .6rem; padding: 0; }
.flag-reasons legend { padding: 0; margin-bottom: .35rem; }

.flag-reason {
  display: flex;
  align-items: center;
  gap: .45rem;
  padding: .2rem 0;
  font-size: .88rem;
  cursor: pointer;
}
.flag-reason input { accent-color: var(--danger); }

.flag-note { display: block; margin-bottom: .6rem; }
.flag-note textarea {
  display: block;
  width: 100%;
  margin-top: .25rem;
  padding: .45rem .55rem;
  font: inherit;
  font-size: .88rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  resize: vertical;
}
.flag-note textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.flag-done {
  font-size: .82rem;
  color: var(--muted);
  margin: 0;
}
.flag-done span { color: var(--accent); font-weight: 700; }

.flag-error {
  font-size: .82rem;
  color: var(--danger);
  margin: 0 0 .5rem;
}

.btn-small { padding: .3rem .7rem; font-size: .82rem; }

/* --------------------------------------------------------------------------
   Bookmarks
   -------------------------------------------------------------------------- */

.marks {
  position: relative;
  z-index: 1;
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
@media (min-width: 86rem) {
  /* Head of its own column now, not an afterthought below the contents.
     The aligned cards below *are* the list, so the panel's copy of it would
     say everything twice -- it keeps only the count and the save button. */
  .marks { margin-top: 0; padding-top: 0; border-top: 0; }
  .marks .mark-list { display: none; }
  .marks .marks-empty { margin-bottom: 1rem; }
}

.marks h2 {
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--muted);
  margin: 0 0 .6rem;
  display: flex;
  align-items: center;
  gap: .4rem;
}

.marks-count {
  font-size: .7rem;
  background: var(--accent-soft);
  color: var(--accent);
  border-radius: 999px;
  padding: .05rem .4rem;
  letter-spacing: 0;
}

.marks-empty { margin: 0 0 .75rem; line-height: 1.5; }

.mark-list { list-style: none; margin: 0 0 .75rem; padding: 0; }

.mark {
  position: relative;
  padding: .5rem 0;
  border-bottom: 1px dashed var(--line);
}
.mark:last-child { border-bottom: 0; }

.mark blockquote {
  margin: 0;
  padding-left: .6rem;
  border-left: 2px solid var(--gold);
  font-size: .84rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

/* A quote whose text no longer appears in the article -- Wikipedia edited it
   out from under the bookmark. Kept and dimmed rather than deleted, because
   the reader may still recognise what they meant. */
.mark-lost blockquote { opacity: .55; border-left-style: dotted; }
.mark-lost::after {
  content: "no longer in the article";
  display: block;
  font-size: .7rem;
  color: var(--muted);
  padding-left: .6rem;
  margin-top: .2rem;
}

.mark-whole-label {
  margin: 0;
  font-size: .84rem;
  color: var(--ink-soft);
}
.mark-whole-label::before { content: "★ "; color: var(--gold); }

.mark-note {
  margin: .3rem 0 0 .6rem;
  font-size: .78rem;
  color: var(--muted);
  font-style: italic;
}

.mark-remove {
  background: none;
  border: 0;
  padding: .15rem 0;
  margin-top: .2rem;
  font: inherit;
  font-size: .72rem;
  color: var(--muted);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--line-strong);
}
.mark-remove:hover { color: var(--danger); }

/* The highlight left in the prose itself.

   Two states, because the passage has two jobs. At rest it only has to say
   "you marked this", and a gold rule says it without turning the article into
   a revision sheet -- a reader scanning the page reads text, not stripes. The
   tint is spent on the second job: showing *which* note goes with which
   passage, and only while you are asking.

   A real underline rather than the border-bottom this used to be. `border`
   draws under descenders and sits at the bottom of the line box; a text
   decoration sits at a chosen offset and `skip-ink` steps around the g and y
   on its own. It also fragments correctly when a passage wraps across lines,
   which is the normal case and the one a box-shadow handled worst. */
mark.mark-hit {
  /* The user-agent default for <mark> is highlighter yellow. Resetting it is
     not optional; leave it out and every marked passage turns fluorescent. */
  background-color: transparent;
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--gold);
  text-decoration-thickness: 1px;
  text-underline-offset: .22em;
  text-decoration-skip-ink: auto;
  border-radius: 2px;
  box-decoration-break: clone;
  transition: background-color .22s ease;
}

/* A link inside a marked passage would otherwise carry its own rule as well,
   at a different offset -- two lines under one phrase. Colour still tells you
   it is a link, and the gold rule already spans it. */
mark.mark-hit a { text-decoration: none; }

/* Lit: this passage is the one under the pointer, or the one whose note is.
   app.js pairs them in both directions. */
mark.mark-hit:hover,
mark.mark-hit.is-lit {
  background-color: color-mix(in srgb, var(--gold) 13%, transparent);
}

/* The floating control that appears over a selection. */
#select-pop {
  position: absolute;
  z-index: 60;
  background: var(--ink);
  border-radius: 6px;
  box-shadow: var(--shadow);
  padding: 0;
}
#select-pop[hidden] { display: none; }
#select-pop button {
  background: none;
  border: 0;
  color: #fff;
  font: inherit;
  font-size: .78rem;
  padding: .4rem .7rem;
  cursor: pointer;
  white-space: nowrap;
}
#select-pop button:hover { color: var(--gold); }
#select-pop::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -4px;
  width: 8px;
  height: 8px;
  margin-left: -4px;
  background: var(--ink);
  transform: rotate(45deg);
}

/* The library page. */
.lib-article { margin-bottom: 2rem; }
.lib-article h2 { font-size: 1.05rem; margin: 0 0 .5rem; }
.lib-article h2 a { color: var(--ink); text-decoration: none; }
.lib-article h2 a:hover { color: var(--accent); }
.mark-list-library .mark { padding: .7rem 0; }
.mark-list-library blockquote { font-size: .92rem; }

.page-head { margin-bottom: 1.5rem; }
.page-head h1 { margin: 0 0 .25rem; }

.empty-state {
  text-align: center;
  padding: 3rem 1rem;
  color: var(--muted);
}
.empty-state p { margin-bottom: 1rem; }

/* --------------------------------------------------------------------------
   The notes rail
   Its own column on a wide screen, so notes sit beside the lines they belong
   to rather than in the reader's peripheral vision on the wrong side. Every
   saved passage gets a rule spanning its own height, which turns the rail
   into an index of what you marked in this article.
   -------------------------------------------------------------------------- */

.rail { min-width: 0; }

/* Below the three-column breakpoint the rail is just the panel, stacked under
   the contents -- there is nowhere to align a note to. */
.marginalia { display: none; }

@media (min-width: 86rem) {
  .marginalia {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    /* Starts below the panel; app.js measures offsets from here, so a note
       never lands behind it. */
    top: 0;
    pointer-events: none;
  }
}

.marginal {
  position: absolute;
  width: 100%;
  pointer-events: auto;
  cursor: pointer;
  padding-left: .85rem;
  border-left: 2px solid color-mix(in srgb, var(--gold) 45%, transparent);
  transition: border-color .25s ease;
}
/* `is-lit` is the other half of the pairing: set by app.js when the pointer is
   over this note's passage, so the rule answers from the margin. */
.marginal:hover,
.marginal.is-lit,
.marginal.is-flash { border-left-color: var(--gold); }

/* A passage saved without a note shows only its rule: a quiet mark in the
   margin that says "you stopped here" without asking for anything. */
.marginal-bare {
  border-left-color: color-mix(in srgb, var(--gold) 26%, transparent);
}

.marginal-note {
  margin: 0;
  font-family: var(--serif);
  font-size: .85rem;
  font-style: italic;
  line-height: 1.5;
  color: var(--muted);
  hyphens: none;
  text-align: left;
}
.marginal:hover .marginal-note { color: var(--ink-soft); }

.marginal-add {
  display: block;
  margin-top: .15rem;
  padding: 0;
  background: none;
  border: 0;
  font-family: var(--ui);
  font-size: .68rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity .18s ease;
}
.marginal:hover .marginal-add,
.marginal-add:focus-visible { opacity: 1; }
.marginal-add:hover { color: var(--accent); }

.marginal-actions { display: flex; gap: .7rem; }
.marginal-remove {
  display: block;
  margin-top: .15rem;
  padding: 0;
  background: none;
  border: 0;
  font-family: var(--ui);
  font-size: .68rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity .18s ease;
}
.marginal:hover .marginal-remove,
.marginal-remove:focus-visible { opacity: 1; }
.marginal-remove:hover { color: var(--danger); }

/* Answering "where is it" after you click a note in the rail: briefly warmer
   than the lit state, so it registers on a page you have just scrolled. */
mark.mark-hit.is-flash {
  background-color: color-mix(in srgb, var(--gold) 30%, transparent);
}

/* The note composer, floating over the selection. */
#note-composer {
  position: absolute;
  z-index: 61;
  width: min(22rem, calc(100vw - 2rem));
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: .6rem;
}
#note-composer textarea {
  display: block;
  width: 100%;
  font: inherit;
  font-size: .88rem;
  line-height: 1.5;
  color: var(--ink);
  background: transparent;
  border: 0;
  resize: vertical;
  outline: none;
}
.composer-actions {
  display: flex;
  justify-content: flex-end;
  gap: .4rem;
  margin-top: .4rem;
  padding-top: .4rem;
  border-top: 1px solid var(--line);
}
.composer-actions button {
  font: inherit;
  font-size: .8rem;
  padding: .25rem .6rem;
  border-radius: 5px;
  border: 1px solid transparent;
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.composer-actions button:hover { color: var(--ink); }
.composer-actions .is-primary {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
}
.composer-actions .is-primary:hover { color: var(--accent-ink); opacity: .9; }

/* Two-action selection popup. */
#select-pop { display: flex; align-items: stretch; }
#select-pop[hidden] { display: none; }
.select-sep { width: 1px; background: rgba(255, 255, 255, .18); }

/* Editing a note from the sidebar list. */
.note-edit { margin-top: .35rem; }
.note-edit > summary {
  cursor: pointer;
  font-family: var(--serif);
  font-size: .8rem;
  font-style: italic;
  line-height: 1.45;
  color: var(--ink-soft);
  padding-left: .6rem;
  border-left: 1px solid var(--line-strong);
  list-style: none;
}
.note-edit > summary::-webkit-details-marker { display: none; }
.note-edit[data-empty] > summary { color: var(--muted); font-style: normal; font-size: .74rem; }
.note-edit > summary:hover { color: var(--accent); }
.note-edit textarea {
  display: block;
  width: 100%;
  margin-top: .4rem;
  padding: .4rem .5rem;
  font: inherit;
  font-size: .82rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 5px;
  resize: vertical;
}
.note-actions { display: flex; align-items: center; gap: .6rem; margin-top: .35rem; }

/* --------------------------------------------------------------------------
   The saved-passages library
   -------------------------------------------------------------------------- */

.wrap-mid { max-width: 52rem; }

.lib-controls {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  margin-bottom: 2rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--line);
}
.lib-controls input[type="search"] {
  flex: 1 1 10rem;
  max-width: 22rem;
  min-width: 0;
  padding: .4rem .6rem;
  font: inherit;
  font-size: .88rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
}
.lib-controls .seg a.is-on { background: var(--accent); color: var(--accent-ink); }

.lib-article { margin-bottom: 2.5rem; }
.lib-article > h2 {
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--muted);
  margin: 0 0 .75rem;
  padding-bottom: .35rem;
  border-bottom: 1px solid var(--line);
}
.lib-article > h2 a { color: inherit; text-decoration: none; }
.lib-article > h2 a:hover { color: var(--accent); }

.entry {
  padding: .9rem 0 1rem;
  border-bottom: 1px dashed var(--line);
}
.entry:last-child { border-bottom: 0; }

.entry blockquote {
  margin: 0;
  padding-left: .9rem;
  border-left: 2px solid color-mix(in srgb, var(--gold) 45%, transparent);
  font-family: var(--serif);
  font-size: 1rem;
  line-height: 1.6;
  color: var(--ink-soft);
}

/* The note is the thing you came back for, so it reads as the reader's own
   voice: their words in italic against the quoted text above. */
.entry-note {
  margin: .6rem 0 0 .9rem;
  font-family: var(--serif);
  font-size: .95rem;
  font-style: italic;
  line-height: 1.55;
  color: var(--ink);
}

/* A row of actions, all of them the same size and sitting on one baseline.
   `center` looked right until the editor opened and made one item tall; and
   .mark-remove carries a top margin for the reader's rail, where the buttons
   stack, which dropped every button half a line below the links beside it. */
.entry-foot {
  display: flex;
  align-items: baseline;
  gap: .9rem;
  flex-wrap: wrap;
  margin-top: .6rem;
  padding-left: .9rem;
  font-size: .78rem;
}
.entry-foot .inline-form { display: inline; }
.entry-foot .mark-remove { margin: 0; padding: 0; font-size: inherit; }

.entry-edit { position: relative; }
/* The editor is a panel, not a link: opening it takes a line of its own under
   the actions instead of stretching the row it lives in. */
.entry-edit[open] { flex: 1 0 100%; order: 1; }
.entry-edit > summary {
  cursor: pointer;
  color: var(--muted);
  list-style: none;
}
.entry-edit > summary::-webkit-details-marker { display: none; }
.entry-edit > summary:hover { color: var(--accent); }
.entry-edit textarea {
  display: block;
  width: 100%;
  margin-top: .5rem;
  padding: .5rem .6rem;
  font: inherit;
  font-size: .9rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  resize: vertical;
}
.entry-actions {
  display: flex;
  align-items: center;
  gap: .9rem;
  margin-top: .5rem;
}

.entry-whole blockquote { border-left-style: dotted; }

/* Deleting a note keeps the passage, so it should not wear the same warning
   colour as removing the passage itself. */
.entry-actions .note-remove:hover { color: var(--ink); }

.composer-gap { flex: 1; }
.composer-actions .is-danger { color: var(--danger); }
.composer-actions .is-danger:hover { color: var(--danger); text-decoration: underline; }

/* ==========================================================================
   The ribbon -- one bookmark per article, placed on a paragraph

   Distinct from a saved passage, which is a highlight plus a note in the right
   gutter. The ribbon is where the reader stopped: a single mark down the left
   edge of one paragraph, and a pip on the reading bar showing where in the
   article that is.
   ========================================================================== */

.prose { position: relative; }

/* The hover affordance. One button, moved to whichever paragraph is under the
   pointer -- see app.js for why it is not one per paragraph. */
.ribbon-btn {
  position: absolute; right: 0; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem; padding: 0;
  border: 1px solid var(--line-strong); border-radius: 999px;
  background: var(--surface); color: var(--muted);
  cursor: pointer; transition: color .12s ease, border-color .12s ease;
  animation: ribbon-btn-in .12s ease-out;
}
/* `display: flex` above beats the user-agent rule for `[hidden]`, so without
   this the button stays on screen after the script has hidden it -- looking
   live, but with its handler already reset, so clicks were silently dropped.
   Same mistake as the notch, made twice in one feature. Any rule setting
   `display` needs its own `[hidden]` companion. */
.ribbon-btn[hidden] { display: none; }
/* Visibility is the `hidden` attribute alone. It was briefly also a
   `.prose:hover` rule, which meant two mechanisms deciding one thing and a
   button that could be `hidden=false` yet invisible. */
@keyframes ribbon-btn-in { from { opacity: 0; } to { opacity: 1; } }
.ribbon-btn:hover, .ribbon-btn:focus-visible { color: var(--accent); border-color: var(--accent); }
.ribbon-btn.is-current { color: var(--gold); border-color: var(--gold); }

/* The glyph is a ribbon tail: a rectangle notched at the bottom. Drawn in CSS
   rather than shipped as an icon because it is nine lines and the site has no
   icon system to put it in. */
.ribbon-btn-glyph {
  display: block; width: .5rem; height: .8rem;
  background: currentColor;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 72%, 0 100%);
  /* So the button is always the event target. Without this the glyph is the
     topmost element over the button's centre, and every handler has to
     remember to walk back up to the button. */
  pointer-events: none;
}

/* Beside the measure on a wide screen, where there is empty column to spare.
   Below that it sits at the paragraph's right edge, over the ragged margin. */
@media (min-width: 62rem) {
  .ribbon-btn { right: auto; left: 100%; margin-left: .75rem; }
}

/* The marked paragraph. A rule down the left edge, not a background wash:
   the wash reads as a highlight, and highlighting is what saved passages do. */
.prose p.is-ribboned {
  box-shadow: inset .18rem 0 0 var(--gold);
  padding-left: .9rem;
  margin-left: -.9rem;
}

/* Shown once on arrival, so the jump is legible as "here is where you were"
   rather than an unexplained scroll position. */
@keyframes ribbon-flash {
  from { background: var(--accent-soft); }
  to   { background: transparent; }
}
.prose p.is-ribbon-flash { animation: ribbon-flash 1.6s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .prose p.is-ribbon-flash { animation: none; }
  .ribbon-btn { transition: none; }
}

/* The bookmark flag: where your bookmark sits in the article, and a control
   that takes you there.

   Earlier versions were a 2x6px sliver and then a 9x7px tick, both of which
   measured correctly and were in practice invisible. It is now an actual
   bookmark -- teeth down -- hanging below the bar, the same in every theme.

   The button is a plain transparent box carrying the hit area; the shape lives
   on an inner span. `clip-path` clips descendants and padding alike, so a
   clipped button would have a target exactly as small as its drawing. */
.readbar { position: relative; overflow: visible; }
#readbar-ribbon {
  position: absolute; top: 0; z-index: 1;
  display: flex; justify-content: center;
  width: 24px; height: 26px; margin-left: -12px; padding: 0;
  border: 0; background: none; cursor: pointer;
}
/* `display: flex` above outranks the user-agent rule for `[hidden]`, so this
   is what actually makes the flag hideable. See core/tests/test_css.py. */
#readbar-ribbon[hidden] { display: none; }

.readbar-flag {
  display: block; width: 13px; height: 18px;
  transition: transform .1s ease;
  overflow: visible;
}
/* The outline is a real stroke rather than a stack of drop-shadows.

   The previous version built a ring from four unblurred `drop-shadow()`s, and
   chained filters feed one another: the second casts its shadow from the first
   shadow's silhouette, not from the shape. The offsets compounded and the edge
   came out heavier on one side -- reported, correctly, as "a dark shadow on the
   right". A stroke is even on every side by construction, and it lets the
   corners be rounded, which clip-path could not do at all.

   The stroke still earns its place: the flag crosses the track, the gold fill,
   the topbar's border and the page in its 18px, and no single gold reads on all
   four. */
.readbar-flag path {
  fill: var(--gold);
  stroke: rgb(26 18 8 / .78);
  /* `non-scaling-stroke` keeps this 1.6 *screen* pixels whatever the viewBox
     scale is. Without it the width would be in viewBox units -- the crop is 144
     units wide rendered at 13px, so a plain `stroke-width: 1.6` would come out
     at about a seventh of a pixel and vanish. */
  stroke-width: 1.6;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
  paint-order: stroke fill;
}
:root[data-theme="dark"] .readbar-flag path { stroke: rgb(0 0 0 / .55); }

#readbar-ribbon:hover .readbar-flag,
#readbar-ribbon:focus-visible .readbar-flag { transform: translateY(3px); }
@media (prefers-reduced-motion: reduce) {
  .readbar-flag { transition: none; }
}

/* Set when a save fails, so a bookmark that did not stick says so rather than
   looking identical to one that did. */
.prose.ribbon-failed::after {
  content: "Bookmark could not be saved — check your connection and try again.";
  position: fixed; left: 50%; bottom: 1.5rem; transform: translateX(-50%);
  z-index: 20; max-width: min(28rem, calc(100vw - 2rem));
  padding: .6rem 1rem; border-radius: var(--radius);
  background: var(--danger); color: #fff;
  font-family: var(--ui); font-size: .9rem; line-height: 1.35;
}
