/* ==========================================================================
   ROAD TO EMMAUS — timeline styles
   Warm-dark adaptation of the OnMeaning palette. Color belongs to threads;
   genre is communicated by glyph construction only.
   Layout rules (from the spec, learned the hard way):
   - All text is DOM text sized in CSS px. SVG is only for fixed-size glyphs.
   - Vertical spine. Thread lanes render per row from book-order indices.
   - One breakpoint: 900px. Below it detail is a full-screen view.
   ========================================================================== */

:root {
  --em-ground: #0b0906;
  --em-ground-2: #131009;
  --em-bone: #efe8d7;
  --em-bone-dim: #b3a88f;
  --em-rule: #332b1a;
  --em-rule-soft: #221c10;
  --em-dim: #8a7d61;
  /* Was #5c5340 at 2.62:1 on the ground and 2.50 on raised surfaces — well
     under AA. It carries era years, genre tags and menu subtitles, all of
     which are informational, not decorative. */
  --em-faint: #847d6f;
  --em-lens: #ffce8a;
  --em-lens-soft: #f2a65a;
  --em-serif: "Newsreader", Georgia, serif;
  --em-sans: "Inter", -apple-system, "Segoe UI", sans-serif;
  --em-mono: "IBM Plex Mono", ui-monospace, "Cascadia Mono", monospace;
}

/* Channels, so the accent and the overlay washes can change with the theme
   rather than being written into each rule. */
:root {
  --em-lens-rgb: 255 206 138;   /* gold, for glows and washes */
  --em-tint: 255 255 255;       /* hover overlay: light over dark */
  --em-scrim: 5 4 2;            /* modal backdrop */
  --em-raised: 24 20 12;        /* toast and floating surfaces */
  --em-plate: 0 0 0;            /* inset control backgrounds */

  /* Depth. On black a shadow is a black smear and reads as depth; on cream
     the same smear reads as dirt, so light uses a warm brown at a fraction
     of the opacity. */
  --em-shadow-rgb: 0 0 0;
  --em-shadow-a: 0.55;
  --em-shadow-a2: 0.25;

  /* The wash behind the header and the bloom at the top of the page. Both
     were hardcoded browns that turned into a dark stain on cream. */
  --em-glow: #1a1408;
  --em-top-wash: #120e07;

  /* The book list sits a step off the reading panel so the two read as
     separate surfaces rather than one sheet. */
  --em-list-bg: #0e0b07;
  --em-list-rgb: 14 11 7;
  --em-hero-core: #171208;
}

/* ---------- Light theme ----------
   The dark timeline reads as light in darkness: books glow, promises light
   up. That metaphor cannot survive a straight inversion, so light is its own
   design — ink and highlighter rather than lamp and halo. The accent drops
   from pale gold to a deep one that can hold its own on cream, and the
   overlay washes flip from white to ink. Thread colours are darkened in JS,
   where they live. */
:root[data-theme="light"] {
  --em-ground: #faf7f0;
  --em-ground-2: #f1ebdc;
  --em-bone: #241f16;
  --em-bone-dim: #4d4535;
  --em-rule: #e2dac6;
  --em-rule-soft: #ece5d5;
  /* A quiet tone on cream still has to clear 4.5:1, so the faint tier cannot
     be as faint as it is on black. Both are solved against --em-ground-2, the
     darker of the two surfaces, so they hold in menus and sheets as well as
     on the page. --em-dim is pushed further than it needs to be, to keep a
     visible step between the two tiers. */
  --em-dim: #5a5342;
  /* Solved to 5.0 rather than 4.5: tags and quiet labels sit on the lead
     section wash, which is tinted with the thread colour and costs the last
     fraction of contrast. */
  --em-faint: #696356;
  --em-lens: #815c16;
  --em-lens-soft: #85631b;
  --em-lens-rgb: 169 126 43;
  --em-tint: 36 31 22;
  --em-scrim: 60 52 36;
  --em-raised: 255 253 247;
  --em-plate: 120 108 84;

  --em-shadow-rgb: 92 74 42;
  --em-shadow-a: 0.14;
  --em-shadow-a2: 0.07;

  /* A warm highlight rather than a dark bloom, and a header wash barely
     distinguishable from the page — the point is a hairline of separation,
     not a banner. */
  --em-glow: #fffaf958;
  --em-top-wash: #f3ede0;

  --em-list-bg: #f5f1e7;
  --em-list-rgb: 245 241 231;
  --em-hero-core: #fffdf6;
}

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

/* Author `display` rules outrank the UA sheet's [hidden] { display: none } */
[hidden] {
  display: none !important;
}

/* The browser's default body margin would inset the whole app by 8px and
   clip the bottom of the 100dvh shell. This page loads no other stylesheet,
   so the reset has to live here. */
html.em-page,
.em-page body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: var(--em-ground);
}

.em-app {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  background:
    radial-gradient(90rem 30rem at 50% -18rem, var(--em-glow) 0%, transparent 65%),
    var(--em-ground);
  color: var(--em-bone);
  font-family: var(--em-sans);
  -webkit-font-smoothing: antialiased;
}

.em-app ::selection {
  background: rgb(var(--em-lens-rgb) / 0.25);
}

.em-app :focus-visible,
.em-dialog :focus-visible {
  outline: 2px solid var(--em-lens);
  outline-offset: 2px;
  border-radius: 3px;
}

.em-app button,
.em-dialog button {
  -webkit-tap-highlight-color: transparent;
  font-family: inherit;
  cursor: pointer;
}

.em-mono {
  font-family: var(--em-mono);
}

/* Text links used inside the app (lede, endcap, dialogs).
   Set in the reading face and underlined, not in spaced mono capitals over a
   full-width rule: that combination read as a section heading, and the one on
   the landing card was being taken for a title rather than something to tap.
   The padding gives a thumb something to land on without thickening the
   underline, which stays tight to the words. */
.em-link {
  display: inline-block;
  background: none;
  border: 0;
  padding: 9px 0;
  font-family: var(--em-sans);
  font-size: 13.5px;
  color: var(--em-lens);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--em-lens) 45%, transparent);
  transition: color 0.2s ease, text-decoration-color 0.2s ease;
}

.em-link:hover {
  color: var(--em-lens-soft);
  text-decoration-color: currentColor;
}

/* Solid + ghost buttons (welcome panel) */
.em-btn-solid,
.em-btn-ghost {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  font-weight: 600;
  padding: 11px 20px;
  min-height: 44px;
  border-radius: 999px;
  border: 1px solid transparent;
  transition: transform 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

/* The accent inverts between themes — pale gold on black, deep gold on cream
   — so the label on it has to invert too, or it reads as dark on dark. */
.em-btn-solid {
  background: var(--em-lens);
  color: var(--em-on-lens);
}

:root {
  --em-on-lens: #17120a;
  --em-lens-hover: #ffdba6;
}

:root[data-theme="light"] {
  --em-on-lens: #fffdf7;
  --em-lens-hover: #75530f;
}

.em-btn-solid:hover {
  background: var(--em-lens-hover);
  transform: translateY(-1px);
}

.em-btn-ghost {
  background: transparent;
  border-color: var(--em-rule);
  color: var(--em-bone-dim);
}

.em-btn-ghost:hover {
  border-color: var(--em-dim);
  color: var(--em-bone);
}

/* ---------- Top bar ---------- */

.em-top {
  flex-shrink: 0;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px 12px;
  border-bottom: 1px solid var(--em-rule);
  background: linear-gradient(180deg, var(--em-top-wash) 0%, transparent 100%);
}

@media (min-width: 900px) {
  .em-top {
    padding: 14px 24px 14px;
  }
}

.em-top-left {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
}

.em-theme-toggle {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  background: transparent;
  color: var(--em-dim);
  transition: color 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
}

.em-theme-toggle svg {
  width: 16px;
  height: 16px;
}

.em-theme-toggle:hover {
  color: var(--em-lens);
  border-color: var(--em-lens);
  background: rgb(var(--em-lens-rgb) / 0.08);
}

.em-theme-toggle .icon-sun { display: none; }
[data-theme="light"] .em-theme-toggle .icon-sun { display: block; }
[data-theme="light"] .em-theme-toggle .icon-moon { display: none; }

/* Back to the head of the line — the corner every reader reaches for first */
.em-start-btn {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  background: transparent;
  color: var(--em-bone-dim);
  text-decoration: none;
  transition: color 0.2s ease, border-color 0.2s ease, background-color 0.2s ease, transform 0.2s ease;
}

.em-start-btn svg {
  width: 19px;
  height: 19px;
}

/* It does step backwards now, so it may slide the way it points. */
.em-start-btn:hover {
  color: var(--em-lens);
  border-color: var(--em-lens);
  background: rgb(var(--em-lens-rgb) / 0.08);
  transform: translateX(-1.5px);
}

.em-title-block {
  min-width: 0;
}

.em-home-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--em-mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--em-dim);
  text-decoration: none;
  padding: 2px 0;
  transition: color 0.2s ease;
}

.em-home-label svg {
  width: 13px;
  height: 13px;
  color: var(--em-lens-soft);
}

/* The only route to the wider site now that the corner button steps back
   inside the timeline instead. It is underlined on hover so it reads as
   somewhere to go rather than as a caption on the title beneath it. */
