/* ---------- focus ----------
   One ring, everywhere, in the app's own colour.

   Before this only inputs and the editing surface defined a focus
   style; buttons and links fell back to the browser's default, which is
   a 1px near-black outline -- thinner than the 2px red used elsewhere,
   a different colour, and rendered differently by each engine. A
   keyboard user was getting an indicator that changed appearance
   depending on what they'd tabbed to.

   :focus-visible rather than :focus, so the ring appears for keyboard
   navigation and not when a button is clicked with a mouse -- which is
   the reason focus rings get removed by people who then break keyboard
   access entirely.

   The colour is --ribbon-red, which the dark theme already redefines.
   Measured against both papers: 4.6 and 5.2, comfortably past the 3.0
   WCAG asks for a UI component. */
:where(a, button, select, textarea, [tabindex], [contenteditable="true"]):focus-visible {
  outline: 2px solid var(--ribbon-red);
  outline-offset: 2px;
  /* The offset ring can fall outside a clipping ancestor; the shadow
     keeps it visible when it does. */
  border-radius: 1px;
}

/* Text inputs keep :focus rather than :focus-visible. Clicking into a
   field and seeing it highlighted is expected behaviour, not noise --
   unlike a button that stays ringed after being clicked. */
:where(input:not([type="checkbox"]):not([type="radio"])):focus {
  outline: 2px solid var(--ribbon-red);
  outline-offset: 1px;
}

/* ---------- scales ----------
   Before this there were 26 distinct spacing values and 26 distinct
   font sizes, every one chosen in isolation. Nothing lined up, because
   nothing was meant to -- which is what makes an interface feel
   unconsidered regardless of its aesthetic.

   The steps below sit close to the clusters that were already in use,
   so adopting them tidies the values rather than redesigning the app.
   Spacing is a 4px base; type is roughly a 1.15 ratio around 1rem.

   Nothing outside these scales. If a new value seems necessary, the
   layout usually wants rethinking rather than a 27th number. */
:root {
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1.0rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2.0rem;
  --space-12: 3.0rem;

  --text-2xs: 0.6875rem;
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1.0rem;
  --text-lg: 1.125rem;
  --text-xl: 1.375rem;
  --text-2xl: 1.75rem;
}

:root {
  --paper: #E8E3D3;
  --paper-raised: #F1ECDE;
  --ink: #232323;
  --ink-soft: #59564C;
  --ribbon-red: #B33A3A;
  --rule: #C9C2AC;
  --highlight: #D8CBA0;
  --shadow: rgba(35, 35, 35, 0.14);
  --success-text: #2B6B2B;

  --font-display: 'Special Elite', 'Courier New', monospace;
  --font-mono: 'IBM Plex Mono', 'Courier New', monospace;
  --font-ui: 'Inter', system-ui, sans-serif;

  /* Tells the browser this page genuinely supports both -- separate
     from (and needed in addition to) the custom-property overrides
     below. Without this, a <select>'s own closed box can be fully
     styled via color/background as normal, but browsers commonly
     render the *open* dropdown list -- and other native chrome like
     scrollbars -- using their default light appearance regardless of
     the page's own CSS, since that part isn't really "the page's CSS"
     to control at all. `light dark` here means "follow the OS
     preference for native UI," matching the automatic
     prefers-color-scheme handling below. */
  color-scheme: light dark;
}

/* Dark mode -- the same warm, ledger/typewriter feel inverted for
   low-light use, not a generic dark theme swapped in wholesale. Applied
   two ways: automatically via prefers-color-scheme (needs no JS at all,
   works identically on every page including the ones that don't load
   app.js), and via a manual [data-theme] override (see
   theme-toggle.js) for anyone who wants a choice independent of their
   OS setting. Both branches carry the exact same values so neither one
   can drift out of sync with the other. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #1E1B16;
    --paper-raised: #28241D;
    --ink: #E8E3D3;
    --ink-soft: #A8A290;
    --ribbon-red: #E0685F;
    --rule: #4A453A;
    --highlight: #4A3F26;
    --shadow: rgba(0, 0, 0, 0.4);
    --success-text: #6FCF6F;
  }
}
:root[data-theme="dark"] {
  --paper: #1E1B16;
  --paper-raised: #28241D;
  --ink: #E8E3D3;
  --ink-soft: #A8A290;
  --ribbon-red: #E0685F;
  --rule: #4A453A;
  --highlight: #4A3F26;
  --shadow: rgba(0, 0, 0, 0.4);
  --success-text: #6FCF6F;
  /* The manual override picks a scheme outright, independent of
     whatever the OS happens to prefer -- native UI should follow that
     explicit choice too, not silently fall back to "light dark"'s
     OS-driven default and disagree with the rest of the page. */
  color-scheme: dark;
}
:root[data-theme="light"] {
  color-scheme: light;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  height: 100%;
}

body {
  display: flex;
  flex-direction: column;
  background-image:
    linear-gradient(var(--rule) 1px, transparent 1px);
  background-size: 100% 2.1em;
  background-attachment: local;
}

::selection { background: var(--highlight); }

button, select { font-family: inherit; }

.skip-link {
  position: absolute;
  top: -3.2rem;
  left: 0.5rem;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  padding: var(--space-2) var(--space-4);
  z-index: 2000;
  text-decoration: none;
  transition: top 0.15s ease;
}
.skip-link:focus {
  top: 0.5rem;
}

.topbar {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-6) clamp(var(--space-5), 4vw, var(--space-12)) var(--space-4);
  border-bottom: 2px solid var(--ink);
  flex: 0 0 auto;
}

.topbar__spacer { flex: 1; }

.topbar__search { display: flex; }
.topbar__search-input {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  width: 12rem;
  max-width: 30vw;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--ink);
}
.topbar__search-input:focus {
  outline: 2px solid var(--ribbon-red);
  outline-offset: 1px;
  border-color: var(--ink);
}

.search__history-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  cursor: pointer;
}
.search__group { margin-bottom: var(--space-6); }
.search__group-title {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-soft);
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--rule);
  margin-bottom: var(--space-2);
}
.search__hit {
  display: block;
  padding: var(--space-2) var(--space-2);
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
}
.search__hit:last-child { border-bottom: none; }
.search__hit:hover { background: var(--highlight); }
.search__hit-where {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}
.search__hit-snippet {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}
.search__hit-when {
  display: block;
  margin-top: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
}

.theme-toggle {
  font-size: var(--text-base);
  line-height: 1;
  padding: var(--space-2) var(--space-2);
}

.notif { position: relative; }
.notif__bell { position: relative; font-size: var(--text-base); }
.notif__badge {
  position: absolute;
  top: -0.35rem;
  right: -0.35rem;
  min-width: 1.05rem;
  height: 1.05rem;
  padding: 0 var(--space-1);
  border-radius: 0.6rem;
  background: var(--ribbon-red);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  line-height: 1.05rem;
  text-align: center;
}
.notif__panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 40;
  width: 22rem;
  max-width: 80vw;
  max-height: 24rem;
  overflow-y: auto;
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  box-shadow: 0 6px 18px var(--shadow);
}
.notif__empty {
  padding: var(--space-4);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}
.notif__item {
  display: block;
  padding: var(--space-3) var(--space-3);
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
}
.notif__item:last-child { border-bottom: none; }
.notif__item:hover { background: var(--highlight); }
.notif__item--unread { border-left: 3px solid var(--ribbon-red); }
.notif__message { display: block; font-size: var(--text-sm); }
.notif__time {
  display: block;
  margin-top: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
}

.topbar__user {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
}

.topbar__username {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  text-decoration: none;
  border-bottom: 1px dotted var(--ink-soft);
}

.topbar__username:hover { color: var(--ink); border-bottom-color: var(--ink); }

.topbar__logout-form { margin: 0; }

.topbar__logout {
  border: none;
  background: none;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  cursor: pointer;
  text-decoration: underline;
  padding: 0;
}
.topbar__logout:hover { color: var(--ribbon-red); }

.topbar__mark {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  letter-spacing: 0.02em;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
}
.topbar__mark:hover { opacity: 0.8; }

.topbar__mark-accent { color: var(--ribbon-red); }

.topbar__sub {
  font-size: var(--text-xs);
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

.app {
  max-width: 920px;
  width: 100%;
  margin: 0 auto;
  padding: clamp(var(--space-5), 4vw, var(--space-12));
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  transition: max-width 0.15s ease;
}

/* While writing, use the full window width -- both this and the
   full-height flex-fill below give a real "editor that fills the window"
   feel instead of a small fixed box floating in a sea of margin. The
   project list/detail views keep their normal centered, narrower width. */
.app--writer {
  max-width: none;
  /* The writer earns its space differently from every other screen:
     it's one tall box, not a document to scroll. The shared
     clamp(var(--space-5), 4vw, var(--space-12)) was taking 96px of height and 96px of
     width at desktop sizes for margin nobody reads -- and the window's
     own edge already provides the breathing room a centred article
     needs from its container. */
  padding: var(--space-4) var(--space-4);
}

/* Two-column writer layout: a fixed-width project sidebar, and the
   editor filling the rest. Both fill the height left below the topbar. */
.writer-layout {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  gap: var(--space-8);
}

.writer-main {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.writer-panes {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
}

.writer-pane {
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.writer-main--split .writer-pane:not(:last-child) {
  padding-right: var(--space-8);
  margin-right: var(--space-8);
  border-right: 1px solid var(--rule);
}

.writer-pane__header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.writer-pane__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.writer-pane__close {
  flex: 0 0 auto;
  width: 1.8rem;
  height: 1.8rem;
  border: 1px solid var(--rule);
  background: var(--paper-raised);
  color: var(--ink-soft);
  cursor: pointer;
  font-size: var(--text-base);
  line-height: 1;
}
.writer-pane__close:hover { border-color: var(--ribbon-red); color: var(--ribbon-red); }

/* ---------- per-document chapter navigation (H1 headings) ---------- */

.writer-pane__body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  gap: var(--space-5);
  /* The scroller. The page used to scroll inside a fixed box; now it
     grows with the writing and this is what moves, the way a document
     scrolls in Google Docs. One scroller per pane, so in split view the
     two documents still scroll independently. */
  overflow-y: auto;
  align-items: flex-start;
}

.chapter-nav {
  flex: 0 0 170px;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--rule);
  padding-right: var(--space-4);
  transition: flex-basis 0.15s ease;
}

.chapter-nav__header {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.chapter-nav__title {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-soft);
}
.chapter-nav__collapse-toggle {
  flex: 0 0 auto;
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: var(--text-sm);
  padding: var(--space-1) var(--space-1);
}
.chapter-nav__collapse-toggle:hover { color: var(--ink); }

.chapter-nav__body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* Same reasoning as .writer-sidebar--collapsed: shrinks the column
   itself, handing the freed width back to the writing surface, rather
   than just hiding content in place. */
.chapter-nav--collapsed {
  flex-basis: 2rem;
  padding-right: var(--space-1);
  overflow: hidden;
}
.chapter-nav--collapsed .chapter-nav__header {
  justify-content: center;
}
.chapter-nav--collapsed .chapter-nav__title {
  display: none;
}
.chapter-nav--collapsed .chapter-nav__body {
  display: none;
}

.chapter-nav__list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: var(--space-6); /* an easy-to-hit drop zone below the last chapter */
}

.chapter-nav__item {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-1);
  cursor: grab;
  border-radius: 2px;
  border-top: 2px solid transparent;
  border-bottom: 2px solid transparent;
}
.chapter-nav__item:hover { background: var(--paper-raised); }

.chapter-nav__item--dragging { opacity: 0.35; }

.chapter-nav__item--over-before {
  border-top-color: var(--ribbon-red);
}

.chapter-nav__item--over-after {
  border-bottom-color: var(--ribbon-red);
}

.chapter-nav__num {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ribbon-red);
}

.chapter-nav__label {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chapter-nav__empty {
  color: var(--ink-soft);
  font-style: italic;
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-1);
}

.chapter-nav__add {
  flex: 0 0 auto;
  margin-top: var(--space-3);
  width: 100%;
  font-size: var(--text-xs);
}

/* ---------- ledger / document list ---------- */

.ledger__toolbar {
  display: flex;
  align-items: center;
  /* Wraps. Without this the project title took whatever width it liked
     and pushed "+ new document" 124px past the right edge of a 390px
     phone, where a chunk of it simply couldn't be tapped. */
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
  min-height: 2.6rem;
  /* .app is a scrolling flex column, and the min-height above replaces
     a flex item's usual "never smaller than its content", so the
     toolbar could be squeezed: once it wrapped onto two lines on a
     phone, it kept one line's height and the Trash button sat on top
     of the list's rule below it. */
  flex-shrink: 0;
}
/* The title yields first: it can wrap or ellipsise, whereas a button
   pushed off screen is unusable. */
