/* --------------------------------------------------------------------
   companion.css — the chat panel, the author panel, and chapter sketches.

   One rule here carries weight rather than style: when the ceiling is set to
   the whole book, the panel goes red and stays red. Spoiler state must never
   be ambient — if it can be left on and forgotten, it will be.
   -------------------------------------------------------------------- */

/*
 * Full height when it is the only thing on screen; between the bar and the
 * footer when there is a book behind it.
 *
 * This panel opens from the library as well as from the reader, so it cannot
 * simply be pinned to the reading area — but while a book is open it has to
 * behave like every other panel: sit in the gap, and let the page step aside
 * rather than be covered. The two heights are measured by reader.js onto the
 * root, which is why a panel outside the reader's own section can read them.
 */
.companion-panel {
  position: fixed;
  inset-block: 0;
  inset-inline: auto 0;
  z-index: 60;
  width: min(460px, 100vw);
  display: flex;
  flex-direction: column;
  background: var(--bg-2);
  border-inline-start: 1px solid var(--border);
  box-shadow: var(--shadow);
}

/* ---------------------------------------------------------------- */
/* Spoilers on                                                       */
/* ---------------------------------------------------------------- */

/*
 * The one place red is still right, and deliberately not changed to amber.
 *
 * Everything else about the boundary is amber now, because meeting the
 * boundary is the product working. This is different: whole-book mode is the
 * boundary switched *off*, at the reader's own insistence, and for as long as
 * it is on there is nothing protecting them. That is the single state in the
 * app where "you have turned the safety off" is the honest message, and the
 * colour should say so. Do not unify this with the amber above.
 */
.companion-panel[data-spoilers="on"] { border-inline-start-color: var(--danger); }
.companion-panel[data-spoilers="on"] .panel-head {
  background: color-mix(in srgb, var(--danger) 14%, var(--bg-2));
  border-bottom-color: var(--danger);
}
.companion-panel[data-spoilers="on"] .panel-head h2 { color: var(--danger); }
.companion-panel[data-spoilers="on"] #companion-ceiling { color: var(--danger); font-weight: 600; }

/* ---------------------------------------------------------------- */
/* Ceiling selector                                                  */
/* ---------------------------------------------------------------- */

.companion-ceiling {
  flex: none;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.companion-ceiling .field-row > span:first-child { flex-basis: 7.5rem; }
.companion-ceiling p { font-size: var(--step--2); }

/*
 * "Answering from", in the companion's title bar (29 Sept).
 *
 * It was a block of its own — a label wrapping to two lines, the dropdown, and
 * the sentence under it — and on a laptop it took as much height as the answer
 * got. The dropdown now sits beside the name and the sentence is one small
 * line under the bar. The name gives way first on a narrow panel, then the
 * dropdown's text; the buttons never do.
 */
.companion-head { gap: var(--sp-2); }
.companion-head h2 { flex: 0 1 auto; }
.companion-head .companion-mode {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 17rem;
  width: auto;
  min-height: 30px;
  padding-inline: var(--sp-2) var(--sp-1);
  font-size: var(--step--1);
  margin-inline-start: auto;
}
#companion-panel .companion-ceiling { padding: var(--sp-1) var(--sp-4); gap: var(--sp-1); }
#companion-panel .companion-ceiling p { margin: 0; line-height: 1.35; }
.companion-panel[data-spoilers="on"] .companion-mode { border-color: var(--danger); color: var(--danger); }
@media (max-width: 480px) {
  .companion-head h2 { display: none; }
  .companion-head .companion-mode { margin-inline-start: 0; max-width: none; }
}

.companion-catchup {
  flex: none;
  margin: var(--sp-3) var(--sp-4) 0;
  padding: var(--sp-3);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 8%, var(--bg-2));
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  font-size: var(--step--1);
}

.set-row { display: flex; gap: var(--sp-2); }
.set-row .btn { flex: 1; }
.set-note { font-size: var(--step--2); line-height: 1.4; }

/* ---------------------------------------------------------------- */
/* Setup dialog                                                      */
/* ---------------------------------------------------------------- */

.ai-setup { display: flex; flex-direction: column; gap: var(--sp-3); }

.ai-current {
  font-size: var(--step--1);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-sm);
  background: var(--bg-3);
}