.em-home-label:hover {
  color: var(--em-lens);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

.em-title {
  font-family: var(--em-serif);
  font-size: clamp(15px, 4.9vw, 22px);
  font-weight: 400;
  line-height: 1.1;
  margin: 3px 0 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  letter-spacing: 0.005em;
}

.em-title em {
  font-style: normal;
  color: var(--em-lens);
}

@media (min-width: 900px) {
  .em-title {
    font-size: 30px;
  }
}

.em-top-actions {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Transient confirmation of what just changed, so the collapsed icon does not
   have to carry a permanent label. */
/* left+right with auto margins centers it while leaving the full viewport
   width available; anchoring at left:50% would cap it at half the screen. */
.em-toast {
  position: fixed;
  left: 1rem;
  right: 1rem;
  bottom: 30px;
  z-index: 80;
  width: fit-content;
  max-width: min(30rem, 100%);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 13px 22px;
  border-radius: 15px;
  text-align: center;
  background: rgb(var(--em-raised) / 0.97);
  border: 1px solid #4a3f28;
  box-shadow: 0 16px 44px rgb(var(--em-shadow-rgb) / var(--em-shadow-a));
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transform: translateY(14px);
  pointer-events: none;
  transition: opacity 0.28s ease, transform 0.28s ease;
}

.em-toast.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.em-toast-title {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--em-lens);
}

.em-toast-sub {
  font-family: var(--em-serif);
  font-size: 13px;
  line-height: 1.45;
  color: var(--em-bone-dim);
}

@media (max-width: 560px) {
  .em-toast {
    bottom: 22px;
    padding: 12px 18px;
  }

  .em-toast-sub {
    font-size: 12.5px;
  }
}

/* Order axis: segmented control where there is room, compact swap where not */
.em-order {
  display: inline-flex;
  padding: 3px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  background: rgb(var(--em-plate) / 0.25);
}

.em-order button {
  padding: 6px 13px;
  min-height: 32px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font-size: 12px;
  font-weight: 500;
  color: var(--em-dim);
  white-space: nowrap;
  transition: color 0.18s ease, background-color 0.18s ease;
}

.em-order button:hover {
  color: var(--em-bone-dim);
}

.em-order button[aria-pressed="true"] {
  background: rgb(var(--em-lens-rgb) / 0.14);
  color: var(--em-lens);
}

/* A 14% gold tint reads as a lit plate on black. On cream it is just tan, and
   gold-on-tan is 3.26:1 — so the selected segment fills properly instead. */
[data-theme="light"] .em-order button[aria-pressed="true"] {
  background: var(--em-lens);
  color: var(--em-on-lens);
}

.em-order-swap {
  display: none;
  align-items: center;
  gap: 7px;
  padding: 8px 13px 8px 10px;
  min-height: 38px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  background: transparent;
  font-size: 12px;
  font-weight: 500;
  color: var(--em-bone-dim);
  white-space: nowrap;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.em-order-swap svg {
  width: 14px;
  height: 14px;
  color: var(--em-lens-soft);
  flex-shrink: 0;
}

.em-order-swap:hover {
  color: var(--em-lens);
  border-color: var(--em-dim);
}

/* Below this width the segmented control and the help label cannot both fit */
@media (max-width: 780px) {
  .em-order {
    display: none;
  }

  .em-order-swap {
    display: inline-flex;
  }
}

/* Phone: trim the chrome so the title has room */
@media (max-width: 460px) {
  .em-top {
    gap: 8px;
    padding-inline: 12px;
  }

  .em-start-btn {
    width: 36px;
    height: 36px;
  }

  .em-top-left {
    gap: 9px;
  }
}

/* Smallest phones still in use (320px): tighten enough to keep the name */
@media (max-width: 360px) {
  .em-top {
    gap: 6px;
    padding-inline: 10px;
  }

  .em-top-left {
    gap: 7px;
  }

  .em-top-actions {
    gap: 6px;
  }
}

.em-help-btn {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  font-weight: 500;
  padding: 9px 16px 9px 11px;
  min-height: 40px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  background: transparent;
  color: var(--em-bone-dim);
  transition: color 0.2s ease, border-color 0.2s ease;
}

/* ---------- The way back to the site ----------
   Named, on the right, and set apart from the tool's own controls by a rule.
   Nothing else in this bar leaves the timeline, so it should not look like
   the things that do not. Desktop only; on narrow screens it is the last
   item in the menu. */
.em-home-btn {
  display: none;
  align-items: center;
  gap: 8px;
  margin-left: 4px;
  padding-left: 14px;
  border-left: 1px solid var(--em-rule);
  font-family: var(--em-mono);
  font-size: 9.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--em-dim);
  text-decoration: none;
  transition: color 0.2s ease;
}

.em-home-btn svg {
  width: 15px;
  height: 15px;
  color: var(--em-lens-soft);
}

.em-home-btn:hover {
  color: var(--em-lens);
}

@media (min-width: 900px) {
  .em-home-btn {
    display: inline-flex;
  }
}

/* ---------- The narrow-screen menu ---------- */

.em-menu-wrap {
  position: relative;
  flex-shrink: 0;
}

.em-menu-btn {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  background: transparent;
  color: var(--em-bone-dim);
  transition: color 0.2s ease, border-color 0.2s ease;
}

.em-menu-btn svg {
  width: 19px;
  height: 19px;
}

.em-menu-btn:hover,
.em-menu-btn[aria-expanded="true"] {
  color: var(--em-bone);
  border-color: var(--em-dim);
}

.em-menu {
  position: absolute;
  z-index: 70;
  top: calc(100% + 8px);
  right: 0;
  width: max-content;
  min-width: 216px;
  padding: 6px;
  border-radius: 14px;
  border: 1px solid var(--em-rule);
  background: rgb(var(--em-raised) / 0.98);
  box-shadow: 0 20px 56px rgb(var(--em-shadow-rgb) / var(--em-shadow-a));
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

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

.em-menu-item {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 9px 11px;
  min-height: 44px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--em-bone-dim);
  font-family: var(--em-sans);
  font-size: 13.5px;
  text-align: left;
  text-decoration: none;
  transition: background-color 0.12s ease, color 0.12s ease;
}

.em-menu-item svg {
  flex-shrink: 0;
  width: 17px;
  height: 17px;
  color: var(--em-dim);
}

.em-menu-item:hover {
  background: rgb(var(--em-tint) / 0.05);
  color: var(--em-bone);
}

.em-menu-rule {
  height: 1px;
  margin: 5px 11px;
  background: var(--em-rule);
}

/* Leaving the tool is not one of the tool's own actions. */
.em-menu-item.is-leave svg {
  color: var(--em-lens-soft);
}

@media (min-width: 900px) {
  .em-menu-wrap {
    display: none;
  }
}

/* Below 900px the whole right-hand cluster is the menu. Collapsing labels to
   keep five controls in the bar is what produced a row of marks nobody could
   name; one control that says "menu" is worth more than five that say
   nothing. */
@media (max-width: 899.98px) {
  .em-order,
  .em-order-swap,
  .em-theme-toggle,
  .em-help-btn {
    display: none;
  }
}

/* The door to the visualizer, dressed like the help button so the top bar
   reads as one family. Desktop only — on phones the lede card carries a
   labeled link instead, because an icon alone would go unrecognized. */
.em-viz-btn {
  display: none;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 500;
  padding: 9px 15px 9px 11px;
  min-height: 40px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  background: transparent;
  color: var(--em-bone-dim);
  text-decoration: none;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.em-viz-btn svg {
  width: 17px;
  height: 17px;
  color: var(--em-lens-soft);
}

.em-viz-btn:hover {
  color: var(--em-bone);
  border-color: var(--em-dim);
}

@media (min-width: 900px) {
  .em-viz-btn {
    display: inline-flex;
  }
}

.em-help-mark {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  border: 1px solid currentColor;
  font-family: var(--em-mono);
  font-size: 11px;
  opacity: 0.8;
}

.em-help-btn:hover {
  color: var(--em-lens);
  border-color: var(--em-dim);
}

/* Just past the two-column breakpoint the header runs out of room and the
   title is the thing that gives — "The Road to E…" on every laptop between
   900 and 1024. Six controls plus a title need about 1025px to sit at full
   width, so in the band below that the help button drops its label and keeps
   its mark. It is the one label that can go: a ? in a ring is not an icon
   anyone has to learn, the tooltip and the accessible name both stay, and
   the two doors that do need naming — Visualizer, OnMeaning — keep theirs. */
@media (min-width: 900px) and (max-width: 1060px) {
  .em-help-label {
    display: none;
  }

  .em-help-btn {
    width: 40px;
    padding: 0;
    gap: 0;
    justify-content: center;
  }
}

/* Narrow: both actions collapse to matching round icon buttons so the
   title is not squeezed. State is carried by the sort icon's tint. */
@media (max-width: 560px) {
  .em-help-label,
  .em-order-swap-label {
    display: none;
  }

  .em-help-btn,
  .em-order-swap {
    width: 36px;
    height: 36px;
    min-height: 36px;
    padding: 0;
    gap: 0;
    justify-content: center;
  }

  .em-help-mark {
    width: 20px;
    height: 20px;
  }

  .em-order-swap svg {
    width: 16px;
    height: 16px;
  }

  /* Canonical is the non-default axis — show that something is switched */
  .em-order-swap.is-alt {
    border-color: var(--em-lens);
    background: rgb(var(--em-lens-rgb) / 0.12);
  }

  .em-order-swap.is-alt svg {
    color: var(--em-lens);
  }
}

/* ---------- Thread bar + ribbon ---------- */

.em-chipbar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 4px;
  border-bottom: 1px solid var(--em-rule);
}

.em-chipbar-label {
  flex-shrink: 0;
  padding: 0 4px 0 16px;
  font-family: var(--em-mono);
  font-size: 8.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--em-faint);
}

@media (min-width: 900px) {
  .em-chipbar-label {
    padding-left: 24px;
  }
}

/* Chips never scroll now. fitChips() hides the ones that will not fit and
   moves them into the More menu, so overflow here is only a safety net. */
.em-chips {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 8px 10px 8px;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
}

/* ---------- More menu ----------
   The chip row is measured against the space it actually has, and the
   remainder collapses into this. The active chip is always kept in the row,
   so the menu never hides the thread you are reading. */

.em-more {
  position: relative;
  flex-shrink: 0;
  padding-right: 16px;
}

.em-chip-more {
  gap: 5px;
}

.em-chip-more[aria-expanded="true"] {
  color: var(--em-bone);
  border-color: var(--em-bone-dim, var(--em-dim));
}

.em-more-count::before {
  content: " ";
}

.em-more-count {
  font-family: var(--em-mono);
  font-size: 11px;
  color: var(--em-faint);
}

.em-more-chevron {
  width: 13px;
  height: 13px;
  transition: transform 0.18s ease;
}

.em-chip-more[aria-expanded="true"] .em-more-chevron {
  transform: rotate(180deg);
}

.em-more-menu {
  position: absolute;
  top: calc(100% - 4px);
  right: 16px;
  z-index: 40;
  width: 17rem;
  max-height: min(60vh, 26rem);
  overflow-y: auto;
  padding: 6px;
  border: 1px solid var(--em-rule);
  border-radius: 14px;
  background: var(--em-ground-2);
  box-shadow: 0 18px 50px rgb(var(--em-shadow-rgb) / var(--em-shadow-a));
  animation: em-more-in 0.14s ease-out both;
}

@keyframes em-more-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

.em-more-menu button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--em-dim);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  text-align: left;
}

.em-more-menu button:hover,
.em-more-menu button:focus-visible {
  background: rgb(var(--em-tint) / 0.06);
  color: var(--em-bone);
}

.em-more-menu button[aria-pressed="true"] {
  color: var(--em-bone);
}

.em-more-dot {
  flex-shrink: 0;
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: var(--row-color);
}

.em-more-sub {
  display: block;
  margin-top: 1px;
  font-size: 11.5px;
  font-weight: 400;
  color: var(--em-faint);
}

/* ---------- Thread picker ----------
   The chip row did not survive the thread count growing: six chips needed
   ~700px, eight ~860px, and fifteen need ~1630px — past any laptop. Raising
   the breakpoint each time only delayed the problem, so the chip row is now
   retired at every width and the picker is the only control.

   It names the current thread and opens a sheet listing every thread at once,
   which costs one tap and scales to any number of them. The chips are left in
   the markup but never shown; nothing reads them. */

/* A <button> shrink-wraps even as a flex container, so it needs an explicit
   width — and the horizontal inset lives on .em-chipbar rather than here,
   since width:100% plus side margins would push the right edge off screen. */
.em-picker {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 12px;
  margin: 9px 0 11px;
  padding: 12px 14px;
  border-radius: 13px;
  border: 1px solid #56492e;
  background: rgb(var(--em-tint) / 0.05);
  box-shadow: inset 0 1px 0 rgb(var(--em-tint) / 0.045), 0 2px 10px rgb(var(--em-shadow-rgb) / var(--em-shadow-a2));
  text-align: left;
  color: inherit;
  transition: border-color 0.18s ease, background-color 0.18s ease;
}

.em-picker:hover {
  border-color: var(--em-bone-dim);
  background: rgb(var(--em-tint) / 0.08);
}

.em-picker-dot {
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 999px;
  border: 1.5px dashed var(--em-lens-soft);
  transition: background-color 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}