.ledger__toolbar .project__title {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
}
.ledger__trash-link {
  margin-left: auto;
  text-decoration: none;
}

.project__title {
  flex: 1;
  margin: 0;
}

.btn {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  border: 1px solid var(--ink);
  background: var(--paper-raised);
  color: var(--ink);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.btn:hover { background: var(--ink); color: var(--paper); }
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

.btn--primary { border-color: var(--ribbon-red); color: var(--ribbon-red); }
.btn--primary:hover { background: var(--ribbon-red); color: var(--paper); border-color: var(--ribbon-red); }

.btn--ghost { border-color: var(--rule); color: var(--ink-soft); }
.btn--ghost:hover { background: var(--rule); color: var(--ink); }

/* Filled red by default, rather than outlined-until-hover like
   .btn--primary -- the app's palette only has one accent color, so
   reusing .btn--primary's own treatment here would make an
   irreversible action look identical to an ordinary call-to-action
   button. Starting filled (rather than starting calm and warning only
   on hover, which is too late to help before the click) reads as
   "this one needs a second look" at a glance, before any interaction
   happens at all. */
.btn--danger {
  background: var(--ribbon-red);
  border-color: var(--ribbon-red);
  color: var(--paper);
}
.btn--danger:hover {
  background: var(--paper);
  color: var(--ribbon-red);
}

.newdoc-form {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-5);
  flex: 0 0 auto;
}
.newdoc-form[hidden] { display: none; }

/* Inside the toolbar's flex ROW, the form should grow to fill remaining
   width next to the button. Outside it (e.g. standalone in project
   detail), .app's flex COLUMN would otherwise interpret flex:1 as "grow
   to fill height" -- which is the bug this fixes. */
.ledger__toolbar .newdoc-form {
  flex: 1;
  margin-bottom: 0;
}

.newdoc-form__input {
  flex: 1;
  max-width: 26rem;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  border: 1px solid var(--ink);
  background: var(--paper-raised);
  padding: var(--space-2) var(--space-3);
  color: var(--ink);
}
.newdoc-form__input:focus { outline: 2px solid var(--ribbon-red); outline-offset: 1px; }

.inline-rename__input {
  font: inherit;
  color: inherit;
  background: var(--paper-raised);
  border: 1px solid var(--ink);
  padding: 0.05em 0.3em;
  width: 16rem;
  max-width: 100%;
}
.inline-rename__input:focus { outline: 2px solid var(--ribbon-red); outline-offset: 1px; }

.rename-trigger {
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: var(--text-xs);
  padding: 0 var(--space-1);
  vertical-align: baseline;
}
.rename-trigger:hover { color: var(--ink); }

.ledger__write {
  font-family: var(--font-mono);
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  padding: var(--space-1) var(--space-2);
  text-decoration: none;
  display: inline-block;
}
.ledger__write:hover { border-color: var(--ribbon-red); color: var(--ribbon-red); }
/* Filled, not just the shared outline-red every other icon in this row
   (export, replay, timeline) also gets on plain hover -- see
   .sidebar-delete:hover's comment for the full reasoning, same
   situation here. */
.ledger__write.doc-delete-trigger:hover {
  background: var(--ribbon-red);
  color: var(--paper);
  border-color: var(--ribbon-red);
}

.ledger__name { cursor: pointer; }
.ledger__name:hover { color: var(--ribbon-red); }

.ledger__branch-badge {
  display: inline-block;
  margin-left: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
  border: 1px solid var(--rule);
  padding: var(--space-1) var(--space-2);
  vertical-align: middle;
}

.ledger__branch-badge--main {
  color: var(--paper);
  background: var(--ribbon-red);
  border-color: var(--ribbon-red);
}

.ledger__row--branch .ledger__name { padding-left: var(--space-2); }
.ledger__row--branch .ledger__index { color: var(--ink-soft); }

.ledger {
  border-top: 1px solid var(--ink);
}

.ledger__empty {
  padding: var(--space-12) 0;
  color: var(--ink-soft);
  font-style: italic;
}

.ledger__row {
  display: grid;
  /* One fewer column since the event count went: it sat beside the
     word count saying the same thing in a unit nobody writes in. */
  grid-template-columns: 2.4rem 1fr auto auto auto auto auto auto auto;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
  transition: background 0.15s ease;
}

.trash__title {
  font-family: var(--font-display);
  margin: 0 0 var(--space-2);
}
.trash__row {
  grid-template-columns: 1fr auto auto auto;
  cursor: default;
}

.ledger__row:hover { background: var(--paper-raised); }

/* ---------- expandable tree groups (project detail) ---------- */

.tree-group { border-bottom: 1px solid var(--rule); position: relative; }
/* One extra column just for tree-root rows' drag handle -- scoped to
   .tree-row specifically (higher specificity than the shared
   .ledger__row alone) so branch rows and the project list's own rows,
   which share that same base class but never get a drag handle, are
   completely unaffected rather than needing an empty placeholder cell
   to keep their own columns aligned. */
.ledger__row.tree-row { grid-template-columns: 1.4rem 2.4rem 1fr auto auto auto auto auto auto auto; }
.tree-group--dragging { opacity: 0.35; }
.tree-group--over-before::before,
.tree-group--over-after::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--ribbon-red);
}
.tree-group--over-before::before { top: 0; }
.tree-group--over-after::after { bottom: 0; }

.drag-handle {
  cursor: grab;
  color: var(--ink-soft);
  font-size: var(--text-sm);
  user-select: none;
}
.drag-handle:active { cursor: grabbing; }
.tree-group .ledger__row { border-bottom: none; cursor: default; }
.tree-group .ledger__row .ledger__name { cursor: pointer; }

.tree-toggle {
  width: 2.4rem;
  height: 2.4rem;
  border: 1px solid var(--rule);
  background: var(--paper-raised);
  color: var(--ink-soft);
  font-size: var(--text-sm);
  cursor: pointer;
}
.tree-toggle:hover:not(:disabled) { border-color: var(--ink); color: var(--ink); }
.tree-toggle:disabled { cursor: default; opacity: 0.5; }

.tree-branches {
  border-top: 1px dashed var(--rule);
  background: var(--paper);
}
.tree-branches .ledger__row { border-bottom: 1px solid var(--rule); }
.tree-branches .ledger__row:last-child { border-bottom: none; }

.folder-section {
  border: 1px solid var(--rule);
  background: var(--paper-raised);
  margin-bottom: var(--space-4);
}
.folder-section__header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
}
.folder-section__name-text {
  font-family: var(--font-mono);
  font-weight: 600;
  flex: 0 0 auto;
}
.folder-section__body {
  border-top: 1px solid var(--rule);
  background: var(--paper);
}
.folder-section__body .tree-group:last-child { border-bottom: none; }
.folder-section__empty {
  padding: var(--space-4);
  font-size: var(--text-sm);
}

.folder-toggle {
  flex: 0 0 auto;
  width: 1.8rem;
  height: 1.8rem;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--ink-soft);
  font-size: var(--text-xs);
  cursor: pointer;
}
.folder-toggle:hover { border-color: var(--ink); color: var(--ink); }

.folder-delete-trigger {
  margin-left: auto;
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: var(--text-sm);
  padding: var(--space-1) var(--space-1);
}
.folder-delete-trigger:hover { color: var(--ribbon-red); }

.move-to-folder-select {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--ink);
  padding: var(--space-1) var(--space-1);
  max-width: 8.5rem;
}

.set-main-btn {
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-2);
  white-space: nowrap;
}

.ledger__index {
  font-family: var(--font-mono);
  color: var(--ink-soft);
  font-size: var(--text-sm);
}

.ledger__name {
  font-family: var(--font-display);
  font-size: var(--text-base);
}

.ledger__stat {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  white-space: nowrap;
}

/* ---------- player view ---------- */

.player__back {
  background: none;
  border: none;
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  cursor: pointer;
  padding: 0;
  margin-bottom: var(--space-6);
  text-decoration: none;
  display: inline-block;
}
.player__back:hover { color: var(--ribbon-red); }

.player__title {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  margin: 0 0 var(--space-5);
}

.page {
  background: var(--paper-raised);
  border: 1px solid var(--ink);
  box-shadow: 6px 6px 0 var(--shadow);
  padding: var(--space-8) clamp(var(--space-5), 4vw, var(--space-8));
  min-height: 340px;
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: 1.7;
  word-wrap: break-word;
  /* .app is a column flex container, so this is a flex item and picks
     up flex-shrink: 1 by default -- meaning a long document gets
     squashed to fit the viewport while its text, which doesn't shrink,
     spills out past the border and over the transport controls below.
     min-height only stops the squashing at 340px, it doesn't prevent
     it. The editable page in the writer avoids this by claiming the
     free space and scrolling internally (flex: 1 1 auto + overflow-y);
     this one is read-only and sits above the replay transport, so it
     should keep its natural height instead and let .app's own
     overflow-y do the scrolling. */
  flex: 0 0 auto;
}

/* The replay view specifically. `.page` above keeps its natural height
   and lets .app scroll, which is right for the read-only version view
   and the share pages -- there you're only reading, and the page
   growing is exactly what you want.

   Replay is different: its whole point is the transport underneath
   (play, scrubber, branch-from-here). With the default behaviour a long
   document makes .page thousands of pixels tall and pushes those
   controls off the bottom of the window -- measured at 3570px down in
   an 800px-tall window for a 150-event document -- so the one thing the
   view exists for can't be reached without scrolling past the whole
   document first. The controls also drift as the text grows during
   playback.

   So here the page claims the leftover height and scrolls its own text
   instead, the way the writer's editable page does, keeping the
   transport on screen where it's usable. */
.page--replay {
  flex: 1 1 auto;
  /* A floor rather than 0 so the text area can't collapse to nothing,
     but low enough that on a 320x600 screen -- where the topbar and the
     wrapped transport together already claim most of the height -- the
     controls still fit on screen rather than being pushed a few pixels
     under the fold. */
  min-height: 5rem;
  overflow-y: auto;
}

.page__caret {
  display: inline-block;
  width: 0.55ch;
  background: var(--ribbon-red);
  animation: blink 1s step-end infinite;
}

.page h1, .page h2, .page h3 {
  font-family: var(--font-display);
  margin: 0.7em 0 0.4em;
  line-height: 1.3;
}
.page h1:first-child, .page h2:first-child, .page h3:first-child { margin-top: 0; }
.page p { margin: 0 0 0.9em; }
.page ul, .page ol { margin: 0 0 0.9em; padding-left: 1.4em; }
.page li { margin: 0.15em 0; }
.page blockquote {
  margin: 0 0 0.9em;
  padding-left: 0.9em;
  border-left: 3px solid var(--ribbon-red);
  color: var(--ink-soft);
  font-style: italic;
}
.page pre {
  background: var(--paper);
  border: 1px solid var(--rule);
  padding: 0.8em;
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: 0.9em;
  margin: 0 0 0.9em;
}
.page code {
  font-family: var(--font-mono);
  background: var(--paper);
  padding: 0.1em 0.35em;
  font-size: 0.92em;
}
.page pre code { background: none; padding: 0; }
.page hr { border: none; border-top: 1px solid var(--rule); margin: 1.2em 0; }
.page a { color: var(--ribbon-red); }

.page--editable {
  display: block;
  width: 100%;
  color: var(--ink);
  cursor: text;

  /* The writing surface is the point of this screen, so it should fill
     the height available rather than being sized to its content. It was
     334px tall on a 900px laptop -- a third of the window, with the rest
     empty. Now it takes whatever the sidebar and transport leave. */
  flex: 1 1 auto;
  min-height: 18rem;
  overflow-y: auto;

  /* The width cap lives on .writer-shell, which holds this and the
     toolbar and status line together -- see the comment there. Measured
     before any of it: 1356px on a 1920px screen, about 143 characters
     per line. Long measures are genuinely harder to read, which is why
     printed pages and Google Docs alike stop around 80. */
}
.page--editable:focus { outline: 2px solid var(--ribbon-red); outline-offset: 2px; }
.page--editable:empty::before {
  content: attr(data-placeholder);
  color: var(--ink-soft);
  font-style: italic;
}

/* ---------- writer sidebar: project structure ---------- */

.writer-sidebar {
  flex: 0 0 250px;
  min-height: 0;
  overflow-y: auto;
  padding-right: var(--space-5);
  border-right: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  transition: flex-basis 0.15s ease;
}