.ai-option {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* ---------------------------------------------------------------- */
/* Connected state                                                   */
/* ---------------------------------------------------------------- */

.ai-live {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--ok) 10%, var(--bg-2));
  border: 1px solid color-mix(in srgb, var(--ok) 40%, var(--border));
}
.ai-live-dot {
  width: 10px; height: 10px; flex: none;
  border-radius: 50%;
  background: var(--ok);
}
.ai-live-title { font-weight: 600; }
.ai-live-sub { font-size: var(--step--1); color: var(--fg-dim); }

.ai-facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-1) var(--sp-4);
  margin: 0;
  font-size: var(--step--1);
}
.ai-facts dt { color: var(--fg-dim); }
.ai-facts dd { margin: 0; }

/* ---------------------------------------------------------------- */
/* Choosing                                                          */
/* ---------------------------------------------------------------- */

.ai-route {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.ai-route:first-of-type { border-color: var(--accent); }
.ai-route-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.ai-route-head h3 { font-size: var(--step-0); font-weight: 600; }
.ai-route p { font-size: var(--step--1); line-height: 1.5; color: var(--fg-dim); }
.ai-route-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }

.ai-more { font-size: var(--step--1); }
.ai-more summary { cursor: pointer; color: var(--fg-dim); padding: var(--sp-2) 0; }
.ai-more p { line-height: 1.5; }

/* ---------------------------------------------------------------- */
/* Guided setup                                                      */
/* ---------------------------------------------------------------- */

.ai-steps {
  margin: 0;
  padding-inline-start: 1.4em;
  font-size: var(--step--1);
  line-height: 1.6;
}
.ai-steps li { margin-bottom: var(--sp-3); }

.ai-art {
  margin: var(--sp-2) 0 0;
  max-width: 300px;
  color: var(--fg);
}
.ai-art svg { width: 100%; height: auto; display: block; }

/* ---------------------------------------------------------------- */
/* Key check result                                                  */
/* ---------------------------------------------------------------- */

.ai-status {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-sm);
  font-size: var(--step--1);
  line-height: 1.45;
}
.ai-status-mark { font-weight: 700; flex: none; }

.ai-status[data-state="ok"] {
  background: color-mix(in srgb, var(--ok) 12%, var(--bg-2));
  border: 1px solid color-mix(in srgb, var(--ok) 45%, var(--border));
  color: var(--ok);
}
.ai-status[data-state="bad"] {
  background: color-mix(in srgb, var(--danger) 10%, var(--bg-2));
  border: 1px solid color-mix(in srgb, var(--danger) 45%, var(--border));
  color: var(--danger);
}
.ai-status[data-state="busy"] {
  background: var(--bg-3);
  border: 1px solid var(--border);
  color: var(--fg-dim);
}

.ai-warn {
  padding: var(--sp-2) var(--sp-3);
  border-inline-start: 3px solid var(--danger);
  background: color-mix(in srgb, var(--danger) 8%, var(--bg-2));
  font-size: var(--step--1);
}

.ai-setup a.btn { text-decoration: none; }
.ai-option h3 { font-size: var(--step--1); font-weight: 600; }
.ai-option p { font-size: var(--step--1); line-height: 1.5; }

/* "Is it working?" — the check's results and the list of recent problems. */
.ai-option > .btn { align-self: flex-start; }
.ai-check-results { display: flex; flex-direction: column; gap: var(--sp-1); }
.ai-check-ok { color: var(--ok); }
.ai-check-bad { color: var(--danger); }
.ai-problems { display: flex; flex-direction: column; gap: var(--sp-2); align-items: flex-start; }
.ai-problem-list {
  margin: 0;
  padding-inline-start: 1.1em;
  font-size: var(--step--2);
  line-height: 1.45;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  word-break: break-word;
}
.ai-option-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }

/* ---------------------------------------------------------------- */
/* Turns                                                             */
/* ---------------------------------------------------------------- */

.companion-turns {
  flex: 1;
  overflow-y: auto;
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  scroll-behavior: smooth;
}

.turn { font-size: var(--step--1); line-height: 1.55; overflow-wrap: anywhere; }
/* A long web address in an answer or a biography wraps, instead of pushing the
   whole transcript sideways. */
#author-body { overflow-wrap: anywhere; }
.turn p + p { margin-top: 0.6em; }

/* ---------------------------------------------------------------- */
/* Asking about what you haven't read                                */
/* ---------------------------------------------------------------- */