.em-picker.is-active .em-picker-dot {
  border: 0;
  background: var(--picker-color);
  box-shadow: 0 0 9px color-mix(in srgb, var(--picker-color) 75%, transparent);
}

/* With a thread on, the control wears its colour at every width. Mixed toward
   transparent rather than into a ground colour, because on a phone this sits
   over the header rather than over the list and has no fixed backdrop to mix
   with. The docked desktop version overrides this with an opaque mix — see
   .em-listcol > .em-picker.is-active. */
.em-picker.is-active {
  background: color-mix(in srgb, var(--picker-color) 18%, transparent);
  border-color: color-mix(in srgb, var(--picker-color) 45%, #56492e);
}

.em-picker.is-active:hover {
  background: color-mix(in srgb, var(--picker-color) 26%, transparent);
}

.em-picker-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.em-picker-title {
  font-size: 14.5px;
  font-weight: 600;
  color: var(--em-bone);
  line-height: 1.2;
}

/* Plain ink, not the thread's colour. The dot and the fill both carry the
   colour now, and putting it on the title as well set the thread's colour on
   a tint of itself — which took The Cross to 4.52 against a 4.5 threshold in
   the light theme. One element carrying it reads more clearly in any case. */
.em-picker.is-active .em-picker-title {
  color: var(--em-bone);
}

.em-picker-sub {
  font-family: var(--em-serif);
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--em-bone-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Names the interaction outright, so the control cannot be mistaken for a label */
.em-picker-action {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 5px 9px 5px 10px;
  border-radius: 999px;
  background: rgb(var(--em-lens-rgb) / 0.14);
  font-family: var(--em-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--em-lens);
}

.em-picker-chevron {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--em-lens);
}

@media (max-width: 899.98px) {
  /* Room at the foot of the list for the last book to clear the home
     indicator on a phone. */
  .em-list {
    padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  }
}

/* Below the chip breakpoint the whole row collapses into the picker — one
   control, one sheet. Kept as a single menu on phones deliberately: a
   measured row of chips is not worth the horizontal space there.
   The JS mirrors this breakpoint in `mqChips`; change both together. */
/* One control at every width: the picker.

   The chip row read as a filter bar rather than as navigation, which is the
   opposite of what it does — clicking a thread opens its whole teaching page.
   And it never delivered the one thing a chip row is for. It showed eight of
   fifteen threads at 1060px and thirteen at 1680px, with the remainder behind
   a More menu that was itself a second, worse menu. The picker shows all
   fifteen with their subtitles and passage counts, which is a table of
   contents rather than a set of switches.

   To restore the chips: put this block back under `max-width: 767.98px`, add
   back `@media (min-width: 768px) { .em-picker { display: none } }`, and
   restore the matching breakpoint in `mqChips`. The fitChips() machinery and
   the More menu are untouched underneath. */

/* Inset matches .em-top so the controls line up with the header */
.em-chipbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-inline: 16px;
}

.em-chipbar-label,
.em-chips,
.em-more {
  display: none !important;
}

.em-picker {
  display: flex;
  flex: 1;
  min-width: 0;
  margin-block: 9px 11px;
}

/* The hint band is no longer rendered at any width — renderRibbon() skips it
   when no thread is on — so the rule that used to hide it on phones is gone
   with it. The picker's own subtitle says what that line used to say. */

/* Above 900px the control sits over the book list it drives, so it is capped
   to that column's width rather than stretching the full window — a control
   running the whole width of a wide screen reads as a banner, not a menu. */
@media (min-width: 900px) {
  .em-chipbar {
    padding-inline: 24px;
  }

  .em-picker {
    flex: 0 1 436px;
  }
}

/* Keep the picker's inset in step with the header's at every width. These
   must follow the rule above to win the cascade. */
@media (max-width: 460px) {
  .em-chipbar {
    padding-inline: 12px;
  }
}

@media (max-width: 360px) {
  .em-chipbar {
    padding-inline: 10px;
  }
}

/* ---------- Thread sheet ---------- */

.em-sheet {
  margin: auto auto 0;
  width: 100%;
  max-width: 34rem;
  max-height: 88dvh;
  padding: 0;
  border: 1px solid var(--em-rule);
  border-bottom: 0;
  border-radius: 20px 20px 0 0;
  background: var(--em-ground-2);
  color: var(--em-bone);
  font-family: var(--em-sans);
  box-shadow: 0 -12px 60px rgb(var(--em-shadow-rgb) / var(--em-shadow-a));
  animation: em-sheet-up 0.26s cubic-bezier(0.2, 0.7, 0.3, 1) both;
}

.em-sheet::backdrop {
  background: rgb(var(--em-scrim) / 0.72);
  backdrop-filter: blur(3px);
}

/* ---------- the same list as a dropdown ----------
   On desktop the picker sits at the head of the column and the list drops from
   it, so this drops the bottom-sheet shape: pinned by JS to the control's rect
   (see placeSheet), rounded on all four corners, and no backdrop, because a
   modal over the whole window is more ceremony than choosing a thread wants.
   The head stays — hidden it would strip the dialog's accessible name, which
   comes from the title inside it — but loses the close button, since Escape,
   an outside click and the control itself all dismiss it. */
.em-sheet.is-dropdown {
  position: fixed;
  /* A modal dialog sits in the top layer, above everything, for free. A
     non-modal one does not — it stacks like any other element at `z-index:
     auto`, so the list's sticky era headers painted straight through it.
     90 clears every layer in the app, the highest of which is the menu at 80. */
  z-index: 90;
  margin: 0;
  max-width: none;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--em-rule);
  border-radius: 14px;
  box-shadow: 0 20px 56px rgb(var(--em-shadow-rgb) / var(--em-shadow-a));
  animation: em-sheet-drop 0.16s cubic-bezier(0.2, 0.8, 0.3, 1) both;
}

.em-sheet.is-dropdown .em-sheet-head {
  padding: 12px 16px 2px;
  border-bottom: 0;
}

.em-sheet.is-dropdown .em-sheet-head h2 {
  font-family: var(--em-mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 400;
  color: var(--em-faint);
}

.em-sheet.is-dropdown .em-sheet-close,
.em-sheet.is-dropdown .em-sheet-intro {
  display: none;
}

@keyframes em-sheet-drop {
  from { transform: translateY(-6px); opacity: 0; }
  to { transform: none; opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .em-sheet.is-dropdown {
    animation: none;
  }
}

@keyframes em-sheet-up {
  from { transform: translateY(18px); opacity: 0.4; }
  to { transform: translateY(0); opacity: 1; }
}

@media (min-width: 1024px) {
  .em-sheet {
    margin: auto;
    border-radius: 20px;
    border-bottom: 1px solid var(--em-rule);
  }
}

.em-sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 20px 12px;
}

.em-sheet-head h2 {
  font-family: var(--em-serif);
  font-size: 21px;
  font-weight: 400;
  margin: 0;
}

.em-sheet-close {
  flex: none;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  background: transparent;
  color: var(--em-dim);
  font-size: 14px;
}

.em-sheet-close:hover {
  color: var(--em-bone);
  border-color: var(--em-dim);
}

.em-sheet-intro {
  margin: 0;
  padding: 0 20px 14px;
  font-family: var(--em-serif);
  font-size: 14px;
  line-height: 1.5;
  color: var(--em-bone-dim);
}

.em-sheet-list {
  list-style: none;
  margin: 0;
  padding: 0 12px 20px;
  overflow-y: auto;
  max-height: calc(88dvh - 130px);
}

.em-sheet-list button {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  padding: 13px 12px;
  border: 0;
  border-radius: 12px;
  background: transparent;
  text-align: left;
  color: inherit;
  transition: background-color 0.15s ease;
}

.em-sheet-list button:hover {
  background: rgb(var(--em-tint) / 0.05);
}

.em-sheet-list button[aria-pressed="true"] {
  background: color-mix(in srgb, var(--row-color) 13%, transparent);
}

.em-sheet-dot {
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 999px;
  background: var(--row-color);
  opacity: 0.55;
}

.em-sheet-list button[aria-pressed="true"] .em-sheet-dot {
  opacity: 1;
  box-shadow: 0 0 10px color-mix(in srgb, var(--row-color) 80%, transparent);
}

.em-sheet-copy {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.em-sheet-name {
  font-size: 15.5px;
  font-weight: 600;
  color: var(--em-bone);
}

.em-sheet-list button[aria-pressed="true"] .em-sheet-name {
  color: var(--row-color);
}

.em-sheet-sub {
  font-family: var(--em-serif);
  font-size: 13px;
  line-height: 1.4;
  color: var(--em-bone-dim);
}

.em-sheet-meta {
  flex: none;
  font-family: var(--em-mono);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  color: var(--em-faint);
  white-space: nowrap;
}

/* On the row that is already on, the passage count is the least useful thing
   it could say — the row is a toggle, and this is where that gets said. */
.em-sheet-meta.is-off-hint {
  color: var(--row-color);
}

.em-sheet-clear {
  margin-top: 6px;
  padding-top: 12px;
  border-top: 1px solid var(--em-rule);
}

.em-sheet-clear button {
  color: var(--em-dim);
  font-size: 14px;
  justify-content: center;
}

.em-sheet-clear button:hover {
  color: var(--em-bone-dim);
}

.em-chip {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 500;
  padding: 8px 14px;
  min-height: 38px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  background: transparent;
  color: var(--em-dim);
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
}

.em-chip:hover {
  border-color: var(--em-dim);
  color: var(--em-bone-dim);
}

/* Each chip's dot always wears its thread color — dimmed until active —
   so the colors on the timeline are learnable at a glance. */
.em-chip .em-chip-dot {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--chip-color);
  opacity: 0.5;
  transition: opacity 0.15s ease, box-shadow 0.15s ease;
}

.em-chip[aria-pressed="true"] {
  border-color: var(--chip-color);
  background: color-mix(in srgb, var(--chip-color) 11%, transparent);
  color: var(--chip-color);
  box-shadow: 0 0 16px color-mix(in srgb, var(--chip-color) 14%, transparent);
}

.em-chip[aria-pressed="true"] .em-chip-dot {
  opacity: 1;
  box-shadow: 0 0 7px color-mix(in srgb, var(--chip-color) 80%, transparent);
}

/* ---------- Body: list + detail ---------- */

.em-body {
  flex: 1;
  min-height: 0;
  display: flex;
}

/* Offset from the reading panel by one step, so the index and the text you
   are reading register as two surfaces rather than one continuous sheet.
   Deliberately slight — a shade, not a border. */
.em-list {
  height: 100%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  flex: 1;
  min-width: 0;
  scroll-padding-top: 44px;
  background: var(--em-list-bg);
}

/* ---------- scrollbars ----------
   Every scrolling surface in the timeline, in one place. Three of these had
   the treatment and two (the More menu and the mobile thread sheet) were
   still showing the platform default, which is a grey slab that belongs to
   no part of this design.

   Declared twice on purpose. `scrollbar-color` is the standard property and
   is what Firefox and current Chrome use; the ::-webkit- rules cover older
   WebKit, which ignores it. Chrome prefers the standard one where both are
   present, so the two paths are given the same colours and land in the same
   place either way. */
.em-list,
.em-detail-scroll,
.em-dialog-body,
.em-more-menu,
.em-sheet-list {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--em-dim) 45%, transparent) transparent;
}