.writer-sidebar__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}
.writer-sidebar__body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
.writer-sidebar__collapse-toggle {
  flex: 0 0 auto;
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: var(--text-sm);
  padding: var(--space-1) var(--space-1);
}
.writer-sidebar__collapse-toggle:hover { color: var(--ink); }

/* Shrinks the whole column, not just its contents -- unlike the goals
   widget (a card in a normal vertical page, where collapsing only
   needs to reclaim vertical space), this sidebar sits in a horizontal
   split next to the writing surface, so the actual point of collapsing
   it is to hand that freed width back to the surface itself. */
.writer-sidebar--collapsed {
  flex-basis: 2.2rem;
  padding-right: var(--space-1);
  overflow: hidden;
}
.writer-sidebar--collapsed .writer-sidebar__header {
  justify-content: center;
}
.writer-sidebar--collapsed .sidebar-back {
  display: none;
}
.writer-sidebar--collapsed .writer-sidebar__body {
  display: none;
}

.sidebar-back {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  text-decoration: none;
  margin-bottom: var(--space-4);
}
.sidebar-back:hover { color: var(--ribbon-red); }

.sidebar-empty {
  color: var(--ink-soft);
  font-style: italic;
  font-size: var(--text-sm);
}

.sidebar-trees { flex: 1 1 auto; }

.sidebar-tree { margin-bottom: var(--space-2); }

.sidebar-folder {
  margin-bottom: var(--space-2);
  padding-left: var(--space-1);
  border-left: 2px solid var(--rule);
}
.sidebar-folder__name {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
  padding: var(--space-1) var(--space-1);
}

.sidebar-main {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-1);
  border-radius: 2px;
}
.sidebar-main:hover { background: var(--paper-raised); }

.sidebar-main--active {
  background: var(--highlight);
}
.sidebar-main--active .sidebar-main__name { color: var(--ink); font-weight: 600; }

.sidebar-main__name {
  flex: 1;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-toggle {
  flex: 0 0 auto;
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-1);
}
.sidebar-toggle:hover { color: var(--ink); }

.sidebar-split {
  flex: 0 0 auto;
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: var(--text-sm);
  padding: var(--space-1) var(--space-1);
  opacity: 0;
  transition: opacity 0.1s ease;
}
.sidebar-main:hover .sidebar-split,
.sidebar-branch:hover .sidebar-split,
.sidebar-main:hover .sidebar-delete,
.sidebar-branch:hover .sidebar-delete {
  opacity: 1;
}
.sidebar-split:hover { color: var(--ribbon-red); }

.sidebar-delete {
  flex: 0 0 auto;
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-1);
  opacity: 0;
  transition: opacity 0.1s ease;
}
/* Filled, not just a color change -- .sidebar-split (a harmless
   "open side-by-side" action) sitting right next to this one already
   turns the same red on plain hover, which would otherwise make red
   just a generic "you're hovering something" signal here rather than
   an actual warning specific to the one action that's destructive. */
.sidebar-delete:hover {
  background: var(--ribbon-red);
  color: var(--paper);
  border-radius: 2px;
}

.sidebar-branches {
  padding-left: var(--space-4);
  border-left: 1px dashed var(--rule);
  margin-left: var(--space-2);
}

.sidebar-branch {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-1);
  cursor: pointer;
  border-radius: 2px;
}
.sidebar-branch:hover { background: var(--paper-raised); }

.sidebar-branch--active { background: var(--highlight); }
.sidebar-branch--active .sidebar-branch__name { color: var(--ink); font-weight: 600; }

.sidebar-branch__dot {
  flex: 0 0 auto;
  color: var(--ribbon-red);
  font-size: var(--text-2xs);
}

.sidebar-branch__name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-add {
  flex: 0 0 auto;
  margin-top: var(--space-4);
  width: 100%;
  font-size: var(--text-xs);
}

/* The writer view fills whatever height is left below the topbar: the
   back-button/title stay their natural size, and within the shell only
   the editable surface grows -- toolbar and status stay pinned. */
.writer-shell {
  flex: 1 1 auto;
  min-width: 0;
  /* At least the pane's height, so a short document still fills the
     screen; beyond that it grows with the text. */
  min-height: 100%;
  display: flex;
  flex-direction: column;

  /* No width cap here. The rule below caps every child of this column
     together, which is what keeps the toolbar aligned with the page;
     capping the column as well would just be a second, slightly
     different number for the same thing. */
}

.writer-shell .writer__toolbar { flex: 0 0 auto; }
.writer-shell .writer__status { flex: 0 0 auto; }
/* Everything in the writing column shares the page's width and centring.
   Capping only the page left the toolbar, status line and goals block
   spanning the full pane while the writing sat narrower and centred
   beneath them -- the controls no longer lined up with the thing they
   control, which looked like a bug because it was one. */
/* Writing width is the writer's choice, not mine.
   
   A fixed 46rem cap is defensible typography -- long measures are harder
   to read because the eye loses the start of the next line -- but on a
   2560px display it left 1,574px of empty desk either side, which is its
   own kind of wrong. Every serious writing tool (Scrivener, Ulysses, iA
   Writer) lets you set this, and for good reason: the right measure
   depends on the screen, the font size, and the person.
   
   Three steps rather than a slider: enough to matter, few enough to
   choose from without deliberating. */
.writer-shell > * {
  width: 100%;
  margin-left: auto;
  margin-right: auto;
}

/* Declared after the base rule, not before it: these have the same
   specificity, so source order decides and a variant listed first would
   simply lose. */
/* The unset default matches the JS default (fill) so there's no flash
   of a narrower column before the script runs. */
.writer-shell > *,
.writer-shell[data-width="wide"] > * { max-width: none; }
.writer-shell[data-width="medium"] > * { max-width: 64rem; }
.writer-shell[data-width="narrow"] > * { max-width: 46rem; }

/* The goals readout lives below the writing surface, where it was
   claiming 127px of a 900px window -- more than a tenth of the screen,
   permanently, for something you glance at. Capped and scrollable so it
   stays available without competing with the page for height. */
.writer-shell .doc-goals {
  flex: 0 0 auto;
  margin-top: var(--space-2);
  max-height: 5.5rem;
  overflow-y: auto;
}

.writer-shell .page--editable {
  /* Grows with the text instead of scrolling inside a box. Fills the
     space when the document is short, and is exactly as tall as the
     writing when it's long. */
  flex: 1 0 auto;
  min-height: 0;
  overflow: visible;
  /* Room below the last line, so what you're typing isn't pinned to the
     bottom edge of the screen behind the status line. */
  padding-bottom: 35vh;
}

.writer__save-error {
  background: var(--ribbon-red);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
  border: 1px solid var(--ink);
  box-shadow: 3px 3px 0 var(--shadow);
}

.toast-container {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: min(24rem, calc(100vw - 2rem));
  /* The container spans the full stack's footprint, but only the
     individual toasts (and their close buttons) are actually
     clickable -- nothing here should block clicks on the page
     underneath wherever a toast isn't. */
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--ink);
  box-shadow: 3px 3px 0 var(--shadow);
  pointer-events: auto;
  animation: toast-in 0.15s ease-out;
}
.toast--leaving {
  animation: toast-out 0.2s ease-in forwards;
}
.toast--error {
  background: var(--ribbon-red);
  color: var(--paper);
}
.toast--success {
  background: var(--ink);
  color: var(--paper);
}
.toast--info {
  background: var(--paper-raised);
  color: var(--ink);
}
.toast__message {
  flex: 1;
  line-height: 1.4;
}
.toast__close {
  background: none;
  border: none;
  color: inherit;
  font-size: var(--text-lg);
  line-height: 1;
  cursor: pointer;
  padding: 0;
  opacity: 0.75;
}
.toast__close:hover {
  opacity: 1;
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(-0.4rem); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes toast-out {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(-0.4rem); }
}

.rhythm-stats {
  display: flex;
  gap: var(--space-5);
  flex-wrap: wrap;
  margin: var(--space-3) 0 var(--space-5);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}
.rhythm-stats__item strong {
  font-family: var(--font-display);
  font-size: var(--text-base);
  display: block;
}

.rhythm-hist {
  display: flex;
  align-items: flex-end;
  gap: var(--space-1);
  height: 120px;
  border-bottom: 1px solid var(--rule);
  padding: 0 var(--space-1);
}
.rhythm-hist__bar-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
}
.rhythm-hist__bar {
  width: 100%;
  min-height: 2px;
  background: var(--highlight);
  border: 1px solid var(--ink);
  border-bottom: none;
}
.rhythm-hist__bar-label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
  margin-top: var(--space-1);
  white-space: nowrap;
  transform: rotate(-40deg);
  transform-origin: top right;
}
.rhythm-hist__caption {
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  margin-top: var(--space-4);
}

.rhythm-hist__note {
  margin-top: var(--space-4);
}

.writer__status {
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
}
.writer__savestate--dirty { color: var(--ribbon-red); }

.writer__toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-1);
  /* Tighter than it was: at 1440px this wrapped to 106px of toolbar
     above a 334px writing area. Every row it wraps to is a row taken
     from the page. */
  margin-bottom: var(--space-2);
  padding: var(--space-1);
  border: 1px solid var(--rule);
  background: var(--paper-raised);
}

.writer__toolbar-sep {
  width: 1px;
  align-self: stretch;
  background: var(--rule);
  margin: 0 var(--space-1);
}

.writer__toolbar-spacer { flex: 1; }

.writer__hint {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  white-space: nowrap;
}

.merge-btn {
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-2);
  margin-right: var(--space-3);
}

.export-btn {
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-2);
  margin-right: var(--space-3);
}

/* ---------- merge modal ---------- */

.merge-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(35, 35, 35, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.merge-modal {
  background: var(--paper-raised);
  border: 1px solid var(--ink);
  box-shadow: 8px 8px 0 var(--shadow);
  width: min(520px, 90vw);
  max-height: 80vh;
  display: flex;
  flex-direction: column;
}

.merge-modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--rule);
}
.merge-modal__header h3 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  margin: 0;
}

.merge-modal__close {
  border: none;
  background: none;
  font-size: var(--text-xl);
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
}
.merge-modal__close:hover { color: var(--ribbon-red); }

.merge-modal__list {
  padding: var(--space-2) var(--space-5) var(--space-5);
  overflow-y: auto;
}

.merge-modal__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--rule);
}
.merge-modal__row:last-child { border-bottom: none; }

.merge-modal__name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.merge-modal__action {
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-2);
  white-space: nowrap;
}

/* ---------- document timeline ---------- */

.timeline-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin-bottom: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.timeline-legend__item { display: flex; align-items: center; gap: var(--space-2); }
.timeline-legend__swatch {
  display: inline-block;
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
}
.timeline-legend__swatch--branch { background: var(--ink); }
.timeline-legend__swatch--merge { background: var(--ribbon-red); }

.activity-summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin: var(--space-4) 0 var(--space-5);
}
.activity-stat { display: flex; flex-direction: column; }
.activity-stat__value {
  font-family: var(--font-mono);
  font-size: var(--text-xl);
  color: var(--ink);
}
.activity-stat__label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}
.activity-wrap {
  border: 1px solid var(--rule);
  background: var(--paper-raised);
  padding: var(--space-3);
}
.activity-svg { width: 100%; height: auto; display: block; }
.act-bar--up { fill: var(--success-text); }
.act-bar--down { fill: var(--ribbon-red); }
.act-bar--flat { fill: var(--ink-soft); }
.act-zero { stroke: var(--rule); stroke-width: 1; }
.act-axis-label {
  font-family: var(--font-mono);
  font-size: 10px;
  fill: var(--ink-soft);
  text-anchor: middle;
}
.activity-note {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  border-left: 3px solid var(--rule);
  padding-left: var(--space-2);
  margin-top: var(--space-4);
}

.timeline-wrap {
  background: var(--paper-raised);
  border: 1px solid var(--ink);
  box-shadow: 6px 6px 0 var(--shadow);
  padding: var(--space-4);
}

.diff-vs {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  font-weight: normal;
  margin: 0 0.3em;
}

.diff-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin-bottom: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}

.diff-legend__item { display: flex; align-items: center; gap: var(--space-2); }

.diff-page { white-space: pre-wrap; }

ins.diff-insert {
  background: rgba(60, 140, 60, 0.16);
  color: var(--success-text);
  text-decoration: none;
  border-bottom: 1px solid rgba(60, 140, 60, 0.5);
}

del.diff-delete {
  background: rgba(190, 40, 40, 0.1);
  color: var(--ribbon-red);
  text-decoration: line-through;
}