/*
 * Asking about something you have not reached is not an error.
 *
 * All three of these states — being offered the choice, seeing an answer kept
 * back, and seeing one you chose to have anyway — were drawn in red, with the
 * borders and headings the app uses for destructive actions. Red says *you did
 * something wrong*. Asking a question about a book is not wrong; the book
 * simply has not told you yet, which is the whole premise of the product
 * working correctly.
 *
 * So they are amber now, and amber here means what it means everywhere else in
 * Taper: this is the boundary, and this is where you are against it. §4 of the
 * brief is explicit that this moment should be something a reader would want to
 * screenshot, and nobody screenshots an error.
 *
 * `--danger` stays where it belongs — deleting a book, clearing the library.
 */
.turn-choice {
  border: 1px solid var(--flame);
  border-radius: var(--radius-sm);
  padding: var(--sp-3);
  background: color-mix(in srgb, var(--flame) 7%, var(--bg-2));
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.turn-choice-head {
  font-weight: 600;
  color: var(--fg);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
/* The mark itself, at reading size, so the sentence is signed by the flame
   rather than flagged by a colour. */
.turn-choice-head .flame,
.turn-hidden .flame { width: 0.95em; height: 0.95em; flex: none; }
.turn-choice-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.turn-choice-actions .btn { flex: 1 1 auto; }

.turn-hidden {
  /* Dashed, and in the flame's amber — the same dashed amber rule that marks
     the boundary on the page. One idea, drawn the same way wherever it is. */
  border: 1px dashed var(--flame);
  border-radius: var(--radius-sm);
  padding: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  align-items: flex-start;
}

/* An answer the reader deliberately chose to see past the boundary. Amber,
   because it is about the boundary — the reader has stepped over a line they
   were shown, which is a different thing from a mistake. */
.turn-spoiled { border-inline-start: 3px solid var(--flame); padding-inline-start: var(--sp-3); }
.turn-spoiled-label {
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--flame);
  font-weight: 600;
}

.turn-heading { font-weight: 600; margin-top: 0.9em !important; }
.turn-list { margin: 0.4em 0 0; padding-inline-start: 1.3em; }
.turn-list li { margin-bottom: 0.2em; }
.turn code {
  font: 0.9em var(--font-mono);
  background: var(--bg-3);
  padding: 0 3px;
  border-radius: 3px;
}

/* A cursor while the answer is still arriving, so a pause reads as thinking
   rather than as the app having stopped. Only once there is text to put it
   after — the waiting state below has its own. */
.turn[data-streaming="true"] > :last-child:not(.turn-waiting)::after {
  content: '▍';
  margin-inline-start: 2px;
  opacity: 0.55;
}

.turn-waiting { color: var(--fg-dim); display: flex; align-items: baseline; gap: 2px; }
.dots { display: inline-flex; gap: 3px; margin-inline-start: 4px; }
.dots i {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.35;
}

@media (prefers-reduced-motion: no-preference) {
  .turn[data-streaming="true"] > :last-child:not(.turn-waiting)::after {
    animation: caret 1.1s steps(2) infinite;
  }
  @keyframes caret { 50% { opacity: 0; } }

  .dots i { animation: think 1.3s ease-in-out infinite; }
  .dots i:nth-child(2) { animation-delay: 0.18s; }
  .dots i:nth-child(3) { animation-delay: 0.36s; }
  @keyframes think { 30% { opacity: 1; transform: translateY(-2px); } }
}

/**
 * When motion is switched off — Windows "show animations" unchecked, or an
 * e-paper screen — nothing above animates, and a still row of faint dots reads
 * as a bug rather than as thinking. So the indicator becomes something that is
 * legible without moving at all.
 */
@media (prefers-reduced-motion: reduce) {
  .dots { display: none; }
  .turn-waiting::after {
    content: ' — working';
    font-variant-caps: all-small-caps;
    letter-spacing: 0.08em;
  }
}
[data-epaper] .dots { display: none; }
[data-epaper] .turn-waiting::after { content: ' — working'; }

.turn-user {
  align-self: flex-end;
  max-width: 85%;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius);
  background: var(--bg-3);
}

.turn-assistant { max-width: 100%; }
.turn-error { color: var(--danger); }

.turn-aside {
  padding: var(--sp-3);
  border-inline-start: 3px solid var(--accent);
  background: var(--bg);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.turn-aside-label {
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--fg-dim);
  margin-bottom: var(--sp-1) !important;
}

/* Citations are the anti-hallucination mechanism, so they are shown as
   first-class buttons rather than tucked away — an answer with none is an
   answer nothing in the book supports. */