.em-list::-webkit-scrollbar,
.em-detail-scroll::-webkit-scrollbar,
.em-dialog-body::-webkit-scrollbar,
.em-more-menu::-webkit-scrollbar,
.em-sheet-list::-webkit-scrollbar {
  width: 10px;
}

.em-list::-webkit-scrollbar-track,
.em-detail-scroll::-webkit-scrollbar-track,
.em-dialog-body::-webkit-scrollbar-track,
.em-more-menu::-webkit-scrollbar-track,
.em-sheet-list::-webkit-scrollbar-track {
  background: transparent;
}

/* The transparent border plus background-clip is what insets the thumb from
   the edge — it reads as a floating pill rather than a bar wedged against
   the panel wall. */
.em-list::-webkit-scrollbar-thumb,
.em-detail-scroll::-webkit-scrollbar-thumb,
.em-dialog-body::-webkit-scrollbar-thumb,
.em-more-menu::-webkit-scrollbar-thumb,
.em-sheet-list::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--em-dim) 45%, transparent);
  background-clip: padding-box;
  border: 3px solid transparent;
  border-radius: 99px;
}

.em-list::-webkit-scrollbar-thumb:hover,
.em-detail-scroll::-webkit-scrollbar-thumb:hover,
.em-dialog-body::-webkit-scrollbar-thumb:hover,
.em-more-menu::-webkit-scrollbar-thumb:hover,
.em-sheet-list::-webkit-scrollbar-thumb:hover {
  background: var(--em-dim);
  background-clip: padding-box;
}

.em-detail {
  display: none;
  flex-direction: column;
  height: 100%;
}

/* The panel is a bar plus a scroller rather than one scrolling box, so the way
   back can stay put while the reading moves under it. */
.em-detail-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.em-detail-bar {
  display: none;
}

@media (min-width: 900px) {
  /* The column is the flex item now, not the list: it carries the width, the
     divider and the slide, so the picker docked at its top travels with it
     when reader mode closes the drawer. */
  .em-listcol {
    flex: none;
    width: 460px;
    display: flex;
    flex-direction: column;
    min-height: 0;
    border-right: 1px solid var(--em-rule);
    transition: margin-left .34s cubic-bezier(.2, .8, .3, 1);
  }

  .em-list {
    flex: 1;
    min-height: 0;
  }

  .em-detail {
    display: flex;
    flex: 1;
    min-width: 0;
  }

  /* Keeps the list clipped once it has travelled past the left edge, rather
     than letting it push a horizontal scrollbar onto the page. */
  .em-body {
    overflow: hidden;
  }

  /* Reader mode. The list slides out on a negative margin rather than
     animating its width to zero. Width animation was the source of the
     jitter: the box got narrower every frame, so all sixty-six rows re-wrapped
     the whole way out. Holding the width at 460px and moving the box means the
     rows never reflow — they simply travel. The negative margin still
     collapses the flex item's outer size to nothing, so the panel takes the
     freed space exactly as before.

     `visibility` waits out the slide so the rows stay drawn while they are
     moving, and then drops them out of the tab order once they are gone. */
  :root[data-em-list="collapsed"] .em-listcol {
    margin-left: -460px;
    border-right-color: transparent;
    visibility: hidden;
    transition: margin-left .34s cubic-bezier(.2, .8, .3, 1), visibility 0s .34s;
  }

  /* Docked at the head of the column rather than floating in a bar of its own.
     It is the tool's primary control, so it gets the weight: full column width,
     its own ground, and a divider separating it from the books it drives. */
  /* Given its own ground rather than the list's, so it reads as the control
     over the index and not the first row of it, and tinted with the gold the
     tool already uses for things you act on.

     The tint is mixed into the list's own colour rather than into --em-raised.
     That token is near-white in the light theme, so gold mixed into it landed
     on (246,242,231) against a list of (245,241,231) — a one-unit difference,
     which is no difference at all. Tinting the list colour moves away from it
     in both themes: lighter on dark, warmer and deeper on light. */
  .em-listcol > .em-picker {
    flex: none;
    margin: 0;
    padding: 14px 24px;
    border: 0;
    border-bottom: 1px solid var(--em-rule);
    border-radius: 0;
    background: color-mix(in srgb, var(--em-lens) 14%, var(--em-list-bg));
  }

  .em-listcol > .em-picker:hover {
    background: color-mix(in srgb, var(--em-lens) 22%, var(--em-list-bg));
  }

  /* With a thread on, the control wears that thread's colour. --picker-color
     is set by renderPicker() and is already theme-corrected, so this follows
     light and dark without a second rule. Mixed into the list colour at a
     strength low enough that the title and subtitle keep their contrast
     against every one of the fifteen. */
  .em-listcol > .em-picker.is-active {
    background: color-mix(in srgb, var(--picker-color) 15%, var(--em-list-bg));
    border-bottom-color: color-mix(in srgb, var(--picker-color) 40%, var(--em-rule));
  }

  .em-listcol > .em-picker.is-active:hover {
    background: color-mix(in srgb, var(--picker-color) 24%, var(--em-list-bg));
  }

  /* With the picker moved out of it, the bar is empty and only takes space. */
  .em-chipbar {
    display: none;
  }

  /* The reading column is capped and centred in BOTH states, not just the
     collapsed one. Applying the cap on toggle was the whole problem: the
     measure jumped the moment you pressed the tab, so the text re-wrapped and
     then slid — two motions, which is what killed the drawer. Held constant,
     nothing re-wraps and the column simply travels.

     37.5rem is the width the two-column layout already produces, so the
     expanded view is unchanged, and the prose stays inside the 45–75 character
     band. Widening the lines for "reader" mode would have made them harder to
     read, not easier.

     Two things this value has to account for. It is set in rem rather than ch
     because these children are section wrappers whose font differs from the
     prose inside them — a ch value resolved against the wrong face and came
     out far too wide, which put the re-wrap straight back. And it caps the
     wrapper, which carries ~86px of its own horizontal padding, so it runs
     that much wider than the ~514px column it is sizing. */
  .em-detail-scroll > * {
    max-width: 37.5rem;
    margin-inline: auto;
  }
}

/* The reader tab. It rides the seam between the list and the panel and slides
   with it, so it reads as a handle on the timeline rather than a page-level
   control. Hidden entirely below 900px, where there is no seam. */
.em-reader-tab {
  display: none;
}

@media (min-width: 900px) {
  .em-reader-tab {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 50%;
    left: 460px;
    z-index: 5;
    width: 19px;
    height: 66px;
    padding: 0;
    transform: translateY(-50%);
    border: 1px solid var(--em-rule);
    border-left: 0;
    border-radius: 0 7px 7px 0;
    background: var(--em-list-bg);
    color: var(--em-bone-dim);
    cursor: pointer;
    transition:
      left .34s cubic-bezier(.2, .8, .3, 1),
      background-color .18s,
      color .18s;
  }

  .em-reader-tab:hover,
  .em-reader-tab:focus-visible {
    background: var(--em-raised, var(--em-list-bg));
    color: var(--em-bone);
  }

  .em-reader-tab svg {
    width: 15px;
    height: 15px;
    transition: transform .34s cubic-bezier(.2, .8, .3, 1);
  }

  /* Pressed = list hidden, so the caret turns to point the way back. */
  .em-reader-tab[aria-pressed="true"] svg {
    transform: scaleX(-1);
  }

  :root[data-em-list="collapsed"] .em-reader-tab {
    left: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .em-reader-tab,
  .em-reader-tab svg,
  .em-list {
    transition: none;
  }
}

/* Below 900px the two panels are a navigation stack, not two columns. The list
   is the page — a phone visitor should land on the sixty-six books, because
   that is the thing the page is for and nothing else announces it. Opening a
   book or a thread pushes this over the top, and it carries its own way back.
   The list keeps its scroll position underneath the whole time. */
@media (max-width: 899.98px) {
  /* The wrapper is transparent to layout here — the list is still the page,
     and the picker stays up in the header where the pushed reading panel
     cannot cover it. */
  .em-listcol {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  .em-list {
    flex: 1;
  }

  .em-detail {
    display: flex;
    position: absolute;
    inset: 0;
    z-index: 30;
    background: var(--em-ground);
    /* The browser keeps vertical panning; horizontal is left for the
       swipe-to-close handler, which therefore needs no preventDefault. */
    touch-action: pan-y;
    box-shadow: -12px 0 40px rgb(var(--em-shadow-rgb) / var(--em-shadow-a));
    transform: translateX(101%);
    /* visibility keeps the parked panel out of the tab order. It flips on
       immediately when opening — a focus() call cannot land on a hidden
       element — and only after the slide when closing. */
    visibility: hidden;
    transition: transform 0.28s cubic-bezier(0.2, 0.7, 0.3, 1), visibility 0s 0.28s;
  }

  .em-app.detail-pushed .em-detail {
    transform: none;
    visibility: visible;
    transition: transform 0.28s cubic-bezier(0.2, 0.7, 0.3, 1), visibility 0s;
  }

  .em-detail-bar {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    padding: 8px 12px;
    border-bottom: 1px solid var(--em-rule);
    background: var(--em-ground-2);
  }
}

.em-detail-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px 8px 6px;
  min-height: 40px;
  border: 0;
  border-radius: 10px;
  background: none;
  color: var(--em-bone-dim);
  font-family: var(--em-sans);
  font-size: 14px;
  cursor: pointer;
}

.em-detail-back svg {
  width: 18px;
  height: 18px;
}

.em-detail-back:hover {
  color: var(--em-bone);
}

/* The pushed panel is positioned against this, and it clips the slide. */
.em-body {
  position: relative;
}

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

/* ---------- List lede (the narrow-screen landing card; scrolls away) ----------
   Below 900px this is the first thing on the page, so it does the job the
   welcome panel does on a wide one. Above 900px the panel is on screen beside
   the list and this would only repeat it. */

.em-lede {
  padding: 18px 16px 14px;
  margin-bottom: 4px;
  max-width: 34em;
  border-bottom: 1px solid var(--em-rule);
}

.em-lede-kicker {
  font-family: var(--em-mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--em-lens-soft);
  margin: 0 0 10px;
}

/* With a thread on, the lede belongs to that thread rather than to the page,
   so its kicker takes the thread's colour — the same signal the picker above
   it and the card below it are already carrying. */
.em-lede.is-thread .em-lede-kicker {
  color: var(--thread-color);
}

.em-lede-title {
  font-family: var(--em-serif);
  font-size: 19px;
  font-weight: 400;
  line-height: 1.45;
  color: var(--em-bone);
  margin: 0 0 8px;
  text-wrap: pretty;
}

.em-lede-text {
  font-size: 13.5px;
  line-height: 1.62;
  color: var(--em-dim);
  margin: 0 0 14px;
}

/* The list below is its own invitation, so the one door worth spelling out is
   the one that is not on screen: the threads. */
.em-lede-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 16px;
}

.em-lede-actions .em-btn-solid {
  min-height: 44px;
}

@media (min-width: 900px) {
  .em-lede {
    display: none;
  }
}

/* ---------- The visualizer invitation ----------
   A miniature of the visualizer, drawn from the same verse counts and the
   same passages as the real thing, standing in for a link that could only
   have described it. The thread it draws follows whichever one is on. */