.goal-calendar__nav {
  display: flex;
  align-items: center;
  /* Wraps at 320px, where the month name plus both arrows plus the
     period selector don't fit on one line and the last control was
     pushed off the edge. */
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.goal-calendar__month {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  min-width: 11rem;
  text-align: center;
}

.goal-calendar__weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  text-align: center;
  margin-bottom: var(--space-2);
}

.goal-calendar__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.goal-calendar__cell {
  aspect-ratio: 1;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  padding: var(--space-1);
  border: 1px solid var(--rule);
  background: var(--paper-raised);
}

.goal-calendar__cell--empty { border: none; background: none; }

.goal-calendar__daynum {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}

/* Solid, saturated fills rather than washed-out tints. A month grid is
   read at a glance -- "how did February go" -- and 14%-opacity blocks
   made that a squint. The 700-weight shades are dark enough for white
   numerals to clear WCAG AA (5.0, 5.0 and 6.5 respectively), which
   full-strength 600-weight versions do not. */
.goal-calendar__cell--met {
  background: #15803d;
  border-color: #14532d;
}
.goal-calendar__cell--met .goal-calendar__daynum { color: #ffffff; }

/* Some writing, but under the target. Amber rather than red: a day with
   400 of 500 words on it is not the same as a blank one, and the
   calendar shouldn't tell a writer it was. */
.goal-calendar__cell--partial {
  background: #b45309;
  border-color: #7c2d12;
}
.goal-calendar__cell--partial .goal-calendar__daynum { color: #ffffff; }

/* Nothing written at all. Red is now reserved for this. */
.goal-calendar__cell--missed {
  background: #b91c1c;
  border-color: #7f1d1d;
}
.goal-calendar__cell--missed .goal-calendar__daynum { color: #ffffff; }

.goal-calendar__cell--pending {
  background: var(--highlight);
  border-color: var(--rule);
}

.goal-calendar__cell--future {
  background: none;
  border-color: var(--rule);
  opacity: 0.5;
}

.goal-calendar__swatch {
  display: inline-block;
  width: 0.8rem;
  height: 0.8rem;
  border: 1px solid var(--rule);
}
/* The legend swatches have to be the same colours as the cells, or the
   key stops being a key. */
.goal-calendar__swatch--met { background: #15803d; border-color: #14532d; }
.goal-calendar__swatch--partial { background: #b45309; border-color: #7c2d12; }
.goal-calendar__swatch--missed { background: #b91c1c; border-color: #7f1d1d; }
.goal-calendar__swatch--pending { background: var(--highlight); }

.timeline-svg {
  width: 100%;
  height: auto;
  display: block;
  font-family: var(--font-ui);
}

.tl-gridline { stroke: var(--rule); stroke-width: 1; }
.tl-axis { stroke: var(--ink); stroke-width: 1.5; }
.tl-axis-label {
  font-family: var(--font-mono);
  font-size: 11px;
  fill: var(--ink-soft);
}

.tl-area { fill: var(--highlight); opacity: 0.5; }
.tl-line { fill: none; stroke: var(--ink); stroke-width: 2; }

.tl-marker-line { stroke-dasharray: 3 3; stroke-width: 1; }
.tl-marker-dot { stroke: var(--paper-raised); stroke-width: 1.5; }
.tl-marker-label {
  font-family: var(--font-mono);
  font-size: 11px;
}

.tl-marker--origin .tl-marker-line,
.tl-marker--branch .tl-marker-line { stroke: var(--ink-soft); }
.tl-marker--origin .tl-marker-dot,
.tl-marker--branch .tl-marker-dot { fill: var(--ink); }
.tl-marker--origin .tl-marker-label,
.tl-marker--branch .tl-marker-label { fill: var(--ink); }

.tl-marker--merge .tl-marker-line { stroke: var(--ribbon-red); }
.tl-marker--merge .tl-marker-dot { fill: var(--ribbon-red); }
.tl-marker--merge .tl-marker-label { fill: var(--ribbon-red); }

/* Named versions use the success/positive tone rather than the merge
   red -- they're waypoints the writer chose, not incoming changes. */
.tl-marker--tag .tl-marker-line { stroke: var(--success-text); }
.tl-marker--tag .tl-marker-dot { fill: var(--success-text); }
.tl-marker--tag .tl-marker-label { fill: var(--success-text); }
.timeline-legend__swatch--tag { background: var(--success-text); }

.tags-panel {
  border: 1px solid var(--rule);
  background: var(--paper-raised);
  padding: var(--space-4) var(--space-4);
  margin-top: var(--space-6);
}
.tags-panel__header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.tags-panel__title {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-soft);
}
.tag-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--rule);
}
.tag-row:last-child { border-bottom: none; }
.tag-row__name { font-family: var(--font-mono); font-size: var(--text-sm); }
.tag-row__meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.tag-row__view { margin-left: auto; text-decoration: none; }

.version-badge { color: var(--success-text); }
.version-note {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  border-left: 3px solid var(--rule);
  padding-left: var(--space-2);
  margin-bottom: var(--space-4);
}

/* ---------- export dropdown ---------- */

.export-menu {
  position: absolute;
  z-index: 90;
  background: var(--paper-raised);
  border: 1px solid var(--ink);
  box-shadow: 4px 4px 0 var(--shadow);
  min-width: 170px;
  display: flex;
  flex-direction: column;
}

.export-menu__item {
  display: block;
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  white-space: nowrap;
}
.export-menu__item:last-child { border-bottom: none; }
.export-menu__item:hover { background: var(--paper); color: var(--ribbon-red); }

.fmt-btn {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink);
  padding: var(--space-1) var(--space-2);
  cursor: pointer;
  min-width: 2rem;
}
.fmt-btn:hover { border-color: var(--ink); background: var(--paper); }
.fmt-btn:active { background: var(--ribbon-red); color: var(--paper); }

@keyframes blink { 50% { opacity: 0; } }

/* ---------- transport / ruler scrubber ---------- */

.transport {
  margin-top: var(--space-6);
  /* Same flex-item problem as .page above: without this, now that the
     page holds its natural height, the transport becomes the only
     shrinkable thing left in the column and absorbs all the squashing
     instead -- clipping the scrubber and carriage on any document long
     enough to overflow the viewport. */
  flex: 0 0 auto;
}

.transport__controls {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-2);
}

.transport__play {
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 50%;
  border: 2px solid var(--ink);
  background: var(--paper-raised);
  cursor: pointer;
  font-size: var(--text-base);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.transport__play:hover { background: var(--ribbon-red); color: var(--paper); border-color: var(--ribbon-red); }

.transport__counter {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--ink-soft);
  min-width: 11ch;
}

.transport__timestamp {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--ink-soft);
  white-space: nowrap;
}

.transport__speed {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  border: 1px solid var(--ink);
  background: var(--paper-raised);
  padding: var(--space-1) var(--space-2);
}

.ruler {
  position: relative;
  height: 3.2rem;
  cursor: pointer;
  user-select: none;
}

.ruler__track {
  position: absolute;
  left: 0; right: 0; top: 1.4rem;
  height: 2px;
  background: var(--ink);
}

.ruler__ticks {
  position: absolute;
  left: 0; right: 0; top: 0.5rem;
  height: 0.9rem;
  display: flex;
  justify-content: space-between;
  pointer-events: none;
}

.ruler__tick { width: 1px; background: var(--ink-soft); height: 100%; }
.ruler__tick--major { background: var(--ink); height: 130%; }

.ruler__fill {
  position: absolute;
  left: 0; top: 1.4rem;
  height: 2px;
  background: var(--ribbon-red);
}

.ruler__carriage {
  position: absolute;
  top: 0.9rem;
  width: 2px;
  height: 1.8rem;
  background: var(--ribbon-red);
  transform: translateX(-1px);
}

.ruler__mark {
  position: absolute;
  top: 0.9rem;
  width: 2px;
  height: 1.8rem;
  background: var(--ink);
  transform: translateX(-1px);
}

.ruler__mark::before {
  content: "";
  position: absolute;
  top: -0.5rem;
  left: -0.3rem;
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--ink);
}

.compare-mark-label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}

.ruler__carriage::after {
  content: "";
  position: absolute;
  top: -6px;
  left: -5px;
  width: 12px;
  height: 12px;
  background: var(--ribbon-red);
  border-radius: 50% 50% 50% 0;
  transform: rotate(45deg);
}

@media (prefers-reduced-motion: reduce) {
  .page__caret { animation: none; }
}

.branch-row {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px dashed var(--rule);
  display: flex;
  align-items: center;
}
.branch-row[hidden] { display: none; }

.branch-row .newdoc-form { flex: 1; }
.branch-row .newdoc-form__input { max-width: none; }

@media (max-width: 560px) {
  /* .ledger__row's normal layout is a many-column grid (name plus up
     to eight stat/action columns) -- fine on a wide screen, but CSS
     grid doesn't wrap extra items onto new rows the way flexbox does,
     so squeezing that same grid into a phone width would overflow
     horizontally rather than reflow. Switching to flex-wrap here,
     specifically for narrow screens, lets the name take its own full
     line and everything else -- word count, last-edited date, the
     export/replay/delete buttons -- wrap naturally onto however many
     lines they need instead. */
  .ledger__row, .trash__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    row-gap: var(--space-2);
    column-gap: var(--space-2);
  }
  .ledger__name { flex: 1 1 100%; }
  .ledger__stat:nth-child(4) { display: none; }

  .topbar {
    flex-wrap: wrap;
    row-gap: var(--space-2);
  }
  /* Decorative on a page this narrow -- the wordmark alone is enough
     identity, and dropping the subtitle leaves real room for the
     account/theme controls that actually do something. */
  .topbar__sub { display: none; }

  .backup-codes { grid-template-columns: 1fr; }

  .transport__controls { flex-wrap: wrap; row-gap: var(--space-2); }

  .writer-layout { flex-direction: column; }
  .writer-sidebar {
    flex: 0 0 auto;
    max-height: 40vh;
    border-right: none;
    border-bottom: 1px solid var(--rule);
    padding-right: 0;
    padding-bottom: var(--space-4);
  }

  .writer-pane__body { flex-direction: column; }
  .chapter-nav {
    flex: 0 0 auto;
    max-height: 30vh;
    border-right: none;
    border-bottom: 1px solid var(--rule);
    padding-right: 0;
    padding-bottom: var(--space-3);
  }
}

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

.auth-page {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-8);
}

.auth-card {
  width: min(400px, 100%);
  background: var(--paper-raised);
  border: 1px solid var(--ink);
  box-shadow: 6px 6px 0 var(--shadow);
  padding: var(--space-8) clamp(var(--space-6), 4vw, var(--space-8));
}

.auth-card--wide { width: min(560px, 100%); }

.account-share-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.account-share-link-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  border: 1px solid var(--rule);
  padding: var(--space-2) var(--space-3);
  flex-wrap: wrap;
}

.account-template-rename {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1;
  min-width: 200px;
}
.account-template-rename .newdoc-form__input { flex: 1; }

.account-share-link-info {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex: 1;
  min-width: 220px;
}

.account-share-link-project {
  font-family: var(--font-display);
  font-size: var(--text-sm);
}

.account-goals {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.account-goal-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  border: 1px solid var(--rule);
  padding: var(--space-2) var(--space-3);
  flex-wrap: wrap;
}

.account-goal-info {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.account-goal-scope {
  font-family: var(--font-display);
  font-size: var(--text-sm);
}

.account-goal-detail {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}

.account-goal-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.auth-card__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  margin: 0 0 var(--space-6);
}

.auth-card__error {
  background: var(--paper);
  border: 1px solid var(--ribbon-red);
  color: var(--ribbon-red);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-5);
}

.auth-card__notice {
  background: var(--paper);
  border: 1px solid var(--ink);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-5);
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.auth-form__label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
  margin-top: var(--space-3);
}
.auth-form__label:first-child { margin-top: 0; }

.auth-form__input {
  font-family: var(--font-ui);
  font-size: var(--text-base);
  border: 1px solid var(--ink);
  background: var(--paper);
  padding: var(--space-2) var(--space-3);
  color: var(--ink);
}
.auth-form__input:focus { outline: 2px solid var(--ribbon-red); outline-offset: 1px; }

.password-field {
  position: relative;
  display: flex;
}
.password-field__input {
  flex: 1;
  padding-right: var(--space-12);
}
.password-field__toggle {
  position: absolute;
  right: 0.4rem;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  cursor: pointer;
  padding: var(--space-1) var(--space-2);
}
.password-field__toggle:hover {
  color: var(--ink);
}
.password-field__toggle:focus-visible {
  outline: 2px solid var(--ribbon-red);
  outline-offset: 1px;
}