.turn-citations {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  margin-top: var(--sp-2);
}
.citation {
  font-size: var(--step--2);
  padding: 2px var(--sp-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg);
  color: var(--fg-dim);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.citation:hover { color: var(--fg); border-color: var(--fg-dim); }

.turn-footer {
  margin-top: var(--sp-2) !important;
  font-size: var(--step--2);
  color: var(--fg-dim);
}

/* ---------------------------------------------------------------- */
/* Where an answer came from                                         */
/* ---------------------------------------------------------------- */

/* One quiet line under each answer, opening to the passages and the reach.
   Closed it costs a line; open it costs what it always did. */
.turn-where { margin-top: var(--sp-2); }
.turn-where > summary {
  font-size: var(--step--2);
  color: var(--fg-dim);
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 2px 0;
}
.turn-where > summary::-webkit-details-marker { display: none; }
/* The triangle, drawn rather than inherited, so it matches the type and turns
   the way the browser's own one does. */
.turn-where > summary::before {
  content: '';
  width: 0;
  height: 0;
  border: 4px solid transparent;
  border-left-color: currentColor;
  transition: transform var(--speed) var(--ease);
}
.turn-where[open] > summary::before { transform: rotate(90deg) translateX(-1px); }
.turn-where > summary:hover { color: var(--fg); }

/* The one thing that stays outside: a caveat nobody opens is not a caveat. */
.turn-warning {
  margin-top: var(--sp-2) !important;
  font-size: var(--step--2);
  color: var(--danger);
}

@media (prefers-reduced-motion: reduce) {
  .turn-where > summary::before { transition: none; }
}

/* ---------------------------------------------------------------- */
/* Input                                                             */
/* ---------------------------------------------------------------- */

.companion-suggestions {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  padding: 0 var(--sp-4) var(--sp-2);
}

/*
 * The ready questions fold away once something has been asked (29 Sept), so
 * the answer gets the panel. The fold is a height animation done the one way
 * CSS can animate "auto": a one-row grid going from 1fr to 0fr, with the
 * buttons fading as they go. E-paper and "reduce motion" get the fold without
 * the movement.
 */
.companion-suggest { flex: none; display: flex; flex-direction: column; }
.companion-suggestions-clip {
  display: grid;
  grid-template-rows: 1fr;
  opacity: 1;
}
.companion-suggestions-clip > .companion-suggestions { min-height: 0; overflow: hidden; }
.companion-suggest[data-open="false"] .companion-suggestions-clip { grid-template-rows: 0fr; opacity: 0; }
.companion-suggest[data-open="false"] .companion-suggestions { padding-bottom: 0; }
@media (prefers-reduced-motion: no-preference) {
  .companion-suggestions-clip { transition: grid-template-rows 260ms var(--ease), opacity 200ms var(--ease); }
  .companion-suggestions { transition: padding 260ms var(--ease); }
}

.companion-suggest-toggle {
  align-self: flex-start;
  margin: 0 var(--sp-4) var(--sp-2);
  gap: var(--sp-2);
  color: var(--fg-dim);
}
.companion-suggest-toggle[hidden] { display: none; }
.companion-suggest[data-open="true"] .companion-suggest-toggle { margin-bottom: var(--sp-1); }
/* A chevron drawn with two borders rather than a character, so it is the
   same shape in every font. Points down when folded, up when open. */
.companion-chevron {
  width: 7px;
  height: 7px;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
}
.companion-suggest[data-open="true"] .companion-chevron { transform: translateY(2px) rotate(-135deg); }
@media (prefers-reduced-motion: no-preference) {
  .companion-chevron { transition: transform 200ms var(--ease); }
}

.companion-form {
  flex: none;
  display: flex;
  gap: var(--sp-2);
  padding: 0 var(--sp-4) var(--sp-2);
}
.companion-form .input { flex: 1; }

.companion-status {
  flex: none;
  padding: 0 var(--sp-4) var(--sp-3);
  font-size: var(--step--2);
}

/* ---------------------------------------------------------------- */
/* Author                                                            */
/* ---------------------------------------------------------------- */

.author-facts {
  flex: none;
  padding: var(--sp-4) var(--sp-4) 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-3);
  align-items: start;
}
.author-portrait {
  width: 96px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  display: block;
}
.author-facts dl {
  margin: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px var(--sp-2);
  font-size: var(--step--1);
}
.author-facts dt { color: var(--fg-dim); }
.author-facts dd { margin: 0; }
.author-credit {
  grid-column: 1 / -1;
  font-size: var(--step--2);
  color: var(--fg-dim);
}

/* ---------------------------------------------------------------- */
/* Sketches (styled inside the book's own page)                      */
/* ---------------------------------------------------------------- */