.em-vizcard {
  display: block;
  margin: 14px 0 4px;
  padding-bottom: 12px;
  border: 1px solid color-mix(in srgb, var(--vc) 32%, var(--em-rule));
  border-radius: 14px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--vc) 10%, transparent) 0%,
    transparent 72%);
  text-decoration: none;
  overflow: hidden;
  transition: border-color 0.2s ease;
}

.em-vizcard:hover {
  border-color: color-mix(in srgb, var(--vc) 58%, transparent);
}

.em-vizcard-art {
  display: block;
  padding: 11px 12px 4px;
}

.em-vizmini {
  display: block;
  width: 100%;
  height: 50px;
}

/* Present enough that "drawn to scale" is visibly true, quiet enough that the
   constellation over them stays the subject. */
.em-vizmini-bars rect {
  fill: rgb(var(--em-tint) / 0.22);
}

.em-vizmini-line {
  fill: none;
  stroke: var(--vc);
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
  /* pathLength normalises the path to 1, so one keyframe draws any thread. */
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: em-vizdraw 1.5s ease 0.35s forwards;
}

.em-vizmini-dots circle {
  fill: var(--vc);
  opacity: 0;
  animation: em-vizdot 0.5s ease 1.5s forwards;
}

.em-vizmini-dots circle.is-f {
  fill: var(--em-lens);
}

@keyframes em-vizdraw {
  to { stroke-dashoffset: 0; }
}

@keyframes em-vizdot {
  to { opacity: 1; }
}

/* Nothing here is information — it is an invitation — so with motion turned
   down it simply arrives drawn. */
@media (prefers-reduced-motion: reduce) {
  .em-vizmini-line {
    stroke-dashoffset: 0;
    animation: none;
  }
  .em-vizmini-dots circle {
    opacity: 1;
    animation: none;
  }
}

.em-vizcard-copy {
  display: block;
  padding: 0 14px;
}

.em-vizcard-title {
  display: block;
  font-family: var(--em-serif);
  font-size: 16.5px;
  color: var(--em-bone);
}

.em-vizcard-text {
  display: block;
  margin-top: 4px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--em-dim);
}

.em-vizcard-cta {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 10px;
  font-size: 13px;
  font-weight: 500;
  color: var(--vc);
}

.em-vizcard-cta svg {
  width: 13px;
  height: 13px;
}

/* ---------- The thread card, and the visualizer under it ----------
   Once a thread is on, its own page is what the tool is pointing at, so it
   takes the card and the visualizer takes a line beneath. It is a <button>
   rather than a link — it opens a view rather than a URL — so it needs the
   resets an <a> did not. */
.em-threadcard {
  width: 100%;
  margin: 4px 0 8px;
  color: inherit;
  font-family: var(--em-sans);
  text-align: left;
  cursor: pointer;
}

.em-vizlink {
  display: flex;
  align-items: center;
  gap: 11px;
  margin: 0 0 10px;
  padding: 11px 14px;
  border: 1px solid var(--em-rule);
  border-radius: 12px;
  background: transparent;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

.em-vizlink:hover {
  border-color: color-mix(in srgb, var(--vc) 50%, var(--em-rule));
  background: color-mix(in srgb, var(--vc) 7%, transparent);
}

.em-vizlink-mark {
  flex: none;
  width: 19px;
  height: 19px;
  color: var(--vc);
}

.em-vizlink-copy {
  flex: 1;
  min-width: 0;
}

.em-vizlink-title {
  display: block;
  font-size: 14px;
  font-weight: 500;
  color: var(--em-bone);
}

.em-vizlink-sub {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  color: var(--em-dim);
}

.em-vizlink > svg:last-child {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--em-faint);
}

/* Both belong to the narrow layout only. On a wide screen the thread's page is
   already open in the panel beside the list, and the visualizer has its own
   labelled door in the header. */
@media (min-width: 900px) {
  .em-threadcard,
  .em-vizlink {
    display: none;
  }
}

/* In the welcome panel it has the full column width to work with. */
.em-empty .em-vizcard {
  margin-top: 26px;
  max-width: 430px;
}

.em-empty .em-vizmini {
  height: 72px;
}


/* ---------- Era headers ---------- */

/* Era headers sit ON the line: lanes and spine run straight through them,
   and a diamond tick marks the era boundary like a milestone. */
/* An era is a milestone on the line, so it is allowed to announce itself:
   a hairline above it closes off the era just ended, and the label sits in
   its own band of tint. Without this the eras read as one more row among the
   books rather than as the divisions of the timeline. */
/* Six devices were marking each era change — a gap, a rule above it, a tinted
   gradient fill, a scrim below, a hairline under the label, and the milestone
   on the spine. Any two of them do the work; all six together chopped a
   continuous line of sixty-six books into a stack of heavy blocks.

   What is left is space, one hairline, and the mark on the spine. */
.em-era {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: stretch;
  margin-top: 30px;
  /* Flat and opaque, because rows scroll underneath. The gradient this
     replaces faded out at its own bottom edge, which is why it needed a scrim
     to hide the seam; a solid band cuts cleanly on the hairline instead. */
  background: var(--em-list-bg);
}

/* The first era opens the list; there is nothing above it to separate from. */
.em-list > section:first-of-type .em-era {
  margin-top: 0;
}

.em-era-spine {
  position: relative;
  flex-shrink: 0;
  width: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The milestone itself, sitting on the spine — larger and in gold now, so an
   era boundary is visible while running down the line at speed. */
.em-era-tick {
  position: relative;
  display: block;
  width: 8px;
  height: 8px;
  background: var(--em-lens-soft);
  transform: rotate(45deg);
  outline: 3px solid var(--em-list-bg);
}

.em-era.is-silence .em-era-tick {
  background: var(--em-bone-dim);
}

.em-era-text {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 14px 7px 0;
  border-bottom: 1px solid var(--em-rule-soft);
}

.em-era-label {
  font-family: var(--em-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--em-bone-dim);
}

.em-era-years {
  font-family: var(--em-mono);
  font-size: 9px;
  letter-spacing: 0.06em;
  color: var(--em-faint);
  white-space: nowrap;
}

.em-era.is-silence .em-era-label {
  color: var(--em-bone-dim);
}

/* ---------- Book rows ---------- */

.em-row {
  display: flex;
  align-items: stretch;
  width: 100%;
  text-align: left;
  border: 0;
  padding: 0;
  background: transparent;
  color: inherit;
  transition: background-color 0.15s ease;
}

.em-row:hover {
  background: rgb(var(--em-tint) / 0.035);
}

/* Books the active thread touches. The halo alone did not carry across a long
   list, so the row itself takes the thread's colour: a wash that fades to the
   right, and a bar on the leading edge to square up the column. */
.em-row.is-onthread {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--thread-color) 17%, transparent),
    color-mix(in srgb, var(--thread-color) 4%, transparent) 62%,
    transparent);
  box-shadow: inset 2px 0 0 var(--thread-color);
}

.em-row.is-onthread:hover {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--thread-color) 26%, transparent),
    color-mix(in srgb, var(--thread-color) 7%, transparent) 62%,
    transparent);
}

/* Where the promise arrives, gold joins the thread rather than replacing it.
   Gold used to take the whole row — bar, wash and name — which broke the
   column of colour exactly where it mattered most: the books at the end of
   the line stopped looking like they belonged to the thread that reached
   them. So the leading edge and the left of the wash stay the thread's, and
   gold blooms in from the right, meeting it under the name and the ✧. Two
   layers, thread underneath. */
.em-row.is-arrival {
  background:
    linear-gradient(270deg,
      rgb(var(--em-lens-rgb) / 0.26),
      rgb(var(--em-lens-rgb) / 0.07) 44%,
      transparent 76%),
    linear-gradient(90deg,
      color-mix(in srgb, var(--thread-color) 22%, transparent),
      color-mix(in srgb, var(--thread-color) 6%, transparent) 62%,
      transparent);
  box-shadow: inset 2px 0 0 var(--thread-color);
}

.em-row.is-arrival:hover {
  background:
    linear-gradient(270deg,
      rgb(var(--em-lens-rgb) / 0.34),
      rgb(var(--em-lens-rgb) / 0.1) 44%,
      transparent 76%),
    linear-gradient(90deg,
      color-mix(in srgb, var(--thread-color) 30%, transparent),
      color-mix(in srgb, var(--thread-color) 9%, transparent) 62%,
      transparent);
}

/* The selection reads over either of them. */
.em-row.is-sel,
.em-row.is-sel.is-onthread,
.em-row.is-sel.is-arrival {
  background: linear-gradient(90deg, rgb(var(--em-lens-rgb) / 0.07), rgb(var(--em-tint) / 0.03));
}

/* Thread lanes (decorative; membership is conveyed by inline text).
   Row internals are <span>s (buttons allow only phrasing content),
   so display is set explicitly throughout. */
.em-lanes {
  display: block;
  position: relative;
  flex-shrink: 0;
  align-self: stretch;
}

.em-lane {
  display: block;
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  opacity: 0.75;
}

.em-lane-dot {
  display: block;
  position: absolute;
  top: 50%;
  margin-top: -3.5px;
  width: 7px;
  height: 7px;
  border-radius: 999px;
}

/* Where a promise lands in Christ — ringed so the arrival reads at a glance */
.em-lane-dot.is-fulfillment {
  box-shadow: 0 0 0 2px var(--em-list-bg), 0 0 0 3.5px var(--em-lens), 0 0 9px rgb(var(--em-lens-rgb) / 0.7);
}