.auth-form__submit {
  margin-top: var(--space-6);
  width: 100%;
  padding: var(--space-3);
  font-size: var(--text-sm);
}

.auth-card__footer {
  margin-top: var(--space-6);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  text-align: center;
}
.auth-card__footer a { color: var(--ribbon-red); }

/* ---------- writing goals ---------- */

.goals-widget__loading {
  color: var(--ink-soft);
  font-style: italic;
  font-size: var(--text-sm);
  margin-bottom: var(--space-4);
}

.goals-widget {
  border: 1px solid var(--rule);
  background: var(--paper-raised);
  padding: var(--space-4) var(--space-4);
  margin-bottom: var(--space-5);
}

.goals-widget__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}

.goals-widget--collapsed .goals-widget__header { margin-bottom: 0; }
.goals-widget--collapsed .goals-widget__body { display: none; }

.goals-widget__collapse-toggle {
  border: none;
  background: none;
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-decoration: underline;
  cursor: pointer;
  padding: 0;
}
.goals-widget__collapse-toggle:hover { color: var(--ribbon-red); }

.goals-widget__title {
  font-family: var(--font-display);
  font-size: var(--text-base);
}

.goals-widget__add-btn {
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-2);
  cursor: pointer;
  margin-top: var(--space-3);
}
.goals-widget__add-btn:hover { border-color: var(--ribbon-red); color: var(--ribbon-red); }

/* The tracks live on the container and every row inherits them through
   subgrid, so all the goals share one set of columns. As separate grids
   each row sized its own -- a goal with a streak got a narrower bar than
   one without (192px against 220px), and the delete buttons stopped
   lining up down the list. */
.goals-widget__rows {
  display: grid;
  grid-template-columns: auto 4rem minmax(4rem, 1fr) auto auto auto auto;
  column-gap: var(--space-3);
  row-gap: var(--space-2);
  align-items: center;
}

.goals-widget__empty {
  color: var(--ink-soft);
  font-style: italic;
  font-size: var(--text-sm);
}

.goals-widget__row {
  /* Seven cells -- scope, period, bar, count, streak, calendar, remove
     -- on the container's seven tracks. The row used to declare five
     of its own, from before the streak and calendar buttons existed,
     so the last two wrapped onto a second line and left the delete
     button stranded there. */
  display: grid;
  grid-template-columns: subgrid;
  grid-column: 1 / -1;
  align-items: center;
}
/* The placeholder keeps its grid cell but shows nothing: it used to
   carry the badge's amber border and fill, an empty box beside every
   goal without a streak. */
.goals-widget__streak--none { display: block; }
.goals-widget__streak.goals-widget__streak--none { border: 0; background: none; padding: 0; }

.goals-widget__scope {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 12rem;
}

.goals-widget__label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}

.goals-widget__bar {
  flex: 1;
  max-width: 220px;
  height: 0.55rem;
  background: var(--paper);
  border: 1px solid var(--rule);
}

.goals-widget__bar-fill {
  height: 100%;
  background: var(--ribbon-red);
}

.goals-widget__count {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  white-space: nowrap;
}

.goals-widget__remove {
  border: none;
  background: none;
  color: var(--ink-soft);
  font-size: var(--text-base);
  line-height: 1;
  cursor: pointer;
  padding: var(--space-1) var(--space-1);
}
.goals-widget__remove:hover { color: var(--ribbon-red); }

.goals-widget__calendar {
  border: none;
  background: none;
  font-size: var(--text-sm);
  line-height: 1;
  cursor: pointer;
  padding: var(--space-1) var(--space-1);
  opacity: 0.75;
}
.goals-widget__calendar:hover { opacity: 1; }

.goals-widget__add-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px dashed var(--rule);
}
.goals-widget__add-form[hidden] { display: none; }

.goals-widget__add-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.goals-widget__scope-select,
.goals-widget__project-select,
.goals-widget__doc-select,
.goals-widget__period-select {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  border: 1px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  padding: var(--space-1) var(--space-2);
}

.goals-widget__doc-select { max-width: 260px; }

.goals-widget__target-input {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  border: 1px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  padding: var(--space-1) var(--space-2);
  max-width: 140px;
  flex: 1;
}

.goals-widget__remove-project {
  display: block;
  margin-top: var(--space-6);
  border: none;
  background: none;
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-decoration: underline;
  cursor: pointer;
  padding: 0;
}
.goals-widget__remove-project:hover { color: var(--ribbon-red); }

/* ---------- project sharing ---------- */

.danger-panel { border-color: var(--ribbon-red); }
.danger-panel .sharing-panel__title { color: var(--ribbon-red); }
.danger-panel__list {
  margin: 0 0 var(--space-4);
  padding-left: var(--space-4);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}
.danger-panel__list li { margin-bottom: var(--space-1); }
.danger-panel__warning {
  border: 1px solid var(--ribbon-red);
  padding: var(--space-3) var(--space-3);
  font-size: var(--text-sm);
  margin: 0 0 var(--space-4);
}
.account-delete-form { display: flex; flex-direction: column; gap: var(--space-2); }
.account-delete-form .btn { align-self: flex-start; margin-top: var(--space-2); }

.settings-section {
  border: 1px solid var(--rule);
  background: var(--paper-raised);
  padding: var(--space-4) var(--space-4);
  margin-bottom: var(--space-5);
}
.settings-section__title {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-soft);
  margin-bottom: var(--space-2);
}
.settings-section__note {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  margin: 0 0 var(--space-3);
}
.settings-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--rule);
}
.settings-row:last-of-type { border-bottom: none; }
.settings-row__main {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  flex: 0 0 auto;
}
.settings-row__meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.settings-row__toggle {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  cursor: pointer;
}
.settings-row__action { margin-left: auto; }
.settings-row__link { flex: 1 1 14rem; min-width: 0; font-size: var(--text-xs); }
.settings-badge {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border: 1px solid var(--rule);
  padding: var(--space-1) var(--space-1);
  color: var(--ink-soft);
}
/* Public exposure is the thing an owner opens this page to check, so
   it's the one badge that carries the accent colour. */
.settings-badge--public {
  border-color: var(--ribbon-red);
  color: var(--ribbon-red);
}

.sharing-panel {
  border: 1px solid var(--rule);
  background: var(--paper-raised);
  padding: var(--space-4) var(--space-4);
  margin-bottom: var(--space-5);
}

.pending-merges {
  border: 1px solid var(--ribbon-red);
  background: var(--paper-raised);
  padding: var(--space-4) var(--space-4);
  margin-bottom: var(--space-5);
}
.pending-merges__title {
  font-family: var(--font-mono);
  font-weight: 600;
  color: var(--ribbon-red);
  margin-bottom: var(--space-3);
}
.pending-merge-card {
  border: 1px solid var(--rule);
  background: var(--paper);
  padding: var(--space-3);
  margin-bottom: var(--space-3);
}
.pending-merge-card:last-child { margin-bottom: 0; }
.pending-merge-card__header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.pending-merge-card__meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.pending-merge-card__preview {
  max-height: 10rem;
  overflow-y: auto;
  border: 1px dashed var(--rule);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
}
/* The reviewer's panel is red because it's a queue waiting on them.
   This one is only telling you something's in flight elsewhere, so it
   deliberately doesn't shout. */
.pending-merges--mine { border-color: var(--rule); }
.pending-merges--mine .pending-merges__title { color: var(--ink-soft); }

.merge-thread {
  border-top: 1px solid var(--rule);
  margin-bottom: var(--space-3);
  padding-top: var(--space-2);
}
.merge-thread__comment {
  padding: var(--space-2) 0 var(--space-2);
  border-bottom: 1px dashed var(--rule);
}
.merge-thread__author {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 600;
}
.merge-thread__time {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
  margin-left: var(--space-2);
}
.merge-thread__body {
  margin-top: var(--space-1);
  font-size: var(--text-sm);
  white-space: pre-wrap;
}
.merge-thread__form {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.merge-thread__input { flex: 1; font-size: var(--text-sm); }

.pending-merge-card__actions {
  display: flex;
  gap: var(--space-2);
}

.sharing-panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}

.sharing-panel__panel-title {
  font-family: var(--font-display);
  font-size: var(--text-base);
}

.sharing-panel__hide {
  border: none;
  background: none;
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-decoration: underline;
  cursor: pointer;
  padding: 0;
}
.sharing-panel__hide:hover { color: var(--ribbon-red); }

.sharing-panel__title {
  font-family: var(--font-display);
  font-size: var(--text-base);
  margin-bottom: var(--space-2);
}

.sharing-panel__title--spaced { margin-top: var(--space-4); }

.sharing-panel__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.sharing-panel__user {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  border: 1px solid var(--rule);
  padding: var(--space-1) var(--space-1) var(--space-1) var(--space-2);
}
.sharing-panel__username { flex: 0 0 auto; }
.sharing-panel__approver-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--ink-soft);
  cursor: pointer;
}
.sharing-panel__unshare { margin-left: auto; }


.sharing-panel__unshare {
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: var(--text-sm);
  line-height: 1;
  padding: 0 var(--space-1);
}
.sharing-panel__unshare:hover { color: var(--ribbon-red); }

.sharing-panel__empty {
  color: var(--ink-soft);
  font-style: italic;
  font-size: var(--text-sm);
}

.sharing-panel__info {
  color: var(--ink);
  font-size: var(--text-sm);
  margin: 0 0 var(--space-2);
}

.e2ee-panel__list {
  font-size: var(--text-sm);
  margin: 0 0 var(--space-3);
  padding-left: var(--space-5);
}

.e2ee-panel__list li { margin-bottom: var(--space-1); }

.e2ee-panel a:not(.btn) { color: var(--ribbon-red); }

/* .auth-form's display: flex beats the hidden attribute, which left the
   "turn on" form showing even while something was in the way. */
.e2ee-panel [hidden] { display: none; }

.e2ee-panel__status {
  font-size: var(--text-sm);
  margin: var(--space-2) 0;
}

.e2ee-panel__code {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  letter-spacing: 0.04em;
  word-break: break-all;
  background: var(--paper);
  border: 1px solid var(--rule);
  padding: var(--space-3);
  margin: var(--space-2) 0;
  user-select: all;
}

.e2ee-panel__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-2) 0;
}

.e2ee-panel__confirm {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  margin: var(--space-3) 0 var(--space-2);
}

.e2ee-panel__progress {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
}

.e2ee-panel__progress progress { width: 100%; }

/* The writing can't be shown until this browser holds the account key. */
.e2ee-gate {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--paper);
  padding: var(--space-4);
}

.e2ee-gate__card {
  max-width: 30rem;
  border: 1px solid var(--rule);
  background: var(--paper-raised);
  padding: var(--space-6);
  font-size: var(--text-sm);
}

.e2ee-gate__card h2 {
  font-family: var(--font-display);
  margin: 0 0 var(--space-3);
}

.e2ee-gate__card progress { width: 100%; margin-top: var(--space-3); }

.e2ee-gate__actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.totp-secret {
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  letter-spacing: 0.12em;
  background: var(--paper-raised);
  border: 1px solid var(--ink);
  padding: var(--space-3) var(--space-4);
  margin: 0 0 var(--space-4);
  word-break: break-all;
}

.backup-codes {
  font-family: var(--font-mono);
  font-size: var(--text-base);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2) var(--space-6);
  background: var(--paper-raised);
  border: 1px solid var(--ink);
  padding: var(--space-4) var(--space-5);
  margin: 0 0 var(--space-4);
}

.sharing-panel__form {
  display: flex;
  gap: var(--space-2);
}

/* ---------- public share-link pages (no account) ---------- */

.share-banner {
  background: var(--highlight);
  border: 1px solid var(--ink);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  padding: var(--space-2) var(--space-4);
  margin-bottom: var(--space-6);
}

.share-doc-row { text-decoration: none; color: inherit; }

.share-folder-heading {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-soft);
  padding: var(--space-4) var(--space-1) var(--space-1);
  border-bottom: 1px solid var(--rule);
}

.sharing-panel__link-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.sharing-panel__link-input {
  flex: 1;
  min-width: 200px;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  border: 1px solid var(--ink);
  background: var(--paper);
  padding: var(--space-2) var(--space-2);
  color: var(--ink-soft);
}

.sharing-panel__mode-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.sharing-panel__mode-label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}


/* Public documents (privacy, terms, cookies, FAQ) -- rendered from the
   Markdown files in scribe/legal/ so there's one copy of each, not a
   template that drifts from the document the team maintains. */