/* These rules are shipped into the iframe by skins.js along with the skin
   stylesheet; they are kept here so every sketch rule lives in one file. */
.lx-sketch {
  margin: 2em auto;
  max-width: 100%;
  text-align: center;
  break-inside: avoid;
  page-break-inside: avoid;
}
.lx-sketch-art svg {
  width: 100%;
  max-width: 460px;
  height: auto;
  color: currentColor;
}
.lx-sketch figcaption {
  font-size: 0.8em;
  opacity: 0.75;
  margin-top: 0.4em;
  text-align: center;
}

@media (max-width: 720px) {
  .companion-panel { width: 100vw; }
}

/* ------------------------------------------------------------------ */
/* Who is… picker                                                      */
/* ------------------------------------------------------------------ */

.who-names {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}

/* ------------------------------------------------------------------ */
/* Relationship map                                                    */
/* ------------------------------------------------------------------ */

/* The diagram is drawn at its natural size and this box scrolls to it, rather
   than the diagram being shrunk until it fits. A twenty-person cast squeezed
   into a dialog width gave names about two pixels tall. */
.rel-host {
  overflow: auto;
  /* Nearly the whole dialog. The modal panel caps itself at 80vh, so this is
     the diagram taking all the room that is going rather than a third of it. */
  height: 52vh;
  /* …but never so tall that the dialog has to scroll to show the bottom of it:
     on a laptop screen the chart's own lower edge and scrollbar sat below the
     fold, and on a phone the slider and the chart could not both be seen. */
  height: clamp(12rem, calc(80vh - 19rem), 60vh);
  margin: var(--sp-3) 0;
  padding: var(--sp-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
}
@media (max-width: 600px) {
  .rel-host { height: clamp(12rem, calc(80vh - 15rem), 60vh); }
}
.rel-host[data-tall] { height: 78vh; }

/* A list scrolls like a list, by touch as well — the diagram's drag-to-move
   had switched touch scrolling off for both. */
.rel-host[data-view="list"] { touch-action: auto; cursor: auto; }

/* What a tapped name says in full, and the note about someone the candlelight
   is hiding. */
.rel-caption { margin: var(--sp-2) 0 0; font-size: var(--step--1); }
.rel-find-hint { margin: var(--sp-2) 0 0; font-size: var(--step--1); }
.rel-find-hint:empty { display: none; }
.rel-legend > summary { cursor: pointer; font-size: var(--step--1); }
.rel-legend > p { margin-top: var(--sp-2); }
.rel-node { outline: none; }

/* It is a thing you move, so it says so before you touch it. `grab` on hover,
   `grabbing` while a drag is under way. */
.rel-host { cursor: grab; touch-action: none; }
.rel-host[data-panning] { cursor: grabbing; }
/* Dragging across a diagram should move it, not select the names in it. */
.rel-host[data-panning] * { user-select: none; }

.rel-svg { display: block; margin: 0 auto; max-width: none; }

/* Zoom controls sit above the diagram and stay put while it scrolls. */
.rel-tools {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.rel-zoom { min-width: 4ch; text-align: center; font-variant-numeric: tabular-nums; }
/* Pushes the view buttons to the far end, away from the zoom controls. */
.rel-tools .spacer { flex: 1 1 var(--sp-3); }
.rel-zoomgroup { display: flex; align-items: center; gap: var(--sp-2); }
/* Zooming a list of sentences would do nothing, so the zoom controls go. The
   search box stays: a list of thirty people is exactly as hard to scan as a
   chart of thirty people, and it was the one view that could not be searched. */
.rel-tools[data-diagram="false"] .rel-zoomgroup { display: none; }

/*
 * Candlelight: how far the light reaches across the cast.
 *
 * The one control named after the flame rather than after what it does, and it
 * earns it — a wider flame showing more of the room is exactly what this does
 * to the chart. This is the same documented exception the search highlight
 * takes: it marks the reach of the light, which is what amber is for.
 *
 * ## Why it is drawn rather than tinted
 *
 * The first version set `accent-color` and left the rest to the browser, which
 * produced the browser's own control in orange: a heavy round thumb taller than
 * the track it sits on, reading as a bead on a wire. What it should look like
 * is light spreading along a groove.
 *
 * So the track is drawn as a thin channel and the lit part as a warm gradient
 * running into it, brightest at the leading edge. The handle is small and pale
 * with a glow around it — the tip of the flame, not a bead. `--fill` is set
 * from the panel as the slider moves, because the fill has to stop exactly
 * where the handle is and CSS alone cannot know where that is.
 */
.rel-size { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--step--1); }
.rel-size-count { min-width: 6ch; font-variant-numeric: tabular-nums; }

/*
 * On a phone the toolbar is most of the screen.
 *
 * Six controls at their natural widths wrapped onto five lines, which left
 * about a thumb's width of actual chart. The search box and the candlelight
 * each take a full line and stretch to it — they are the two that benefit from
 * the room — and the gap that pushes the view buttons to the far end is
 * dropped, because on one line there is no far end to push them to.
 */
@media (max-width: 560px) {
  .rel-tools { gap: var(--sp-1); }
  .rel-tools .spacer { display: none; }
  .rel-find { width: 100%; }
  .rel-size { width: 100%; }
  .rel-size input[type="range"] { flex: 1 1 auto; width: auto; }
}

.rel-size input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 9.5rem;
  height: 16px;
  margin: 0;
  background: transparent;
  cursor: ew-resize;
}