/* Spine */
.em-spine {
  position: relative;
  flex-shrink: 0;
  width: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.em-spine-rule {
  display: block;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 19.5px;
  width: 1px;
  background: var(--em-rule);
}

.em-halo {
  display: block;
  position: absolute;
  border-radius: 999px;
  pointer-events: none;
  animation: em-bloom 0.55s ease-out both;
}


/* The stop on the line. Sized to sit inside the spine's width with a ring of
   the list's own background around it, so the rule appears to pass behind it
   rather than through it. */
.em-node {
  display: block;
  position: relative;
  width: 7px;
  height: 7px;
  border-radius: 99px;
  background: var(--em-dim);
  box-shadow: 0 0 0 4px var(--em-list-bg);
  transition: background-color 0.2s ease, box-shadow 0.2s ease;
}

.em-row.is-lit .em-node {
  background: var(--em-bone-dim);
}

/* On a thread the node takes its colour; where the promise lands it takes
   gold and a ring, the same language the lanes and the row wash use. */
.em-row.is-onthread .em-node {
  background: var(--thread-color);
  box-shadow: 0 0 0 4px var(--em-list-bg), 0 0 10px color-mix(in srgb, var(--thread-color) 70%, transparent);
}

.em-row.is-arrival .em-node {
  width: 9px;
  height: 9px;
  background: var(--thread-color);
  box-shadow:
    0 0 0 2px var(--em-list-bg),
    0 0 0 3.5px var(--em-lens),
    0 0 12px rgb(var(--em-lens-rgb) / 0.75);
}

.em-row.is-sel .em-node {
  background: var(--em-bone);
}

/* Row content */
.em-row-main {
  flex: 1;
  min-width: 0;
  padding: 9px 12px 9px 0;
  min-height: 46px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

@media (min-width: 900px) {
  .em-row-main {
    padding-block: 7px;
    min-height: 36px;
  }
}

.em-row-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

.em-book-name {
  font-size: 15.5px;
  line-height: 1.3;
  color: var(--em-bone-dim);
  transition: color 0.15s ease;
}

@media (min-width: 900px) {
  .em-book-name {
    font-size: 14px;
  }
}

.em-row:hover .em-book-name,
.em-row.is-lit .em-book-name,
.em-row.is-sel .em-book-name {
  color: var(--em-bone);
}

/* Must follow .is-lit above: same specificity, so source order decides.
   A book on the active thread reads brighter; an arrival reads gold. */
.em-row.is-onthread .em-book-name {
  color: var(--em-bone);
  font-weight: 500;
}

/* Not gold: the name is the largest thing in the row, and setting it in gold
   was most of why an arrival stopped reading as part of the thread. The ✧
   beside it and the gold under it say what this book is. */
.em-row.is-arrival .em-book-name {
  color: var(--em-bone);
  font-weight: 500;
}

.em-row.is-sel .em-book-name {
  font-family: var(--em-serif);
  font-weight: 500;
  font-size: 16.5px;
}

@media (min-width: 900px) {
  .em-row.is-sel .em-book-name {
    font-size: 15px;
  }
}

/* Shown at every width now that it is the only place the genre is stated —
   the spine carries a plain node instead of a genre mark. */
.em-genre-tag {
  display: inline;
  font-family: var(--em-mono);
  font-size: 8.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--em-faint);
}

/* Carrying more of the arrival now that the book name is no longer gold. */
.em-star {
  font-family: var(--em-mono);
  font-size: 11px;
  color: var(--em-lens);
  opacity: 1;
  text-shadow: 0 0 8px rgb(var(--em-lens-rgb) / 0.55);
}

@media (min-width: 900px) {
  .em-star {
    display: none;
  }
}

/* A promise arriving in Christ. The thread keeps its color (color belongs to
   threads); the gold star marks the arrival. */
.em-entry.is-fulfillment .em-entry-ref::before {
  content: "\2726\00a0";
  color: var(--em-lens);
}

.em-entry.is-fulfillment {
  background: linear-gradient(90deg, rgb(var(--em-lens-rgb) / 0.06), transparent 75%);
  border-radius: 0 8px 8px 0;
  padding-block: 8px;
  margin-left: 0;
}

/* ---------- The Silence ---------- */

.em-silence {
  display: flex;
  align-items: stretch;
  height: 150px;
}

.em-silence-text {
  flex: 1;
  display: flex;
  align-items: center;
  font-family: var(--em-serif);
  font-style: italic;
  font-size: 15px;
  letter-spacing: 0.08em;
  color: var(--em-faint);
}

/* ---------- End cap ---------- */

.em-endcap {
  padding: 44px 24px 64px;
  text-align: center;
}

.em-endcap-star {
  display: block;
  font-size: 13px;
  color: var(--em-lens);
  text-shadow: 0 0 14px rgb(var(--em-lens-rgb) / 0.7);
  margin-bottom: 18px;
}

.em-endcap-line {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--em-dim);
  margin: 0 0 6px;
}

.em-endcap-quote {
  font-family: var(--em-serif);
  font-style: italic;
  font-size: 19px;
  color: var(--em-bone);
  margin: 0 0 22px;
}

.em-endcap-links {
  display: flex;
  justify-content: center;
  gap: 22px;
  flex-wrap: wrap;
}

/* ---------- Detail view ---------- */

.em-detail-inner {
  padding: 22px 20px 56px;
  max-width: 660px;
  animation: em-fade-up 0.32s ease-out both;
}

@media (min-width: 900px) {
  .em-detail-inner {
    padding: 36px 40px 64px;
  }
}

/* Must follow the shorthand above, or it is overwritten by it. Clears the home
   indicator so the last line of a passage is never under it. */
@media (max-width: 899.98px) {
  .em-detail-inner {
    padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px));
  }
}

/* Route from a book to the thread's full arc. A button rather than a back
   link: the reader may have opened this book first and turned the thread on
   afterwards, in which case there is nothing to go "back" to — it is an
   invitation to somewhere they have not been. */
.em-thread-cta {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 20px;
  padding: 9px 15px 9px 13px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--thread-color) 42%, transparent);
  background: color-mix(in srgb, var(--thread-color) 11%, transparent);
  color: var(--thread-color);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.005em;
  transition: background-color 0.16s ease, border-color 0.16s ease, transform 0.16s ease;
}

.em-thread-cta:hover {
  background: color-mix(in srgb, var(--thread-color) 20%, transparent);
  border-color: var(--thread-color);
  transform: translateX(2px);
}

.em-thread-cta svg {
  width: 14px;
  height: 14px;
  opacity: 0.75;
}

.em-thread-cta-dot {
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: var(--thread-color);
  box-shadow: 0 0 8px color-mix(in srgb, var(--thread-color) 70%, transparent);
}

/* ---------- Thread overview (the panel a thread opens into) ---------- */

/* ---------- thread header watermark ----------
   The title block only ever fills the left half of the panel, so the thread's
   own shape from the visualizer sits in the space to the right of it. It is
   the same miniature the visualizer door card uses — a reader who has been
   there recognises the line — and it redraws whenever a thread opens.

   Strictly decorative: aria-hidden in the markup, and it never takes a
   pointer event, so it cannot intercept a click meant for the title. */
.em-ov-head,
.em-bk-head {
  position: relative;
}

.em-ov-viz {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  width: min(52%, 330px);
  pointer-events: none;
  /* The line is the thread's colour, the same variable the door card feeds. */
  --vc: var(--thread-color);
}

/* The chart is masked, the wash is not. Separating them lets the colour carry
   on past the point where the constellation stops — a faint tint behind a word
   costs nothing in legibility, whereas the lines and dots would. --viz-fade is
   measured per thread in fitThreadWatermark(), so a short name like Seed shows
   far more of its own shape than Appearances can. */
.em-ov-viz .em-vizmini {
  -webkit-mask-image: linear-gradient(90deg, transparent 0%,
    transparent var(--viz-fade, 30%), #000 calc(var(--viz-fade, 30%) + 24%));
  mask-image: linear-gradient(90deg, transparent 0%,
    transparent var(--viz-fade, 30%), #000 calc(var(--viz-fade, 30%) + 24%));
}

/* A wash of the thread's own colour behind the line, so the graphic reads as
   part of the header rather than a small chart floating in the corner.

   Radial rather than linear. A linear gradient only fades along one axis, so
   however soft its left edge, the top and bottom stay hard — and on a
   high-chroma thread like Throne that turned the header's right half into a
   visible lavender box with two ruled edges. An ellipse anchored past the
   right edge dissolves in every direction at once, which is what a watermark
   has to do. */
   The radii matter as much as the shape. The ellipse has to die inside the
   box on every side, so each radius is sized against the distance to its own
   edge: 118% horizontally reaches past the far side but leaves the near one
   beyond the last stop, and 58% vertically — with the box grown well past the
   graphic — puts the top and bottom edges outside it too. At 125% vertical the
   fade never finished before the edge, which is what drew the two hard rules
   across the header in the first place. */
.em-ov-viz::before {
  content: "";
  position: absolute;
  inset: -30px -8px;
  background: radial-gradient(
    118% 58% at 106% 50%,
    color-mix(in srgb, var(--thread-color) 24%, transparent) 0%,
    color-mix(in srgb, var(--thread-color) 12%, transparent) 48%,
    transparent 80%);
}

/* The same percentages read very differently on the two grounds. On black a
   bright thread colour lifts the panel plainly; on cream the light-mode colour
   is already dark and low-chroma, so the same mix barely moves the paper and
   the graphic floats with nothing under it. Light mode gets a firmer wash so
   both themes carry the same weight. */
[data-theme="light"] .em-ov-viz::before {
  background: radial-gradient(
    118% 58% at 106% 50%,
    color-mix(in srgb, var(--thread-color) 30%, transparent) 0%,
    color-mix(in srgb, var(--thread-color) 15%, transparent) 48%,
    transparent 80%);
}

.em-ov-viz .em-vizmini {
  position: relative;
  height: 84px;
}

/* Quieter than the door card's version — this one sits beside a title rather
   than being the thing you are looking at — but present enough to read as the
   thread's shape rather than decoration. */
.em-ov-viz .em-vizmini-bars rect {
  fill: rgb(var(--em-tint) / 0.18);
}

.em-ov-viz .em-vizmini-line {
  stroke-width: 1.4;
  opacity: 0.85;
}

.em-ov-viz .em-vizmini-dots circle {
  r: 1.5;
}

/* On a phone the header has no spare half — the longest subtitle runs to
   within 15px of the right edge — so the watermark moves up beside the title
   line instead, which is the one row with real room. The widest thread name
   ("Appearances") ends at about 64% of the header, and the subtitle starts
   below the band entirely, so the mask holds everything transparent until 70%
   and only the tail of the constellation is ever drawn. The graphic still
   spans the full width underneath, so the line keeps its true proportions
   rather than being squeezed into a corner.

   70% rather than 64% on purpose: at 64% the first painted pixel landed two
   from the end of the longest name, which any font substitution or a longer
   thread added later would have closed. This leaves about twenty. */
@media (max-width: 560px) {
  .em-ov-viz {
    top: 0;
    right: 0;
    width: 100%;
    height: 54px;
    transform: none;
  }

  .em-ov-viz .em-vizmini {
    height: 54px;
  }

  /* The wash runs the full band and starts well left of where the chart does,
     so the colour reads across the header rather than stopping in a block at
     the right edge. It sits behind the title text, which a tint this faint can
     do without costing legibility. */
  .em-ov-viz::before {
    inset: -20px -6px;
    background: radial-gradient(
      130% 58% at 104% 50%,
      color-mix(in srgb, var(--thread-color) 16%, transparent) 0%,
      color-mix(in srgb, var(--thread-color) 8%, transparent) 46%,
      transparent 82%);
  }

  /* Same correction as the wide layout, at the phone's lighter weights. */
  [data-theme="light"] .em-ov-viz::before {
    background: radial-gradient(
      130% 58% at 104% 50%,
      color-mix(in srgb, var(--thread-color) 20%, transparent) 0%,
      color-mix(in srgb, var(--thread-color) 10%, transparent) 46%,
      transparent 82%);
  }
}

.em-ov-hero {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
}

.em-ov-dot {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: var(--thread-color);
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--thread-color) 18%, transparent);
}

.em-ov-sub {
  margin-top: 3px;
  font-size: 14px;
  color: var(--em-dim);
}

.em-ov-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--em-rule);
  font-family: var(--em-mono);
  font-size: 11.5px;
  color: var(--em-faint);
}

.em-ov-meta b {
  color: var(--em-bone-dim, var(--em-dim));
  font-weight: 500;
}

.em-ov-intro p {
  margin: 16px 0 0;
  font-family: var(--em-serif);
  font-size: 16.5px;
  line-height: 1.72;
  color: var(--em-bone-dim, var(--em-dim));
}

.em-ov-arc-label {
  margin: 30px 0 4px;
}

/* The arc. A rule runs the length of it in the thread's colour, so the beats
   read as one sequence rather than as a stack of separate cards. */
.em-beats {
  position: relative;
  margin: 0;
  padding: 6px 0 0 20px;
  list-style: none;
}

.em-beats::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 14px;
  bottom: 14px;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--thread-color) 15%, transparent),
    var(--thread-color) 12%,
    var(--thread-color) 88%,
    color-mix(in srgb, var(--thread-color) 15%, transparent));
  opacity: 0.5;
}