.doc { max-width: 42rem; line-height: 1.65; }
.doc h1 { font-family: var(--font-display); font-size: var(--text-2xl); margin: 0 0 var(--space-4); }
.doc h2 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  margin: var(--space-8) 0 var(--space-2);
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--rule);
}
.doc h3 { font-size: var(--text-base); margin: var(--space-6) 0 var(--space-2); }
.doc p { margin: 0 0 var(--space-4); }
.doc ul, .doc ol { margin: 0 0 var(--space-4); padding-left: var(--space-5); }
.doc li { margin-bottom: var(--space-1); }
.doc a { color: var(--ribbon-red); }
/* A detail the operator hasn't filled in yet (legal_render._GAP). Loud
   on purpose: it should be impossible to publish one without noticing. */
.doc-gap {
  background: var(--highlight);
  color: inherit;
  outline: 1px dashed var(--ribbon-red);
  padding: 0 0.15em;
}
.doc code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--paper-raised);
  padding: 0.1em 0.35em;
}
.doc blockquote {
  margin: 0 0 var(--space-4);
  padding: var(--space-2) var(--space-4);
  border-left: 3px solid var(--rule);
  background: var(--paper-raised);
  font-size: var(--text-sm);
}
.doc hr { border: none; border-top: 1px solid var(--rule); margin: var(--space-6) 0; }
.doc-code {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  padding: var(--space-3) var(--space-3);
  overflow-x: auto;
}
/* Tables carry most of the detail in these documents, so they have to
   stay readable on a phone rather than pushing the page sideways. */
.doc-table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 var(--space-4);
  font-size: var(--text-sm);
  display: block;
  overflow-x: auto;
}
.doc-table th, .doc-table td {
  border: 1px solid var(--rule);
  padding: var(--space-2) var(--space-2);
  text-align: left;
  vertical-align: top;
}
.doc-table th { background: var(--paper-raised); font-family: var(--font-mono); font-size: var(--text-xs); }

.policy-list { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-6); }
.policy-card {
  display: block;
  border: 1px solid var(--rule);
  background: var(--paper-raised);
  padding: var(--space-3) var(--space-4);
  text-decoration: none;
  color: inherit;
}
.policy-card:hover { border-color: var(--ink); }
.policy-card__title {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-base);
  margin-bottom: var(--space-1);
}
.policy-card__blurb { display: block; font-size: var(--text-sm); color: var(--ink-soft); }

/* ---------- welcome page (signed out) ---------- */

.welcome { max-width: 54rem; }
.welcome__hero {
  padding: var(--space-6) 0 var(--space-8);
  border-bottom: 1px solid var(--rule);
  margin-bottom: var(--space-8);
}
.welcome__title {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 5vw, 2.6rem);
  line-height: 1.15;
  margin: 0 0 var(--space-4);
}
.welcome__lede {
  font-size: var(--text-base);
  line-height: 1.6;
  max-width: 38rem;
  margin: 0 0 var(--space-6);
}
.welcome__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.welcome__note {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  margin: 0;
}
/* What wrisp won't do, said where it's seen rather than in small print. */
.welcome__promises {
  list-style: none;
  margin: 0 0 var(--space-3);
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  font-size: var(--text-sm);
  color: var(--ink);
}
.welcome__promises li::before {
  content: "\2713";
  color: var(--ribbon-red);
  font-weight: 600;
  margin-right: 0.4em;
}

/* Who it's for: three short cards, so a visitor finds themselves before
   reading a list of features. */
.welcome__audiences {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-8);
}
.welcome__audience {
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  padding: var(--space-4);
}
.welcome__audience h2 {
  font-family: var(--font-display);
  font-size: var(--text-base);
  margin: 0 0 var(--space-2);
}
.welcome__audience p {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.55;
}

/* Two columns where there's room, one where there isn't -- auto-fit
   rather than a media query so it also behaves in the writer's narrower
   layouts. */
.welcome__features {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--space-6) var(--space-8);
  margin-bottom: var(--space-8);
}
.welcome__feature h2 {
  font-family: var(--font-display);
  font-size: var(--text-base);
  margin: 0 0 var(--space-2);
}
.welcome__feature p {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--ink-soft);
}