/*
 * The channel, and the light along it.
 *
 * Two stops at `--fill` so the lit part ends cleanly instead of fading into the
 * unlit part, which at this size read as a smudge rather than as an edge.
 *
 * `--lit` is how hard the candle is burning, and it rises with the slider: a
 * dim ember when four people are showing, burning hard when the whole cast is.
 * Everything warm here is mixed through it, so the flame gets brighter, wider
 * and glowier together rather than only longer.
 */
.rel-size input[type="range"]::-webkit-slider-runnable-track {
  height: 5px;
  border-radius: 999px;
  background:
    linear-gradient(90deg,
      color-mix(in srgb, var(--flame) calc(var(--lit, 70%) * 0.6), transparent) 0%,
      color-mix(in srgb, var(--flame) var(--lit, 70%), transparent) calc(var(--fill, 30%) - 6%),
      color-mix(in srgb, var(--flame-core) var(--lit, 70%), var(--flame)) var(--fill, 30%),
      color-mix(in srgb, var(--border) 85%, transparent) var(--fill, 30%));
}

.rel-size input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 11px;
  height: 11px;
  margin-top: -3px;          /* centred on a 5px track */
  border-radius: 50%;
  border: 0;
  background: color-mix(in srgb, var(--flame-core) var(--lit, 70%), var(--flame));
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--flame) 70%, transparent),
    0 0 6px color-mix(in srgb, var(--flame) calc(var(--lit, 70%) * 0.8), transparent),
    0 0 13px color-mix(in srgb, var(--flame) calc(var(--lit, 70%) * 0.5), transparent);
}

/* Firefox draws the lit part for us, so it needs no gradient — only the same
   colours, or the control would be a different object on a different browser. */
.rel-size input[type="range"]::-moz-range-track {
  height: 5px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--border) 85%, transparent);
}
.rel-size input[type="range"]::-moz-range-progress {
  height: 5px;
  border-radius: 999px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--flame) calc(var(--lit, 70%) * 0.6), transparent),
    color-mix(in srgb, var(--flame-core) var(--lit, 70%), var(--flame)));
}
.rel-size input[type="range"]::-moz-range-thumb {
  width: 11px;
  height: 11px;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--flame-core) var(--lit, 70%), var(--flame));
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--flame) 70%, transparent),
    0 0 6px color-mix(in srgb, var(--flame) calc(var(--lit, 70%) * 0.8), transparent),
    0 0 13px color-mix(in srgb, var(--flame) calc(var(--lit, 70%) * 0.5), transparent);
}

.rel-size input[type="range"]:focus-visible { outline: none; }
.rel-size input[type="range"]:focus-visible::-webkit-slider-thumb {
  box-shadow:
    0 0 0 2px var(--bg-2),
    0 0 0 4px var(--accent),
    0 0 10px color-mix(in srgb, var(--flame) 70%, transparent);
}

/* Finding somebody on the chart. Narrow, because it sits in a row of buttons
   and a full-width search box would read as the main event. */
.rel-find { width: 12rem; min-height: 28px; font-size: var(--step--1); }
/* A name that matches nobody: said by the box itself rather than by a message
   that would have to appear and then go away again. */
.rel-find[data-miss] { border-color: var(--danger); }

/* A side panel's way back to where it was opened from — the menu, or the list
   of authors. Before the name, where a back control belongs, and the name then
   takes the room up to the panel's own buttons rather than floating centred. */
.panel-back { flex: none; margin-inline-end: var(--sp-1); }
.panel-back:not([hidden]) ~ h2 { flex: 1; min-width: 0; }