.em-beat {
  position: relative;
  padding: 0 0 26px;
}

.em-beat::before {
  content: "";
  position: absolute;
  left: -21px;
  top: 7px;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--thread-color);
}

/* A landing is where the promise arrives; ring it and turn it gold. */
.em-beat.is-fulfillment::before {
  left: -23px;
  top: 5px;
  width: 12px;
  height: 12px;
  background: var(--em-lens);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--em-lens) 22%, transparent);
}

.em-beat-book {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: 0;
  background: none;
  font-family: var(--em-mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--em-faint);
  transition: color 0.15s ease;
}

.em-beat-book:hover,
.em-beat-book:focus-visible {
  color: var(--em-bone);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.em-beat.is-fulfillment .em-beat-book {
  color: var(--em-lens);
}

.em-beat-body {
  margin-top: 5px;
}

.em-beat-ref {
  display: inline-block;
  font-size: 13px;
  font-weight: 600;
  color: var(--em-bone-dim, var(--em-dim));
  text-decoration: none;
}

.em-beat-ref:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.em-beat-quote {
  margin: 7px 0 0;
  padding-left: 12px;
  border-left: 2px solid var(--em-rule);
  font-family: var(--em-serif);
  font-size: 15.5px;
  font-style: italic;
  line-height: 1.62;
  color: var(--em-bone);
}

.em-beat-text {
  margin: 9px 0 0;
  font-size: 14px;
  line-height: 1.68;
  color: var(--em-dim);
}

.em-beat .em-tags {
  margin-top: 9px;
}

.em-ov-fulfillment {
  margin-top: 12px;
  padding: 18px 20px 20px;
  border: 1px solid color-mix(in srgb, var(--em-lens) 26%, var(--em-rule));
  border-radius: 14px;
  background: color-mix(in srgb, var(--em-lens) 5%, transparent);
}

.em-ov-fulfillment p {
  margin: 8px 0 0;
  font-family: var(--em-serif);
  font-size: 16px;
  line-height: 1.72;
  color: var(--em-bone);
}

.em-detail-hero {
  display: flex;
  align-items: center;
  gap: 18px;
}

/* ---------- the book watermark ----------
   The book header wears the thread header's watermark treatment unchanged —
   same class, so the position, the mask, the wash, the fade measurement and
   the phone layout are all one implementation. Only the graphic inside it
   differs: bookMiniSvg draws one book's chapters where vizMiniSvg draws the
   sixty-six books, using the same frame and the same parts, so both are styled
   by the .em-vizmini rules above.

   Two things differ. The strip is pushed right rather than masked, and the
   ticks a promise lands on stand taller in the thread's colour. */
.em-bk-head {
  position: relative;
}

/* The thread graphic is masked because it always draws all sixty-six books and
   only the tail needs to show. A book's strip cannot be treated that way: mask
   the left and you hide the opening of the book, so a promise in Genesis 2 or
   3 simply never appeared — worst on a phone, where the title takes most of
   the band. So the whole book is drawn to the right of the title instead,
   narrower but complete, with a hard start because that is where the book
   starts. The cap is a floor on the strip's width, not a working limit: the
   widest measurement any of the sixty-six produces is 66.4% (1 Thessalonians,
   on a phone), so at 67% every book clears its own title and the narrowest
   strip still gets a third of the band — thirty ticks at four pixels each.
   It exists so a name longer than any we have cannot squeeze it to nothing. */
.em-bk-viz .em-vizmini {
  --bk-start: min(var(--viz-fade, 30%), 67%);
  margin-left: var(--bk-start);
  width: calc(100% - var(--bk-start));
  -webkit-mask-image: none;
  mask-image: none;
}

/* Hairlines, not blocks. The strip should read as a length of time with the
   promise struck on it, so the unmarked ticks sit at the threshold of visible
   and only the marked ones carry weight and colour. */
.em-bk-viz .em-vizmini-bars rect {
  fill: rgb(var(--em-tint) / 0.3);
}

.em-bk-risers rect {
  fill: var(--vc);
  opacity: 0.85;
}

/* Where a promise arrives, gold — the same mark the visualizer and the list
   already use, carried by the tick itself so it survives any width. */
.em-bk-risers rect.is-f {
  fill: var(--em-lens);
  opacity: 1;
}

.em-detail-kicker {
  font-family: var(--em-mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--em-dim);
}

.em-detail-title {
  font-family: var(--em-serif);
  font-size: 31px;
  font-weight: 400;
  line-height: 1.08;
  margin: 6px 0 7px;
  text-wrap: balance;
}

@media (min-width: 900px) {
  .em-detail-title {
    font-size: 37px;
  }
}

.em-detail-pos {
  font-family: var(--em-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  color: var(--em-faint);
}

/* Dating line and theme tags, between the title and the body */
.em-dates {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 18px;
  margin-top: 18px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--em-rule);
  font-family: var(--em-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--em-bone-dim);
}

.em-dates b {
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--em-faint);
  margin-right: 6px;
}

.em-themes {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 14px;
}

.em-themes span {
  padding: 4px 10px;
  border-radius: 999px;
  background: rgb(var(--em-tint) / 0.06);
  border: 1px solid var(--em-rule);
  font-size: 10.5px;
  letter-spacing: 0.02em;
  color: var(--em-bone-dim);
}

.em-detail-threads {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 18px;
}

/* Each dot selects its thread, so a reader can move from a book into the
   promise running through it without leaving the page. */
.em-detail-threads .em-tdot {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px 5px 9px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  background: transparent;
  font-family: var(--em-mono);
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--em-dim);
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}

.em-detail-threads .em-tdot:hover {
  color: var(--em-bone-dim);
  border-color: var(--em-dim);
}

.em-detail-threads .em-tdot i {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--tdot-color);
  opacity: 0.55;
  transition: opacity 0.15s ease, box-shadow 0.15s ease;
}

.em-detail-threads .em-tdot.is-on {
  border-color: var(--tdot-color);
  background: color-mix(in srgb, var(--tdot-color) 12%, transparent);
  color: var(--tdot-color);
}

.em-detail-threads .em-tdot.is-on i {
  opacity: 1;
  box-shadow: 0 0 7px color-mix(in srgb, var(--tdot-color) 80%, transparent);
}

/* Held back by the cap until the +N is tapped. */
.em-detail-threads:not(.is-expanded) .em-tdot.is-extra {
  display: none;
}

.em-tdot-more {
  display: inline-flex;
  align-items: center;
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px dashed var(--em-rule);
  background: transparent;
  font-family: var(--em-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  color: var(--em-faint);
  transition: color 0.15s ease, border-color 0.15s ease;
}

.em-tdot-more:hover {
  color: var(--em-bone-dim);
  border-color: var(--em-dim);
}

.em-detail-hint {
  font-family: var(--em-serif);
  font-style: italic;
  font-size: 14px;
  color: var(--em-dim);
  margin: 0;
}

/* ---------- every thread in this book, at once ----------
   The study view. The label and its control share a row so the section reads
   as one thing you can open, rather than a heading with a stray button. */
.em-allthreads-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.em-allthreads-head > .em-field-label {
  margin-bottom: 0;
}

.em-allthreads-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--em-rule);
  border-radius: 999px;
  background: transparent;
  font-family: var(--em-mono);
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--em-bone-dim);
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background-color .15s ease;
}

.em-allthreads-btn:hover,
.em-allthreads-btn:focus-visible {
  color: var(--em-bone);
  border-color: var(--em-dim);
  background: rgb(var(--em-lens-rgb) / 0.07);
}

.em-allthreads-btn svg {
  width: 13px;
  height: 13px;
  transition: transform .2s ease;
}

.em-allthreads-btn[aria-expanded="true"] svg {
  transform: rotate(180deg);
}

.em-allthreads .em-detail-hint {
  margin-top: 12px;
}

/* Each thread keeps its own colour so the groups stay tellable apart while
   scrolling — the same job the lead panel does for a single thread. */
.em-allthreads-group {
  margin-top: 22px;
  padding: 14px 18px 16px;
  border-left: 2px solid var(--lead-color);
  border-radius: 0 14px 14px 0;
  background: color-mix(in srgb, var(--lead-color) 6%, transparent);
}

.em-allthreads-group .em-entry {
  border-left: 0;
  padding-left: 0;
}

.em-allthreads-group .em-entry.is-fulfillment {
  background: none;
  border-radius: 0;
  padding-block: 0;
}

/* The name is a button: seeing a beat you want to follow and being unable to
   act on it would be the obvious frustration here, so it selects that thread. */
.em-allthreads-name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 9px;
  width: 100%;
  margin-bottom: 12px;
  padding: 0;
  border: 0;
  background: transparent;
  text-align: left;
  font-family: var(--em-mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--lead-color);
  cursor: pointer;
}

.em-allthreads-name > i {
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: 50%;
  background: var(--tdot-color);
}

.em-allthreads-name:hover .em-allthreads-sub,
.em-allthreads-name:focus-visible .em-allthreads-sub {
  color: var(--em-bone-dim);
}

.em-allthreads-sub {
  font-family: var(--em-serif);
  font-style: italic;
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--em-dim);
  transition: color .15s ease;
}

.em-fields {
  margin-top: 30px;
  display: grid;
  gap: 26px;
}

.em-field-label {
  font-family: var(--em-mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--em-dim);
  margin-bottom: 8px;
}

.em-field-text {
  font-family: var(--em-serif);
  font-size: 18px;
  line-height: 1.6;
  margin: 0;
}

@media (min-width: 900px) {
  .em-field-text {
    font-size: 17.5px;
  }
}

.em-detail-section {
  margin-top: 34px;
  padding-top: 26px;
  border-top: 1px solid var(--em-rule);
}

.em-detail-section > .em-field-label {
  margin-bottom: 18px;
}

/* The active thread now sits above the book's own description, so it reads as a
   panel in the thread's colour rather than a section divided off by a rule. */
.em-detail-section.is-lead {
  margin-top: 22px;
  padding: 18px 20px 20px;
  border-top: 0;
  border-left: 2px solid var(--lead-color);
  border-radius: 0 14px 14px 0;
  background: color-mix(in srgb, var(--lead-color) 7%, transparent);
}

.em-detail-section.is-lead > .em-field-label {
  color: var(--lead-color);
  margin-bottom: 14px;
}

/* Entries inside the panel drop their own left rule — the panel supplies it */
.em-detail-section.is-lead .em-entry {
  border-left: 0;
  padding-left: 0;
}

.em-detail-section.is-lead .em-entry.is-fulfillment {
  background: none;
  border-radius: 0;
  padding-block: 0;
}

.em-entry {
  border-left: 2px solid var(--entry-color, var(--em-lens-soft));
  padding-left: 14px;
  margin-bottom: 18px;
}

.em-entry:last-child {
  margin-bottom: 0;
}

.em-entry-ref {
  font-family: var(--em-mono);
  font-size: 11px;
  letter-spacing: 0.03em;
  color: var(--entry-color, var(--em-lens));
  margin-bottom: 4px;
}

.em-entry-ref a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, currentColor 35%, transparent);
  transition: border-color 0.15s ease;
}

.em-entry-ref a:hover {
  border-bottom-color: currentColor;
}