.welcome__closing {
  border-top: 1px solid var(--rule);
  padding-top: var(--space-8);
}
.welcome__closing h2 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  margin: 0 0 var(--space-3);
}
.welcome__closing p {
  font-size: var(--text-base);
  line-height: 1.65;
  max-width: 40rem;
  margin: 0 0 var(--space-4);
}
.welcome__footer {
  margin-top: var(--space-8);
  padding-top: var(--space-4);
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.welcome__footer a { color: var(--ink-soft); }

/* The post-signup typing-capture choice. Both buttons are primary and
   sit side by side on purpose: consent that is easier to give than to
   refuse isn't freely given, so "Skip" must not read as the lesser
   option. */
.welcome-choice {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: var(--space-5) 0 var(--space-2);
}
.welcome-choice form { flex: 1 1 14rem; }
.welcome-choice .btn { width: 100%; }

/* An unprotected public link. Deliberately not styled as an error --
   it's a legitimate and common choice -- but it does carry the accent
   colour, because "anyone holding this link can read it" is the state
   worth noticing on a page whose job is auditing exposure. */
.settings-badge--open {
  border-color: var(--ribbon-red);
  color: var(--ribbon-red);
}

/* Per-author replay colouring. The strip sits under the scrubber and
   shows who was writing across the document's history at a glance. */
.ruler__authors {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 4px;
  display: flex;
  pointer-events: none;
}
.ruler__author-band { flex: 1 1 0; pointer-events: auto; }
.author-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-top: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.author-legend__item { display: flex; align-items: center; gap: var(--space-1); }
.author-legend__swatch {
  width: 0.7rem;
  height: 0.7rem;
  border: 1px solid var(--rule);
  flex: 0 0 auto;
}

/* Per-author prose colouring during replay. Classes rather than inline
   styles because the CSP is `style-src 'self'` with no unsafe-inline --
   a style="" attribute injected through innerHTML would be dropped in
   production while working fine in development.

   Must match AUTHOR_COLOURS in app.js, which drives the legend; the two
   are the same palette seen from either end. */
.page .author-ink-1 { color: #c2410c; }
.page .author-ink-2 { color: #1d4ed8; }
.page .author-ink-3 { color: #15803d; }
.page .author-ink-4 { color: #7e22ce; }
.page .author-ink-5 { color: #b91c1c; }
.page .author-ink-6 { color: #0f766e; }
.page .author-ink-7 { color: #a16207; }
.page .author-ink-8 { color: #be185d; }

/* No dark-theme override needed now: the fills are solid, so the white
   numerals sit on the same colour in either theme. */

/* Goal streaks. Amber-warm rather than the success green, so it reads as
   an accolade beside the progress bar rather than a second "met" signal. */
.goals-widget__streak {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  white-space: nowrap;
  color: rgb(150, 100, 10);
  border: 1px solid rgba(200, 140, 20, 0.55);
  background: rgba(200, 140, 20, 0.14);
  padding: var(--space-1) var(--space-1);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .goals-widget__streak { color: rgb(230, 175, 60); }
}
:root[data-theme="dark"] .goals-widget__streak { color: rgb(230, 175, 60); }

/* The writer had no width breakpoint at all: the sidebar held its 250px
   from 1920 down to 561, so at 768 the writing surface was 239px wide
   with a 319px toolbar wrapped above it -- unusable, and the narrowest
   the page ever got.

   Below 1000px the sidebar stacks above the writing area instead of
   beside it. The document list is worth a scroll on a narrow screen;
   the writing surface is not worth losing two thirds of. */
@media (max-width: 1000px) {
  .writer-layout {
    flex-direction: column;
    gap: var(--space-4);
  }
  .writer-sidebar {
    flex: 0 0 auto;
    /* Capped rather than free-flowing: a project with thirty documents
       would otherwise push the writing off the bottom of the screen. */
    max-height: 9rem;
    padding-right: 0;
    padding-bottom: var(--space-3);
    border-right: none;
    border-bottom: 1px solid var(--rule);
  }
}

/* ---------- the welcome page's live replay ---------- */

.welcome-replay {
  margin: 0 0 var(--space-6);
  max-width: 34rem;
}
.welcome-replay__page {
  background: var(--paper-raised);
  border: 1px solid var(--ink);
  box-shadow: 4px 4px 0 var(--shadow);
  padding: var(--space-6) var(--space-6);
  /* Fixed height so the box doesn't grow and shove the buttons down the
     page while the sentence types itself -- layout jumping under a
     reader's cursor is worse than a little empty space. */
  min-height: 9rem;
  display: flex;
  align-items: center;
}
@media (max-width: 600px) {
  .welcome-replay__page { min-height: 10rem; padding: var(--space-4); }
}
/* The pasted line: marked the way a replay shows text that arrived in
   one block, rather than typed. */
.welcome-replay__pasted {
  background: rgba(179, 58, 58, 0.14);
  color: inherit;
  box-shadow: inset 0 -2px 0 var(--ribbon-red);
  padding: 0 0.1em;
}
.welcome-replay__caption {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--ink-soft);
}
.welcome-replay__text {
  margin: 0;
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: 1.6;
}
.welcome-replay__caret {
  display: inline-block;
  width: 2px;
  margin-right: -2px;
  background: var(--ink);
  animation: welcome-caret-blink 1s step-end infinite;
  vertical-align: text-bottom;
}
.welcome-replay__caret--cutting {
  background: var(--ribbon-red);
  width: 3px;
}
@keyframes welcome-caret-blink {
  50% { opacity: 0; }
}
.welcome-replay__bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  min-height: 2rem;
}

@media (prefers-reduced-motion: reduce) {
  .welcome-replay__caret { animation: none; }
}

/* ---------- margin comments ---------- */

.writer__comments {
  flex: 0 0 auto;
  /* Capped like the goals block: comments are for glancing at, and a
     long thread shouldn't push the writing surface off the screen. */
  max-height: 11rem;
  overflow-y: auto;
  margin-top: var(--space-2);
  border-top: 1px solid var(--rule);
  padding-top: var(--space-2);
}
.comment {
  border-left: 3px solid var(--ribbon-red);
  padding: var(--space-2) 0 var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}
/* A comment whose text has been deleted. Muted rather than hidden --
   the note is still part of how the document got here, and quietly
   dropping it when a line is cut loses the discussion with the
   sentence. */
.comment--orphaned {
  border-left-color: var(--ink-soft);
  opacity: 0.75;
}
.comment__quote {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  margin-bottom: var(--space-1);
}
.comment__body { font-size: var(--text-sm); line-height: 1.5; }
.comment__meta {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
}
.comment__reply {
  margin-top: var(--space-1);
  padding-left: var(--space-3);
  border-left: 1px solid var(--rule);
  font-size: var(--text-sm);
}
.comment__actions { display: flex; gap: var(--space-2); margin-top: var(--space-1); }
.comment__actions .btn { font-size: var(--text-xs); padding: var(--space-1) var(--space-2); }

/* One block per person who typed in the document. Separated visibly,
   because a pooled figure was the bug: two rhythms averaged together
   describe nobody. */
.rhythm-person {
  border-top: 1px solid var(--rule);
  padding-top: var(--space-3);
  margin-top: var(--space-3);
}
.rhythm-person:first-child { border-top: none; padding-top: 0; margin-top: 0; }
.rhythm-person__name {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink);
  margin-bottom: var(--space-2);
}

/* Twenty-one buttons in a 736px bar wrapped to three rows -- 124px of
   chrome above a 416px writing area. Shrinking the buttons rather than
   removing any: every one of them is someone's most-used control, and a
   hidden overflow menu trades a visible row for a hunt. */
.writer__toolbar .btn {
  padding: var(--space-1) var(--space-1);
  font-size: var(--text-xs);
}

/* The goals readout is a glance, not a panel. Collapsed to its summary
   line by default; the details are one click away and the writing gets
   the 88px back. */
.writer-shell .doc-goals {
  max-height: none;
  overflow: visible;
}
/* The widget already had its own collapse, with its own persistence --
   adding a second one was redundant. It simply defaults to collapsed
   inside the writer (see renderGoalsWidget). */

/* Inside the writer the goals widget is a one-line summary, so it
   should not carry a settings-panel's padding and margin. 0.9rem of
   padding plus 1.2rem of margin was most of the 70px a collapsed block
   still took from the writing area. */
.writer-shell .doc-goals .goals-widget {
  padding: var(--space-2) var(--space-2);
  margin-bottom: 0;
}
.writer-shell .doc-goals .goals-widget--collapsed .goals-widget__header {
  margin-bottom: 0;
}

/* ---------- goals in the writer ---------- */
/*
   A vertical gauge per goal rather than the full widget.
   The widget is a five-column row built for a page; even stacked it
   wanted 240px of a screen whose whole point is the writing, for
   something you glance at between sentences. This answers the same
   question -- how far through today am I -- in about 3rem, so it can
   stay on screen instead of hiding behind a toggle.
*/
.writer-goals {
  flex: 0 0 auto;
  display: flex;
  gap: var(--space-2);
  /* Top, not stretched. Stretching made each gauge as tall as the
     column -- 1330px on a 1440px window -- and since the gauge stacks
     bottom-upwards, its bar and count ended up down at the foot of the
     screen, level with nothing. Sized to its content it sits beside the
     first lines of the page, where the eye already is. */
  align-items: flex-start;
  min-height: 0;
  padding-left: var(--space-2);
}
.writer-goals .doc-goals {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  align-items: stretch;
}

.goal-gauge {
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: var(--space-1);
  width: 3rem;
}
.goal-gauge__track {
  /* A definite height rather than growing to fill. Stretching tied the
     bar's length to the window -- the same 60% looked different on a
     laptop and a monitor, and made "am I nearly there" a question about
     screen size. A fixed measure reads the same everywhere. */
  flex: 0 0 auto;
  height: 12rem;
  width: 0.7rem;
  min-height: 6rem;
  background: var(--paper);
  border: 1px solid var(--ink);
  /* Fills from the bottom, the way a measure fills. */
  display: flex;
  flex-direction: column-reverse;
}
.goal-gauge__fill {
  width: 100%;
  height: var(--gauge-pct, 0%);
  background: var(--highlight);
  transition: height 0.2s ease;
}
.goal-gauge--met .goal-gauge__fill { background: #15803d; }

.goal-gauge__label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
  /* Reads up the spine, so the count costs no width of its own. */
  writing-mode: vertical-rl;
  text-orientation: mixed;
  transform: rotate(180deg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-height: 11rem;
}
.goal-gauge--met .goal-gauge__label { color: #15803d; }

@media (prefers-reduced-motion: reduce) {
  .goal-gauge__fill { transition: none; }
}

/* Below the writer's stacking breakpoint the gauges sit in a row under
   the text, where vertical text would be unreadable. */
@media (max-width: 1000px) {
  .writer-goals { padding-left: 0; padding-top: var(--space-2); }
  .goal-gauge { flex-direction: row; align-items: center; width: auto; gap: var(--space-2); }
  .goal-gauge__track { width: 100%; min-height: 0; height: 0.55rem; flex-direction: row; }
  .goal-gauge__fill { height: 100%; width: var(--gauge-pct, 0%); }
  .goal-gauge__label { writing-mode: horizontal-tb; transform: none; max-height: none; }
}
.writer-goals .goals-widget__streak {
  white-space: normal;
}

/* Commented text, marked without touching the DOM.
   ::highlight() styles ranges registered through the CSS Custom
   Highlight API -- it accepts only colour, decoration and shadow
   properties, which is exactly why it can't disturb layout or the
   editor's saved markup. */
::highlight(comment-anchor) {
  background-color: rgba(200, 140, 20, 0.28);
  text-decoration: underline;
  text-decoration-color: var(--ribbon-red);
  text-decoration-style: dotted;
  text-underline-offset: 0.18em;
}

/* Inline comment composer. Replaces prompt(), which blocked the page,
   gave one line for something people write paragraphs in, and hid the
   selection you were commenting on. */
.comment-composer {
  border-left: 3px solid var(--ribbon-red);
  padding: var(--space-2) 0 var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}
.comment-composer--reply {
  border-left-width: 1px;
  border-left-color: var(--rule);
  margin: var(--space-2) 0 0 var(--space-3);
}
.comment-composer__quote {
  font-style: italic;
  margin-bottom: var(--space-2);
}
.comment-composer__quote:empty { display: none; }
.comment-composer__input {
  width: 100%;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  line-height: 1.5;
  padding: var(--space-2);
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--ink);
  resize: vertical;
}

/* The name a share link's owner chose to be known by on that link.
   Quiet: it's attribution, not a headline. */
/* Reporting a shared link, and reviewing reports. */
.share-report {
  margin-top: var(--space-8);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.share-report a { color: var(--ink-soft); }

.report-form__reasons {
  border: 0;
  padding: 0;
  margin: 0 0 var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.report-form__reason {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
}
.report-form__details { min-height: 6rem; resize: vertical; }
.report-list { margin: 0; padding-left: var(--space-5); font-size: var(--text-sm); }
.report-list li { margin-bottom: var(--space-1); }
.report-details { white-space: pre-wrap; border-left: 2px solid var(--rule); padding-left: var(--space-3); }
.report-shown { margin-bottom: var(--space-4); }
.report-shown__page { min-height: 0; max-height: 32rem; overflow: auto; }
.report-form a { color: var(--ribbon-red); }

/* An encrypted share link: when it was last published, and a way to
   publish what's been written since. */
.share-publish {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}

.share-byline--quiet { margin-top: 0; }

.share-byline {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  margin: calc(-1 * var(--space-2)) 0 var(--space-4);
}

.share-name-btn {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  border-style: dashed;
  max-width: 10rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The styled replacement for prompt(). Narrower than the merge and
   compare modals -- it asks one or two short questions, and a dialog
   sized for a diff would make a single text field look lost. */
.ask-modal { max-width: 26rem; }
.ask-modal__form .newdoc-form__input {
  width: 100%;
  margin-bottom: var(--space-1);
}
.ask-modal__form .settings-row__meta {
  display: block;
  margin-bottom: var(--space-3);
}
.ask-modal__form .auth-form__label { margin-top: var(--space-2); }
.ask-modal__form .btn { margin-top: var(--space-3); }

/* The link's name. Looks like a field you can edit rather than a
   button, because that's what it is. */
.share-label-btn {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  border-style: dashed;
  max-width: 12rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* View counts sit quietly beside a link -- information the owner
   glances at, not a metric the page is about. */
.share-views {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
  white-space: nowrap;
}

/* Today.
   An outline rather than a fill, because the fill already carries the
   day's status -- met, partial, missed. Colouring today differently
   would mean losing that, and "how am I doing today" is exactly the
   thing someone opens this calendar to see.
   The outline sits inside the cell so it doesn't shift the grid. */
.goal-calendar__cell--today {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
}

/* ---------- comments in the margin ---------- */

/* The page and its comment margin, side by side. This wrapper is the
   flex child that grows, so the page keeps filling the height it did
   when it was a direct child of the shell. */
.writer__with-margin {
  flex: 1 0 auto;
  display: flex;
  align-items: stretch;
  gap: var(--space-4);
}

/* The width setting (narrow/medium/wide) caps the writing measure, and
   it has to keep applying to the PAGE rather than to page-plus-margin
   -- otherwise opening a comment would narrow the text you're reading,
   which is the opposite of what a margin is for. */
.writer-shell[data-width="medium"] .writer__with-margin > .page--editable { max-width: 64rem; }
.writer-shell[data-width="narrow"] .writer__with-margin > .page--editable { max-width: 46rem; }
.writer__with-margin > .page--editable { flex: 1 1 auto; }

.writer-shell .writer__comments {
  flex: 0 0 16rem;
  max-height: none;         /* it has its own column now */
  margin-top: 0;
  border-top: none;
  border-left: 1px solid var(--rule);
  padding: 0 0 0 var(--space-4);
  overflow-y: auto;
  min-height: 0;
}

/* Below the stacking breakpoint there isn't room for a margin, so it
   goes back under the text -- a 16rem column beside a 350px page would
   leave neither usable. */
@media (max-width: 1000px) {
  .writer__with-margin { flex-direction: column; }
  .writer-shell .writer__comments {
    flex: 0 0 auto;
    max-height: 11rem;
    border-left: none;
    /* Above the page, not below it. Below was fine while the page was a
       fixed box; now that it grows, "below" meant after the last line
       of the document -- measured at 3,444px of scrolling on a tablet
       in portrait and 6,110px on a phone. Above, the list is always a
       short scroll from the top, and click-to-jump still takes you to
       the line. */
    order: -1;
    border-bottom: 1px solid var(--rule);
    padding: 0 0 var(--space-2);
  }
}

/* The streak, beside its gauge.
   A flame and a number rather than "3 days in a row": the column is
   about 56px wide, and the words only fit by turning them on their
   side, where a reader has to work to take in something meant for a
   glance. The full phrasing is in the tooltip. */
.goal-gauge__streak {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  font-size: var(--text-2xs);
  line-height: 1;
  color: var(--ink-soft);
  cursor: default;
}
.goal-gauge__streak-n {
  font-family: var(--font-mono);
  font-weight: 700;
  color: var(--ink);
}
/* A met goal's streak is the one worth looking at. */
.goal-gauge--met .goal-gauge__streak-n { color: #15803d; }

/* The note you last clicked. Marked so it's clear which one the caret
   was sent to -- with several notes in the margin, a jump with no
   indication of its source reads as the page moving on its own. */
.comment--current {
  border-left-color: var(--ink);
  border-left-width: 3px;
}
.comment { cursor: pointer; }
/* ...but not the things inside it that do something else. */
.comment button,
.comment textarea,
.comment a { cursor: auto; }
.comment button { cursor: pointer; }

/* Comments hidden: the margin goes and the page takes its width back,
   so hiding notes gives the writing more room rather than leaving an
   empty strip where they were. */
.writer-shell--comments-hidden .writer__comments { display: none; }
.comments-toggle-btn[aria-pressed="true"] {
  border-style: dashed;
  color: var(--ink-soft);
}

/* Narrow: seven cells don't fit on one line, so each goal takes three
   short ones -- what it's for and its buttons, the bar, then the period
   and the count. The count used to share the bar's line in the space
   of two icon buttons (3.6rem), so "1,234 / 50,000 words" ran 2rem past
   the panel's edge on a 390px phone, and a long project name pushed its
   cell wider than the screen because grid items don't shrink below
   their content unless told to. */
@media (max-width: 700px) {
  .goals-widget__rows { grid-template-columns: minmax(0, 1fr); row-gap: var(--space-3); }
  .goals-widget__row {
    grid-column: auto;
    /* Fixed widths for the buttons, so they line up down the list here
       too without subgrid's shared tracks. */
    grid-template-columns: minmax(0, 1fr) auto 1.8rem 1.8rem;
    grid-template-areas:
      "scope streak calendar remove"
      "bar   bar    bar      bar"
      "label count  count    count";
    column-gap: var(--space-2);
    row-gap: var(--space-1);
  }
  .goals-widget__row .goals-widget__scope    { grid-area: scope; min-width: 0; max-width: none; }
  /* Kept, not hidden: without it a daily and a weekly goal on the same
     project are indistinguishable. */
  .goals-widget__row .goals-widget__label    { grid-area: label; }
  .goals-widget__row .goals-widget__bar      { grid-area: bar; max-width: none; }
  .goals-widget__row .goals-widget__count    { grid-area: count; justify-self: end; }
  .goals-widget__row .goals-widget__streak   { grid-area: streak; justify-self: end; }
  /* "12 days in a row" squeezed the goal's name to "Ov..." on a small
     phone: the flame and the number stay, the words are still read out
     and still in the tooltip. */
  .goals-widget__streak-words {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
  .goals-widget__row .goals-widget__calendar { grid-area: calendar; }
  .goals-widget__row .goals-widget__remove   { grid-area: remove; }
}

/* ---------- the growing page: what stays in reach ---------- */

/* The toolbar stays at the top while the document scrolls under it --
   formatting a paragraph forty pages down shouldn't mean scrolling back
   to the start. It already has a solid background. */
.writer-shell .writer__toolbar {
  position: sticky;
  top: 0;
  z-index: 5;
}

/* The word count and save state stay at the bottom edge. A sticky
   footer can't be drawn over the text, because the text scrolls past
   it -- which retires the overlap fixed for short tablet screens. */
.writer-shell .writer__status {
  position: sticky;
  bottom: 0;
  z-index: 5;
  background: var(--paper);
  padding: var(--space-1) 0;
  margin-top: 0;
}

/* The chapter list stays put beside a scrolling chapter, scrolling on
   its own if the outline is longer than the screen. */
.writer-pane__body > .chapter-nav {
  position: sticky;
  top: 0;
  align-self: flex-start;
  max-height: 100%;
}

/* Side by side, the margin grows with the page rather than scrolling
   itself: one scrollbar for the document, not two. Stacked on narrow
   screens it keeps its own short scrolling box. */
@media (min-width: 1001px) {
  .writer-shell .writer__comments { overflow: visible; }
}

/* Below 560px the pane body stacks into a column (chapter list above
   the writing). Two of the wide-screen rules don't survive that:
   flex-start would shrink the page to the width of its text rather than
   the screen, and a sticky chapter list would pin itself over the prose
   beside the sticky toolbar. Scoped to exactly that breakpoint: applied
   more widely, stretch clamps a side-by-side column to the window
   height, and the sticky toolbar scrolls away once the column ends. */
@media (max-width: 560px) {
  .writer-pane__body { align-items: stretch; }
  .writer-pane__body > .chapter-nav { position: static; max-height: none; }
  /* In a column the flex axis is height, so a shrinkable shell was
     squeezed to fit the screen -- measured at 390px tall holding 6,447px
     of text, with the sticky toolbar scrolling away after the first
     screenful. It grows with the document instead. */
  .writer-pane__body > .writer-shell { flex: 1 0 auto; }
}

/* ---------- writing mode: the page first ---------- */

/* The title bar slims down in the editor. Before: 78px of title bar,
   62px of document name and a toolbar of up to three rows put the
   first line of writing 27% of the way down a 1600x900 screen, 37% at
   1280x720, and below the first screenful on a phone. Everything here
   is still reachable; it just stops competing with the text. */
body.is-writing .topbar {
  padding-top: var(--space-1);
  padding-bottom: var(--space-1);
  gap: var(--space-3);
}
body.is-writing .topbar__mark { font-size: var(--text-lg); }
/* The tagline is for the welcome page, and search for the project
   list -- neither is something you reach for mid-sentence. */
body.is-writing .topbar__sub,
body.is-writing .topbar__search { display: none; }

.app--writer { padding-top: var(--space-2); }

/* The document's name: one slim line rather than a heading. */
.app--writer .writer-pane__header { margin-bottom: var(--space-1); }
.app--writer .writer-pane__title {
  font-size: var(--text-base);
  line-height: 1.3;
}

/* One toolbar row. Formatting takes the space it's given and scrolls
   sideways if that isn't enough; Comment and "More" keep their place. */
.writer-shell .writer__toolbar { flex-wrap: nowrap; }
.writer__format {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: thin;
}
.writer__format > * { flex: 0 0 auto; }
.writer__toolbar > .writer__toolbar-spacer { flex: 0 0 var(--space-2); }

.writer__more { position: relative; flex: 0 0 auto; }
.writer__more-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  min-width: 13rem;
  padding: var(--space-1);
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.writer__more-menu[hidden] { display: none; }
.writer__more-menu .btn {
  text-align: left;
  border-color: transparent;
  white-space: nowrap;
}
.writer__more-menu .btn:hover { border-color: var(--rule); }
.writer__more-menu .writer__hint {
  padding: var(--space-1) var(--space-2) 0;
  border-top: 1px solid var(--rule);
  margin-top: var(--space-1);
}

/* A phone has room for one row of formatting or the actions, not both:
   two deliberate rows, formatting first and full width. */
@media (max-width: 560px) {
  .writer-shell .writer__toolbar { flex-wrap: wrap; }
  .writer__format { flex: 1 1 100%; }
  .writer__toolbar > .writer__toolbar-spacer { flex: 1 1 auto; }
}

/* The name sits in an h3 for its outline role, which brought the
   browser's default margins: 19px above and below a 24px line, 61px in
   all. */
.app--writer .writer-pane__title-row { margin: 0; }

/* Formatting buttons sized for one row. At 1280 wide the row needed
   722px and had 511, so it scrolled; tighter padding and shorter list
   labels (their names stay in the tooltip and for screen readers) let
   it fit. */
.writer__format .fmt-btn {
  padding: 2px var(--space-1);
  min-width: 1.75rem;
}

.writer__more-group {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
  padding: var(--space-1) var(--space-2) 0;
}
.writer__more-group:not(:first-child) {
  border-top: 1px solid var(--rule);
  margin-top: var(--space-1);
}
.writer__more-menu .fmt-btn.writer__more-item {
  text-align: left;
  width: 100%;
  padding: var(--space-1) var(--space-2);
  white-space: nowrap;
}


/* The one-time suggestion to watch a first replay (maybeSuggestReplay). */
.writer__replay-hint {
  font-size: var(--text-xs);
  color: var(--ink-soft);
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  margin: 0 var(--space-3);
}
.writer__replay-hint a { color: var(--ribbon-red); }
.writer__replay-hint-close {
  background: none; border: 0; padding: 0 var(--space-1);
  color: var(--ink-soft); cursor: pointer; font-size: var(--text-sm); line-height: 1;
}
.writer__replay-hint-close:hover { color: var(--ink); }


/* ---------- the administrators' launch page ---------- */

.launch-checks { list-style: none; margin: 0 0 var(--space-3); padding: 0; }
.launch-check { display: flex; gap: var(--space-3); padding: var(--space-2) 0; border-bottom: 1px solid var(--rule); }
.launch-check__mark { font-family: var(--font-mono); color: var(--ribbon-red); width: 1.2em; flex: 0 0 auto; }
.launch-check--done .launch-check__mark { color: var(--ink-soft); }
.launch-check__body { display: flex; flex-direction: column; gap: var(--space-1); }
.launch-check__detail { font-size: var(--text-sm); color: var(--ink-soft); }
.launch-gaps { margin: var(--space-2) 0; font-size: var(--text-sm); }
.launch-gaps summary { cursor: pointer; }
.launch-gaps ul { margin: var(--space-2) 0; padding-left: var(--space-5); }
.launch-gaps a { color: var(--ribbon-red); }

.launch-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--space-4); margin-bottom: var(--space-5); }
.launch-stat__value { font-family: var(--font-display); font-size: var(--text-2xl); color: var(--ink); line-height: 1.1; }
.launch-stat__label { font-size: var(--text-xs); color: var(--ink-soft); margin-top: var(--space-1); }

.launch-chart { margin: 0 0 var(--space-3); }
.launch-chart__title { font-family: var(--font-mono); font-size: var(--text-xs); text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ink-soft); margin-bottom: var(--space-2); }
.launch-chart__plot { display: block; width: 100%; height: 8rem; overflow: visible; }
.launch-chart__baseline { stroke: var(--rule); stroke-width: 1; vector-effect: non-scaling-stroke; }
.launch-chart__hit { fill: transparent; }
.launch-chart__bar { fill: var(--ribbon-red); }
.launch-chart__col:hover .launch-chart__hit { fill: var(--highlight); opacity: 0.5; }
.launch-chart__axis { display: flex; justify-content: space-between; font-family: var(--font-mono);
  font-size: var(--text-2xs); color: var(--ink-soft); margin-top: var(--space-1); }
.launch-table td, .launch-table th { font-variant-numeric: tabular-nums; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}


/* ---------- the November challenge (november.js, november*.html) ---------- */

.november__when { font-family: var(--font-mono); font-size: var(--text-xs); text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ribbon-red); margin: 0 0 var(--space-2); }
.november__lede { max-width: 40rem; color: var(--ink-soft); margin: 0 0 var(--space-4); }
.november__of { font-size: var(--text-base); color: var(--ink-soft); font-weight: normal; }
.november__totals { margin: 0 0 var(--space-4); }

.november-bar { display: block; width: 100%; height: 0.6rem; margin: var(--space-2) 0 var(--space-4); }
.november-bar__track { fill: var(--rule); }
.november-bar__fill { fill: var(--ribbon-red); }
.november-days__pace { stroke: var(--ink-soft); stroke-width: 1; stroke-dasharray: 4 3; vector-effect: non-scaling-stroke; }
.november-figures { display: flex; flex-wrap: wrap; gap: var(--space-6); margin-bottom: var(--space-2); }
.november-form { max-width: 24rem; }
.november-share { display: flex; gap: var(--space-2); align-items: center; cursor: pointer; }
.november-share__link { display: flex; gap: var(--space-2); margin: var(--space-2) 0; max-width: 34rem; }
.november-share__link .auth-form__input { flex: 1 1 auto; min-width: 0; }
.november-share__badge { display: block; max-width: 100%; height: auto; margin: var(--space-2) 0; }

.november-panel {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--space-3); padding: var(--space-3) var(--space-4); margin-bottom: var(--space-4);
  border: 1px solid var(--ink); background: var(--paper-raised); color: inherit; text-decoration: none;
}
.november-panel__text { display: flex; flex-direction: column; gap: var(--space-1); max-width: 44rem; }
.november-panel__text .november__when { margin: 0; }
.november-panel__actions { display: flex; gap: var(--space-2); }
.november-panel--joined { display: block; }
.november-panel--joined .november-bar { margin: var(--space-2) 0 0; }
.november-panel--joined:hover { background: var(--paper); }

.november-banner {
  display: block; text-align: center; padding: var(--space-2) var(--space-4);
  background: var(--ink); color: var(--paper); text-decoration: none; font-size: var(--text-sm);
}
.november-banner:hover { background: var(--ribbon-red); }
.november-public { max-width: 44rem; }
.november-public__name { font-family: var(--font-display); font-size: var(--text-2xl); margin: 0 0 var(--space-4); }
.november-replay .btn { display: inline-block; }
.launch-subtitle { font-size: var(--text-sm); margin: var(--space-4) 0 var(--space-2); }

/* ---------- bookmarks in Replay (renderPlayer) ---------- */

.ruler__bookmarks { position: absolute; inset: 0; pointer-events: none; }
.ruler__bookmark {
  position: absolute;
  top: 0.15rem;
  width: 1.1rem;
  height: 1.1rem;
  transform: translateX(-50%);
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  pointer-events: auto;
}
/* A small flag on a staff: the staff meets the track, the flag above it. */
.ruler__bookmark::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0.2rem;
  width: 2px;
  height: 1.05rem;
  background: var(--ribbon-red);
  transform: translateX(-1px);
}
.ruler__bookmark::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 0.2rem;
  width: 0.5rem;
  height: 0.38rem;
  background: var(--ribbon-red);
}
.ruler__bookmark:hover::after, .ruler__bookmark:focus-visible::after { background: var(--ink); }

.replay-bookmarks { margin-top: var(--space-4); border-top: 1px solid var(--rule); padding-top: var(--space-3); }
.replay-bookmarks__title { font-family: var(--font-mono); font-size: var(--text-xs); text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ink-soft); margin-bottom: var(--space-2); }
.replay-bookmarks__title .settings-row__meta { text-transform: none; letter-spacing: 0; margin-left: var(--space-2); }
.replay-bookmarks__list { list-style: none; margin: 0; padding: 0; }
.replay-bookmarks__row { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3);
  padding: var(--space-2) 0; border-bottom: 1px solid var(--rule); }
.replay-bookmarks__note { flex: 1 1 14rem; min-width: 0; overflow-wrap: anywhere; }
.replay-bookmarks__actions { display: flex; gap: var(--space-2); }
.replay-bookmarks__actions .btn { white-space: nowrap; }

/* ---------- the public record of fingerprints (fingerprints.html) ---------- */
.fingerprint-table { width: 100%; margin: var(--space-4) 0 var(--space-6); }
.fingerprint-table code { overflow-wrap: anywhere; }
@media (max-width: 600px) {
  .fingerprint-table th:nth-child(3), .fingerprint-table td:nth-child(3) { display: none; }
}

/* Fingerprints checked in the reader's browser (fingerprint.js): one
   quiet line above a replay, open for what exactly was checked. */
.fingerprint-slot { margin: 0 0 var(--space-4); }
.fingerprint-slot[hidden] { display: none; }
.fingerprint {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  margin: 0;
}
.fingerprint__summary {
  cursor: pointer;
  width: fit-content;
  max-width: 100%;
}
.fingerprint__mark { font-weight: 700; }
.fingerprint[data-state="verified"] .fingerprint__summary { color: var(--success-text); }
.fingerprint[data-state="mismatch"] .fingerprint__summary { color: var(--ribbon-red); font-weight: 600; }
.fingerprint__body {
  margin-top: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-left: 2px solid var(--rule);
  max-width: 42rem;
}
.fingerprint__line { margin: 0 0 var(--space-2); line-height: 1.5; }
.fingerprint__line:last-child { margin-bottom: 0; }
.fingerprint__compare {
  font: inherit;
  color: var(--ink);
  background: none;
  border: 0;
  padding: 0;
  text-decoration: underline;
  cursor: pointer;
}
.fingerprint__compare:disabled { opacity: 0.6; cursor: progress; }
.fingerprint__note:empty { display: none; }
.fingerprint a { color: var(--ink); }