/* The side panels the menu opens are as wide as the menu itself, so stepping
   from one to the other does not move the edge. See `--drawer-w`. */
.author-panel, .practice-panel { width: var(--drawer-w); }

.rel-box {
  fill: var(--bg-2);
  stroke: var(--border);
  stroke-width: 1;
}
.rel-node-focus .rel-box { stroke: var(--accent); stroke-width: 2.5; }
.rel-node:focus-visible .rel-box { stroke: var(--accent); stroke-width: 2.5; }
.rel-node { cursor: default; }

.rel-name {
  fill: var(--fg);
  font: 600 16px var(--font-ui);
}

/*
 * Importance, made legible.
 *
 * Sitting near the middle is a real signal and a weak one — at a glance a
 * grid of identical boxes is a grid of identical boxes. Three weights, no
 * colour: the people the story turns on, the people around them, and the ones
 * named once. Colour is not available here; amber means the reading position.
 */
.rel-node[data-tier="major"] .rel-box { stroke-width: 2.2; }
.rel-node[data-tier="major"] .rel-name { font-size: 17px; font-weight: 700; }
.rel-node[data-tier="minor"] .rel-box { stroke: color-mix(in srgb, var(--border) 70%, transparent); }
.rel-node[data-tier="minor"] .rel-name { font-size: 15px; font-weight: 500; fill: var(--fg-dim); }

/*
 * The name being searched for.
 *
 * This is the documented exception to "amber is not a UI colour", and it earns
 * it by behaving like the flame rather than like chrome: one box at a time,
 * only while the reader is typing, gone the moment the box is cleared. A
 * neutral outline on a dark box among dark boxes was genuinely easy to miss,
 * and indistinguishable from the focus ring already on the chart.
 *
 * Kept quiet: a warm fill, a flame outline and a small glow. Not a fill in full
 * amber, which at this size reads as an error.
 *
 * Two things make these win over the tier rules directly above, and both are
 * needed. The class is written twice so the weight matches
 * `.rel-node[data-tier="minor"]`, and the block sits after that one so the tie
 * breaks this way. With only one of the two, a minor character kept their
 * dimmed name and faded outline while being searched for — so the person
 * hardest to find on the chart was the one the highlight did least for.
 */
.rel-node.rel-node-sought .rel-box {
  fill: color-mix(in srgb, var(--flame) 14%, var(--bg-2));
  stroke: var(--flame);
  stroke-width: 2.5;
}
.rel-node.rel-node-sought .rel-name { fill: var(--fg); font-weight: 700; }
.rel-node-sought { filter: drop-shadow(0 0 5px color-mix(in srgb, var(--flame) 55%, transparent)); }

/* The same mark in the list view, in the terms a list has. */
.rel-list > li[data-sought] {
  background: color-mix(in srgb, var(--flame) 12%, transparent);
  box-shadow: -0.55em 0 0 color-mix(in srgb, var(--flame) 12%, transparent),
    inset 2px 0 0 var(--flame);
  border-radius: 0 3px 3px 0;
}
.rel-list > li[data-sought] > strong { color: var(--flame); }

@media (prefers-reduced-motion: no-preference) {
  .rel-node-sought .rel-box { transition: fill var(--dur-2, 160ms) ease; }
}

/* The rule separating the diagram from the people who have no place in it. */
.rel-divider { stroke: var(--border); stroke-width: 1; stroke-dasharray: 3 5; }
.rel-divider-label {
  fill: var(--fg-dim);
  font: 13px var(--font-ui);
  letter-spacing: 0.02em;
}

/* Edge colour carries the kind, so the diagram needs no legend to be read.
   Dashed for the ones that are not family, because on a small screen colour
   alone is not enough to tell them apart. */
.rel-edge { stroke: var(--border); stroke-width: 2; }
.rel-edge-family { stroke: var(--accent); stroke-width: 2.5; }
.rel-edge-partner { stroke: var(--accent); stroke-dasharray: 6 4; }
.rel-edge-friend { stroke: var(--ok); stroke-dasharray: 3 4; }
.rel-edge-colleague { stroke: var(--fg-dim); stroke-dasharray: 3 4; }
.rel-edge-rival { stroke: var(--danger); stroke-dasharray: 7 4; }

/*
 * A link between two people the layout could not put near each other.
 *
 * Faint, and thinner. These are the lines that were making the chart look
 * busy — a dashed rule running the width of the diagram is read as clutter
 * long before it is read as a relationship. The relationship is still drawn,
 * because it is real and this is the only place the reader can see it; it
 * simply stops competing with the ones between neighbours. Hovering the line
 * still gives the whole claim.
 */