.em-entry-ref a::after {
  content: "\2197";
  font-size: 8.5px;
  margin-left: 4px;
  opacity: 0.65;
}

.em-entry-ref .em-entry-thread {
  color: var(--em-dim);
}

/* The scripture itself, set apart from the commentary on it */
.em-quote {
  margin: 8px 0 9px;
  padding: 0;
  font-family: var(--em-serif);
  font-size: 16px;
  line-height: 1.55;
  color: var(--em-bone);
}

@media (min-width: 900px) {
  .em-quote {
    font-size: 15.5px;
  }
}

.em-entry-text {
  font-size: 15px;
  line-height: 1.62;
  color: var(--em-bone-dim);
  margin: 0;
}

/* What kind of claim this is, and how firmly it is grounded */
.em-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 9px;
}

.em-tag {
  padding: 3px 8px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  font-family: var(--em-mono);
  font-size: 8.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--em-faint);
  cursor: help;
}

.em-tag.is-stated {
  border-color: color-mix(in srgb, var(--em-lens) 40%, transparent);
  color: var(--em-lens);
}

.em-tag.is-pattern {
  color: var(--em-dim);
}

.em-tag.is-traditional {
  border-style: dashed;
}

@media (min-width: 900px) {
  .em-entry-text {
    font-size: 14px;
  }
}

/* Testimony gets its own gold-washed panel */
.em-detail-section.is-testimony {
  border-top: 0;
  padding: 22px 20px 20px;
  border-radius: 16px;
  background: rgb(var(--em-lens-rgb) / 0.045);
  border: 1px solid rgb(var(--em-lens-rgb) / 0.14);
}

.em-detail-section.is-testimony > .em-field-label {
  color: var(--em-lens);
}

/* Prev / next book navigation */
.em-booknav {
  margin-top: 40px;
  padding-top: 22px;
  border-top: 1px solid var(--em-rule);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.em-booknav button {
  display: block;
  text-align: left;
  padding: 13px 16px;
  border: 1px solid var(--em-rule);
  border-radius: 14px;
  background: transparent;
  color: inherit;
  transition: border-color 0.15s ease, transform 0.15s ease, background-color 0.15s ease;
}

.em-booknav button:hover {
  border-color: var(--em-lens-soft);
  background: rgb(var(--em-lens-rgb) / 0.05);
  transform: translateY(-1px);
}

.em-booknav .is-next {
  text-align: right;
}

.em-booknav .nav-label {
  display: block;
  font-family: var(--em-mono);
  font-size: 8.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--em-faint);
  margin-bottom: 4px;
}

.em-booknav .nav-title {
  display: block;
  font-family: var(--em-serif);
  font-size: 16px;
  color: var(--em-bone);
}

/* ---------- Welcome panel (desktop empty state) ---------- */

.em-empty {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100%;
  padding: 48px 48px 56px;
  max-width: 620px;
}

/* A phone cannot spare 48px a side — the lede was breaking after three words.
   The deep bottom padding keeps the panel clear of the floating button, which
   would otherwise sit on top of the first step. */
@media (max-width: 599.98px) {
  .em-empty {
    padding: 26px 20px 100px;
  }

  .em-empty-lede {
    font-size: 23px;
  }
}

/* One line to say where the name comes from, then straight into what this is.
   The row of genre glyphs that used to head the panel is gone: they carried no
   action and read as buttons. */
.em-empty-kicker {
  font-family: var(--em-mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--em-lens-soft);
  margin: 0 0 14px;
}

.em-empty-lede {
  font-family: var(--em-serif);
  font-size: 26px;
  font-weight: 300;
  line-height: 1.4;
  color: var(--em-bone);
  margin: 0;
  text-wrap: balance;
}

.em-empty-tie {
  margin: 22px 0 0;
  font-size: 15px;
  line-height: 1.68;
  color: var(--em-bone-dim);
}

.em-empty-steps {
  list-style: none;
  margin: 30px 0 0;
  padding: 0;
  display: grid;
  gap: 14px;
}

.em-empty-steps li {
  display: flex;
  gap: 14px;
  align-items: baseline;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--em-bone-dim);
}

.em-empty-steps .n {
  font-family: var(--em-mono);
  font-size: 10px;
  color: var(--em-lens-soft);
}

/* The ways in, ahead of the explanation rather than below it. The thread
   picker is not a conventional control, so the panel leads with the doors and
   explains afterwards. */
.em-empty-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 30px;
  padding-bottom: 30px;
  border-bottom: 1px solid var(--em-rule);
}

/* On a phone they stack full width, which reads as a menu rather than as a
   row of chips and gives each a comfortable tap target. */
@media (max-width: 599.98px) {
  .em-empty-actions {
    display: grid;
    gap: 10px;
  }

  .em-empty-actions button {
    width: 100%;
    justify-content: center;
    min-height: 50px;
  }
}

.em-btn-spine {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  margin-right: -2px;
}

.em-empty-steps {
  margin-top: 28px;
}

/* ---------- Help dialog ---------- */

.em-dialog {
  margin: auto;
  width: min(92vw, 600px);
  max-height: min(86dvh, 720px);
  padding: 0;
  border: 1px solid var(--em-rule);
  border-radius: 20px;
  background: var(--em-ground-2);
  color: var(--em-bone);
  font-family: var(--em-sans);
  box-shadow: 0 24px 80px rgb(var(--em-shadow-rgb) / var(--em-shadow-a));
}

.em-dialog::backdrop {
  background: rgb(var(--em-scrim) / 0.72);
  backdrop-filter: blur(3px);
}

/* Icons shown inside a sentence, so the instruction points at the mark the
   reader is hunting for rather than describing it. Boxed like a keycap so it
   reads as "this thing on your screen" and not as decoration. */
.em-inline-icon {
  display: inline-block;
  width: 24px;
  height: 24px;
  padding: 3px;
  vertical-align: -7px;
  margin: 0 2px;
  border-radius: 7px;
  border: 1px solid var(--em-rule);
  background: rgb(var(--em-plate) / 0.18);
  color: var(--em-bone);
}

/* Disclosure chevrons sit inside a label, so they stay bare and small. */
.em-inline-icon.is-tight {
  width: 13px;
  height: 13px;
  padding: 0;
  vertical-align: -2px;
  margin: 0 0 0 2px;
  border: 0;
  background: none;
  border-radius: 0;
}

/* A miniature of the button that carries a reader from a book to its thread. */
.em-inline-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 7px 2px 6px;
  margin: 0 1px;
  vertical-align: -3px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--pill-color) 45%, transparent);
  background: color-mix(in srgb, var(--pill-color) 12%, transparent);
  color: var(--pill-color);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}

.em-inline-pill i {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: currentColor;
}

.em-dialog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 24px 14px;
  border-bottom: 1px solid var(--em-rule);
}

.em-dialog-head h2 {
  font-family: var(--em-serif);
  font-size: 22px;
  font-weight: 400;
  margin: 0;
}

.em-dialog-close {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 999px;
  border: 1px solid var(--em-rule);
  background: transparent;
  color: var(--em-dim);
  font-size: 15px;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.em-dialog-close:hover {
  color: var(--em-bone);
  border-color: var(--em-dim);
}

.em-dialog-body {
  padding: 20px 24px 28px;
  overflow-y: auto;
  max-height: calc(min(86dvh, 720px) - 76px);
}

/* .em-dialog-body's scrollbar is styled with the rest of them, up beside
   .em-list — see the scrollbars block. */

.em-dialog-body > p {
  font-size: 14.5px;
  line-height: 1.7;
  color: var(--em-bone-dim);
  margin: 0 0 14px;
}

.em-dialog-body h3 {
  font-family: var(--em-mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--em-lens);
  margin: 24px 0 8px;
}

/* ---------- Guide sections ----------
   Collapsible so the dialog can be scanned rather than read. Built on
   <details>, which brings its own keyboard handling and open/close semantics
   and needs no script. */

.em-dialog-lede {
  font-family: var(--em-serif);
  font-size: 17px;
  line-height: 1.6;
  color: var(--em-bone);
  margin: 0 0 6px;
}

.em-guide {
  border-top: 1px solid var(--em-rule);
}

.em-guide:last-of-type {
  border-bottom: 1px solid var(--em-rule);
}

.em-guide > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--em-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--em-bone-dim);
  transition: color 0.15s ease;
}

.em-guide > summary::-webkit-details-marker {
  display: none;
}

.em-guide > summary:hover {
  color: var(--em-bone);
}

.em-guide[open] > summary {
  color: var(--em-lens);
}

/* A chevron drawn from two borders, so it needs no glyph or asset. */
.em-guide > summary::after {
  content: "";
  flex-shrink: 0;
  width: 7px;
  height: 7px;
  margin-right: 3px;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 0.18s ease;
}

.em-guide[open] > summary::after {
  transform: translateY(1px) rotate(225deg);
}

.em-guide > *:not(summary) {
  margin-bottom: 16px;
}

.em-guide p {
  font-size: 14px;
  line-height: 1.65;
  color: var(--em-bone-dim);
  margin: 0 0 10px;
}

.em-guide-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 9px;
}

.em-guide-list li {
  position: relative;
  padding-left: 16px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--em-bone-dim);
}

.em-guide-list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 9px;
  width: 4px;
  height: 4px;
  border-radius: 999px;
  background: var(--em-faint);
}

.em-guide-defs {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 8px 14px;
  margin: 0;
  font-size: 14px;
  line-height: 1.55;
}

.em-guide-defs dt {
  font-family: var(--em-mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--em-lens-soft);
  padding-top: 2px;
  white-space: nowrap;
}

.em-guide-defs dd {
  margin: 0;
  color: var(--em-bone-dim);
}

@media (max-width: 460px) {
  .em-guide-defs {
    grid-template-columns: 1fr;
    gap: 3px;
  }

  .em-guide-defs dd {
    margin-bottom: 10px;
  }
}

/* Navigation is what someone is stuck without, so it reads as instructions:
   the action in bold, the method after it. */
.em-nav-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 11px;
}

.em-nav-list li {
  font-size: 14px;
  line-height: 1.6;
  color: var(--em-bone-dim);
}

.em-nav-list b {
  color: var(--em-bone);
  font-weight: 600;
}

/* Needs two classes to outrank `.em-dialog-body > p`, which otherwise sets
   this at body size and left it looking like a paragraph someone forgot. */
.em-dialog-body > .em-dialog-foot {
  margin: 20px 0 0;
  font-size: 11.5px;
  line-height: 1.7;
  color: var(--em-faint);
}

.em-dialog-body > .em-dialog-foot a {
  color: var(--em-dim);
  text-decoration: none;
  border-bottom: 1px solid var(--em-rule);
  transition: color 0.15s ease, border-color 0.15s ease;
}

.em-dialog-body > .em-dialog-foot a:hover {
  color: var(--em-lens);
  border-bottom-color: var(--em-lens-soft);
}

/* ---------- Animations ---------- */

@keyframes em-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes em-fade-up {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes em-slide-in {
  from { opacity: 0.4; transform: translateX(28px); }
  to { opacity: 1; transform: translateX(0); }
}

@keyframes em-bloom {
  from { opacity: 0; transform: scale(0.55); }
  to { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .em-app *,
  .em-app *::before,
  .em-app *::after,
  .em-dialog * {
    transition-duration: 0.001s !important;
    animation-duration: 0.001s !important;
  }
}