.rel-edge-far { opacity: 0.32; stroke-width: 1.4; }

/* E-paper: no faint lines, and the name searched for marked as firmly as the
   person whose chart it is — a pale amber tint is nothing on e-ink. */
[data-epaper] .rel-edge-far { opacity: 1; }
[data-epaper] .rel-node.rel-node-sought .rel-box { stroke: var(--fg); stroke-width: 3.5; stroke-dasharray: 5 3; }
[data-epaper] .rel-list > li[data-sought] { border-inline-start: 3px solid var(--fg); }
/* On the darkest themes the box outline all but vanished into the page. */
[data-theme="taper-dark"] .rel-box, [data-theme="ink"] .rel-box, [data-theme="midnight"] .rel-box {
  stroke: var(--fg-dim);
}

/* The arrowhead says which way the label is to be read: the line from Anna to
   Sam labelled "mother" points at Sam, and means Anna is Sam's mother. */
.rel-arrow { stroke: none; }
.rel-arrow-family, .rel-arrow-partner { fill: var(--accent); }
.rel-arrow-friend { fill: var(--ok); }
.rel-arrow-colleague { fill: var(--fg-dim); }
.rel-arrow-rival { fill: var(--danger); }

.rel-edge-label {
  fill: var(--fg-dim);
  font: 13px var(--font-ui);
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 4px;
}

.rel-list { margin: 0; padding-inline-start: 1.1em; }
.rel-list > li { margin-bottom: 0.5em; }
.rel-list ul { margin: 0.2em 0 0; padding-inline-start: 1.1em; }
.rel-list ul li { font-size: var(--step--1); color: var(--fg-dim); }


/* ------------------------------------------------------------------ */
/* Lexigraphy — the named places and things, as far as you have read   */
/* See lexigraphy.js. Shares nothing with the character tree above but */
/* the look of a window.                                                */
/* ------------------------------------------------------------------ */

.lx-body { display: flex; flex-direction: column; gap: var(--sp-3); min-height: 0; }
.lx-hint { font-size: var(--step--1); }
.lx-tools { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.lx-tools .search-field { flex: 1 1 200px; max-width: 320px; }
.lx-kinds { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.lx-kinds .btn[aria-pressed="true"] { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }

.lx-list {
  display: flex;
  flex-direction: column;
  max-height: min(56vh, 560px);
  overflow-y: auto;
  border-top: 1px solid var(--border);
  overscroll-behavior: contain;
}
.lx-group {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0;
  padding: var(--sp-2) var(--sp-2) var(--sp-1);
  background: var(--bg-2);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--fg-dim);
  border-bottom: 1px solid var(--border);
}
.lx-row { border-bottom: 1px solid var(--border); }
.lx-row-head {
  display: grid;
  grid-template-columns: minmax(8rem, 13rem) 1fr auto;
  gap: var(--sp-3);
  align-items: baseline;
  width: 100%;
  padding: var(--sp-2);
  border: 0;
  background: transparent;
  color: inherit;
  text-align: start;
  font: inherit;
}
.lx-row-head:hover { background: var(--bg-3); }
.lx-row-head[aria-expanded="true"] { background: var(--bg-3); }
.lx-name { font-weight: 600; overflow-wrap: anywhere; }
.lx-note { color: var(--fg-dim); font-size: var(--step--1); line-height: 1.4; }
.lx-page { font-size: var(--step--2); font-variant-numeric: tabular-nums; white-space: nowrap; }
.lx-detail {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: 0 var(--sp-2) var(--sp-3) calc(var(--sp-2) + var(--sp-3));
  font-size: var(--step--1);
}
.lx-detail p { margin: 0; }
.lx-detail .set-row { justify-content: flex-start; flex-wrap: wrap; }
.lx-detail .set-row .btn { flex: 0 0 auto; }
.lx-quote {
  margin: 0;
  padding: var(--sp-1) var(--sp-3);
  border-inline-start: 3px solid var(--border);
  font-style: italic;
  line-height: 1.5;
}

/* On a phone the note goes under the name rather than beside it. */
@media (max-width: 600px) {
  .lx-row-head { grid-template-columns: 1fr auto; }
  .lx-note { grid-column: 1 / -1; grid-row: 2; }
  .lx-list { max-height: 52vh; }
}
[data-epaper] .lx-kinds .btn[aria-pressed="true"] { outline: 2px solid var(--fg); }