/* The builder. Same shape language as the guide — light ground, white panels,
   hairline rules, one blue action — so the tool and the thing it builds do not
   look like two different products. It stays distinguishable by layout rather
   than by palette: a sidebar and a dense editor could not be mistaken for the
   single-column guide even at a glance. */
:root {
  /* Tells the browser this page is light, so its own widgets — the date picker,
     the select menu, scrollbars, autofill — are drawn light too. Without it they
     follow the operating system and a person in dark mode gets black controls on
     a white form. */
  color-scheme: light;

  --bg: #f4f5f7;
  --panel: #ffffff;
  --raised: #f7f8f9;
  --ink: #102542;
  --muted: #69748a;
  --line: #e5e7eb;
  --field-line: #d9dde3;
  --accent: #376af2;
  --accent-fill: #eef3fe;
  --accent-edge: #c3d3fb;
  --accent-ink: #1c3f8f;
  --accent-ring: rgba(55, 106, 242, 0.35);
  --danger: #d33a34;
  --danger-fill: #fdf1f0;
  --danger-edge: #f4d3d0;
  --warn-fill: #fff8ec;
  --warn-edge: #f0e0c2;
  --warn-ink: #6f4710;
  --ok-fill: #e7f5ee;
  --ok-edge: #cbe6d7;
  --ok-ink: #0e5334;
  --radius: 12px;
  --radius-control: 10px;
  --hairline: 1px solid var(--line);
  --lift: 0 1px 2px rgba(16, 37, 66, 0.04);
}

* { box-sizing: border-box; }

/* The hidden attribute is implemented by the UA stylesheet as display: none, so
   ANY author rule that sets display beats it. Several elements here are both
   toggled with .hidden and given display: flex, which silently made them
   permanently visible. This is the standard baseline for that trap. */
[hidden] { display: none !important; }

/* A closed <dialog> is hidden by the UA stylesheet, and ANY author rule that
   sets display beats it — the same trap as [hidden] above, and it has now been
   sprung. .person-dialog sets display:flex so its head can stay put while its
   body scrolls, which made it permanently visible at the foot of every page:
   an empty white box with a Send SMS button in it, on screen since v1.64.0,
   because every test of it had it open.
   
   Stated once, for every dialog there is and every dialog there will be, rather
   than left to be remembered per dialog — three of the four did remember, which
   is exactly the hit rate that makes a baseline worth having. */
dialog:not([open]) { display: none !important; }



body {
  margin: 0;
  min-height: 100dvh;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* ── Sign-in, and being turned away ───────────────────────────────────────
   
   The two pages reachable without a session. They share a shell, and it uses
   none of the builder's class names.
   
   That last part is the whole lesson here: this container was called `.card`,
   which was unambiguous until the flow tree introduced a `.card` of its own — a
   flex row with a tree node drawn in ::before. From that commit the sign-in page
   quietly became three columns with a dot floating beside them, and stayed that
   way, because nothing that renders it is ever looked at by anybody who is
   already signed in. */

body.centred {
  display: grid;
  place-items: center;
  /* The extra room at the bottom is the badge's. It is positioned against the
     viewport rather than the flow, so on a short window — a laptop with the
     browser half height — the card would otherwise be centred straight through
     it. Padding keeps the two apart without either having to know about the
     other's size. */
  padding: 24px 24px 56px;
  min-height: 100dvh;
}

/* The version, without a line of script on a page that loads none. attr() reads
   the element's own attribute and the server stamps the version onto <body>, so
   this is the one element that can say it. Guarded on the attribute so a page
   served some other way shows nothing rather than a dangling "ecStart". */
body.centred[data-version]::after {
  content: "ecStart " attr(data-version);
  position: fixed;
  left: 0;
  right: 0;
  bottom: 18px;
  text-align: center;
  font-size: 11.5px;
  letter-spacing: 0.03em;
  color: var(--muted);
}

.signin {
  width: 100%;
  max-width: 380px;
  /* Grid items take their content's minimum size unless told otherwise, and
     that minimum beats max-width — which is the other half of why this page was
     830px wide. */
  min-width: 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 34px 32px 28px;
  /* Two shadows: a hairline lift so the edge reads on white, and a wide soft
     one so the card sits above the ground rather than being drawn on it. */
  box-shadow: 0 1px 2px rgba(16, 37, 66, 0.05), 0 14px 34px -14px rgba(16, 37, 66, 0.18);
  text-align: center;
}

/* The same two-weight wordmark the builder's top bar uses, so the page you sign
   in on and the page you land on are recognisably one product. */
.signin-brand {
  margin: 0 0 22px;
  font-size: 13.5px;
  letter-spacing: 0.01em;
  color: var(--muted);
}
.signin-brand strong { color: var(--ink); font-weight: 700; }

.signin h1 {
  margin: 0 0 8px;
  font-size: 22px;
  letter-spacing: -0.01em;
}

.signin-lede {
  margin: 0 0 24px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.55;
  /* Balanced, because this is three lines of prose in a narrow column and the
     default leaves one word alone on the last one. */
  text-wrap: balance;
}

/* Microsoft's own treatment: their mark, their colours, on a plain ground. It
   is not styled as this application's primary button on purpose — the button
   does not submit anything here, it hands off to somebody else, and looking
   like the thing it hands off to is the honest signal. Full width so it still
   reads as the action despite the quiet colours. */
.signin-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 12px 16px;
  border: 1px solid var(--field-line);
  border-radius: var(--radius-control);
  background: #ffffff;
  color: var(--ink);
  font-size: 14.5px;
  font-weight: 600;
  text-decoration: none;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}
.signin-button:hover { background: var(--raised); border-color: var(--muted); }
.signin-button:focus-visible {
  outline: 3px solid var(--accent-ring);
  outline-offset: 2px;
  border-color: var(--accent);
}
.signin-button svg { flex: 0 0 auto; }

.signin-note {
  margin: 20px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
}

/* Turned away. The same card, with the reason given the weight it deserves —
   it is nearly always "your account is not in the admin group", which is a
   fact to act on rather than an error to apologise for. */
.signin-reason {
  margin: 0 0 22px;
  padding: 12px 14px;
  border-radius: var(--radius-control);
  background: var(--danger-fill);
  border: 1px solid var(--danger-edge);
  color: var(--danger);
  font-size: 13.5px;
  line-height: 1.5;
  text-align: left;
}

/* The retry, which is a second attempt rather than the main event. */
.signin-again {
  display: inline-block;
  margin-top: 2px;
  color: var(--accent);
  font-size: 13.5px;
  font-weight: 600;
  text-decoration: none;
}
.signin-again:hover { text-decoration: underline; }

/* ── Top bar ──────────────────────────────────────────────────────────────── */

.topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 18px;
  height: 54px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 5;
}
.topbar-brand { font-weight: 700; letter-spacing: 0.01em; }
.topbar-brand span { font-weight: 400; color: var(--muted); }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 16px; font-size: 13.5px; }
.who { color: var(--muted); }
.link { color: var(--accent); text-decoration: none; }
.link:hover { text-decoration: underline; }

/* ── Layout ───────────────────────────────────────────────────────────────── */

.layout {
  display: grid;
  /* auto rather than a fixed track, so the sidebar's own width decides the
     column and dragging its edge actually moves the boundary. */
  grid-template-columns: auto 1fr;
  min-height: calc(100dvh - 54px);
}

.tree {
  width: 296px;
  /* Drag the right edge. Titles are as long as somebody wrote them, and at a
     fixed width the longest ones can only be guessed at. */
  resize: horizontal;
  min-width: 220px;
  max-width: 640px;
  border-right: 1px solid var(--line);
  background: var(--panel);
  padding: 16px 12px;
  overflow-y: auto;
  /* Never sideways. A horizontal scrollbar here is a symptom of a row failing to
     truncate rather than something to scroll. */
  overflow-x: hidden;
  max-height: calc(100dvh - 54px);
  position: sticky;
  top: 54px;
}
/* The heading and whatever buttons belong to it.
   
   Wraps, because the panel is resizable from 220px and this row went from two
   buttons to three: at the default width the third pushed the others off the
   edge and broke "Eksportér alt" across two lines. A row of controls in a panel
   whose width is a user preference cannot assume it fits on one line. */
.tree-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0 4px 12px;
}
.tree-head h2 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin: 0;
  /* Takes the row to itself when the buttons need their own line, and shares it
     when they fit. */
  flex: 1 1 auto;
}
/* A label broken mid-phrase reads as a rendering fault; the row wraps instead. */
.tree-head button { white-space: nowrap; }

/* Three buttons never share a line with the heading at any width this panel
   allows, and one of them wrapping alone above the other two looks like a
   mistake rather than a layout. So the heading takes its own row and the three
   sit together beneath it. Two or fewer still share the line, which is what the
   notices head wants. */
.tree-head:has(.tree-head-group) h2 { flex: 1 1 100%; }

/* Export and import move as one. Wrapping is fine; wrapping *between* them is
   what made the row look accidental. */
.tree-head-group {
  display: flex;
  gap: 8px;
  flex: 0 0 auto;
}
.tree-hint { font-size: 12px; color: var(--muted); margin: 14px 4px 0; }
.tree-empty { font-size: 13.5px; color: var(--muted); margin: 4px; }

.tree-group { margin-bottom: 10px; border-radius: var(--radius); }
.tree-group.is-dragging, .tree-step.is-dragging { opacity: 0.4; }

/* Rows used to sit flush against each other, so two adjacent highlighted
   backgrounds — a hover next to a selection — read as one overlapping the other.
   A couple of pixels is enough to separate them. */
.tree-steps { display: grid; gap: 2px; margin-top: 2px; }

/* The chapter head is a row, not a button, because it holds two controls: the
   label selects the chapter, the + adds a step to it. */
.tree-chapter-row { display: flex; align-items: center; gap: 2px; }
.tree-chapter-row .tree-chapter { flex: 1; min-width: 0; }

button.tree-add {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  padding: 0;
  font-size: 17px;
  font-weight: 400;
  line-height: 1;
  background: transparent;
  border-color: transparent;
  color: var(--muted);
  border-radius: 7px;
}
button.tree-add:hover:not(:disabled) {
  background: var(--accent-fill);
  color: var(--accent);
  filter: none;
}

.tree-chapter, .tree-step {
  width: 100%;
  /* A flex container will not shrink below its content without this, so a long
     title pushes the row wider than the sidebar instead of truncating. */
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 7px;
  text-align: left;
  font: inherit;
  border: 0;
  background: transparent;
  color: var(--ink);
  border-radius: var(--radius-control);
  padding: 8px 9px;
  cursor: pointer;
}
.tree-chapter { font-weight: 600; font-size: 14px; }
.tree-step { font-size: 13.5px; color: var(--muted); padding-left: 22px; }
.tree-chapter:hover, .tree-step:hover { background: var(--raised); }
.tree-chapter.is-active, .tree-step.is-active {
  background: var(--accent-fill);
  color: var(--accent-ink);
  box-shadow: inset 2px 0 0 var(--accent);
}
/* min-width: 0 is what makes the ellipsis work at all. A flex item defaults to
   min-width: auto — its content size — so overflow: hidden never gets a chance to
   apply, and the row overflows the sidebar instead of being cut short. */
.tree-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.grip { color: var(--muted); font-size: 10px; cursor: grab; letter-spacing: -2px; }

.chip {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--raised);
  color: var(--muted);
  font-weight: 600;
}
.chip-key { background: var(--accent-fill); color: var(--accent-ink); }
.chip-locked { background: var(--warn-fill); color: var(--warn-ink); }
/* One character wide, so a narrow sidebar spends its width on the title. */
.chip-glyph {
  padding: 2px 5px;
  font-size: 10px;
  line-height: 1.4;
  letter-spacing: 0;
}

/* ── Editor ───────────────────────────────────────────────────────────────── */

/* No overflow of its own. The layout never constrains this column's height, so
   the page is what scrolls and `overflow-y: auto` only ever made this a scroll
   container that does not scroll — which is enough to capture a sticky child
   and carry it off the top of the screen along with everything else. */
.editor { padding: 26px; }

.empty { max-width: 520px; color: var(--muted); }
.empty h1 { font-size: 19px; color: var(--ink); margin: 0 0 10px; }
.empty strong { color: var(--accent); font-weight: 600; }

.pane { max-width: 660px; }

/* 660px is a measure for reading and editing prose — a step's text, a chapter's
   description. Live and the Log are neither: they are lists of people and
   events, where a column that has to truncate a name is a column that has to be
   hovered to be read. They get the whole column. */
.pane-wide { max-width: none; }
/* Sticky, and the save button lives in it now. A form long enough to need
   scrolling is a form whose save button must not be at the far end of the
   scroll — on a step with an image, formatting help and a branch selector, the
   button was three screens below the field being typed into. */
.pane-head {
  position: sticky;
  /* Below the topbar, which is sticky at 54px and would otherwise cover this.
     The dialog overrides it back to 0 — there is no topbar inside a dialog. */
  top: 54px;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 16px;
  padding: 10px 0;
  /* The page's own colour, not a card's. Sticky needs something opaque for the
     form to scroll behind, and --panel made a white band 660px wide floating on
     a grey page — a card belonging to nothing. In --bg it is simply the top of
     the column, and the hairline is what separates it. */
  background: var(--bg);
  border-bottom: var(--hairline);
}
.pane-head h1 { font-size: 19px; margin: 0; flex: 1; }
.pane-actions { display: flex; align-items: center; gap: 12px; }

.pane-note {
  margin: 0 0 18px;
  padding: 11px 13px;
  border-radius: var(--radius-control);
  background: var(--warn-fill);
  border: 1px solid var(--warn-edge);
  color: var(--warn-ink);
  font-size: 13.5px;
}
.pane-note.tap-note {
  background: var(--accent-fill);
  border-color: var(--accent-edge);
  color: var(--accent-ink);
}

/* The settings page is not a pane — it took the window — but its fields are the
   same fields and must not be styled by accident of which container they landed
   in. Listed rather than merged into one class, because the two containers
   genuinely differ in everything else. */
.pane label,
.settings-page label {
  display: block;
  margin-bottom: 16px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Every text-ish control, listed by exclusion rather than by enumeration.
   type=date was the one that got away: it matched none of the old selectors, so
   it fell through to the browser's native widget — which follows the OS colour
   scheme, not the page, and rendered as a dark box on a white form. */
input:not([type=color]):not([type=range]):not([type=file]):not([type=checkbox]):not([type=radio]):not([type=submit]),
textarea,
select {
  display: block;
  width: 100%;
  margin-top: 7px;
  padding: 10px 12px;
  font: 15px/1.5 inherit;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  color: var(--ink);
  /* Fields are white on a white panel, separated by their border rather than by
     a fill — the same as the guide. A grey fill on a light panel reads as a
     disabled control. */
  background: #ffffff;
  border: 1px solid var(--field-line);
  border-radius: var(--radius-control);
}
textarea { resize: vertical; min-height: 88px; font-family: inherit; }

/* A date field is a control you press, so it should say so. The indicator is a
   UA-drawn glyph; opacity is the only honest lever on it. */
input[type=date] { font-variant-numeric: tabular-nums; }
input[type=date]::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: 0.55;
  border-radius: 4px;
  padding: 2px;
}
input[type=date]::-webkit-calendar-picker-indicator:hover { opacity: 1; }
input:focus-visible, textarea:focus-visible, select:focus-visible {
  outline: 3px solid var(--accent-ring);
  outline-offset: 1px;
  border-color: var(--accent);
}

/* A hint sits under whatever it explains.
   The negative top margin only makes sense directly after a label, which carries
   16px of its own bottom margin — that is what it cancels. Applied everywhere, it
   pulled the hint up over the control above it whenever there was no such margin
   to cancel, which is what happens inside a fieldset. */
.field-hint {
  margin: 6px 0 18px;
  font-size: 12.5px;
  color: var(--muted);
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
}
/* A stacked label owns the field under it — the field is *inside* the label —
   and carries 16px of bottom margin, which is what this cancels so the hint sits
   against the control it explains.
   
   Written as "a label that wraps a field" rather than as a list of labels it
   must not apply to. The exclusion list version has now been wrong twice: first
   for .inline-label, a row with a slider in it, then for .switch, a row with a
   checkbox — each time pulling the hint 8px up onto the control above it. A
   third class would have been wrong a third time.
   
   :has() is doing the work: match only where the label genuinely contains a
   stacked field. Where it is unsupported the hint simply keeps its normal
   margin, which is a slightly loose layout rather than an overlapping one. */
label:has(> input[type=text], > input[type=url], > input:not([type]), > select, > textarea)
  + .field-hint,
/* The wrapper that joins a strip to its textarea hides the textarea from a
   direct-child test, so the second selector has to reach through it. */
label:has(> .formatted-field > textarea) + .field-hint {
  margin-top: -8px;
}

/* Nothing follows it inside the box, so its bottom margin is only padding twice. */
.media-field > .field-hint:last-child { margin-bottom: 0; }

.switch {
  display: flex !important;
  align-items: center;
  gap: 7px;
  margin: 0 !important;
  font-size: 13px;
  cursor: pointer;
}
.switch input { width: auto; margin: 0; }

/* Fieldsets are the panels of this page: white, hairline, gentle corner — the
   card treatment the guide uses, so the two read as one product. */
.media-field {
  background: var(--panel);
  box-shadow: var(--lift);
  border: var(--hairline);
  border-radius: var(--radius);
  padding: 16px;
  margin: 0 0 18px;
}
.media-field legend {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  padding: 0 6px;
}
.media-current img {
  max-width: 100%;
  max-height: 190px;
  border-radius: var(--radius-control);
  border: 1px solid var(--line);
  display: block;
  margin-bottom: 10px;
}
.media-controls { display: flex; gap: 8px; }

.pane-foot {
  display: flex;
  gap: 10px;
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}

/* ── Buttons ──────────────────────────────────────────────────────────────── */

button {
  font: inherit;
  font-weight: 600;
  padding: 11px 18px;
  border-radius: var(--radius-control);
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #ffffff;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
/* Darken rather than brighten: on a light ground brightening a solid colour
   moves it toward the page and the button appears to recede. */
button:hover:not(:disabled) { filter: brightness(0.94); }
button:focus-visible { outline: 3px solid var(--accent-ring); outline-offset: 2px; }
button:disabled { opacity: 0.55; cursor: progress; }

button.secondary {
  background: var(--panel);
  border-color: var(--field-line);
  color: var(--ink);
  font-weight: 600;
}
button.secondary:hover:not(:disabled) {
  background: var(--raised);
  border-color: var(--muted);
  filter: none;
}

button.danger {
  background: var(--panel);
  border-color: var(--danger-edge);
  color: var(--danger);
  font-weight: 600;
}
button.danger:hover:not(:disabled) { background: var(--danger-fill); filter: none; }

button.small { padding: 6px 11px; font-size: 13px; }

/* ── Toast ────────────────────────────────────────────────────────────────── */

/* Top, not bottom. It used to sit 22px off the bottom edge — which is exactly
   where the pane footer's buttons are, so "Gemt" landed on top of Gem, Nulstil
   and Se guiden and, being a solid element, swallowed clicks on them for the
   two and a half seconds it was up.
   
   Two fixes, because either alone is a near miss: it moved out of the footer's
   way, and it cannot take a pointer event wherever it happens to land. */
.toast {
  position: fixed;
  top: 70px;
  /* Right, not centre. Centre-top is over the pane header — the publish toggle
     and Slet — and centre-bottom was over the footer. The column to the right of
     the editor is the only part of this layout that never holds a control. */
  right: 22px;
  pointer-events: none;
  background: var(--ink);
  border: 1px solid var(--ink);
  color: #ffffff;
  padding: 11px 18px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 550;
  box-shadow: 0 10px 26px rgba(16, 37, 66, 0.18);
  z-index: 20;
  max-width: calc(100vw - 32px);
}
.toast.is-error { background: var(--danger); border-color: var(--danger); color: #ffffff; }

@media (max-width: 860px) {
  .layout { grid-template-columns: 1fr; }
  .tree { position: static; max-height: none; border-right: 0; border-bottom: 1px solid var(--line); }
  .editor { padding: 18px; }
}

/* Variable chips. The useful variable names cannot be guessed, and typing
   {{firstName}} correctly from memory is a coin flip. */
.vars { margin: -6px 0 18px; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.vars-label { font-size: 12.5px; color: var(--muted); }
.vars-list { display: flex; gap: 5px; flex-wrap: wrap; }

button.varchip {
  width: auto;
  padding: 3px 8px;
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 500;
  background: var(--raised);
  border-color: var(--line);
  color: var(--accent);
  border-radius: 6px;
}
button.varchip:hover { background: var(--accent-fill); filter: none; }

.tree-head-second {
  margin-top: 22px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

/* An expired notice is still listed — it is the thing you would come here to fix
   or reuse — but it must not look live. */
.tree-notice.is-expired .tree-label,
.tree-notice.is-draft .tree-label { color: var(--muted); }
.chip-expired { background: var(--warn-fill); color: var(--warn-ink); }

/* A control that belongs to the fieldset above it rather than standing alone. */
.inline-label {
  display: flex !important;
  align-items: center;
  gap: 10px;
  margin: 12px 0 0 !important;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}
.inline-label select { margin-top: 0; width: auto; flex: 1; }

/* A swatch beside the colour picker, so the choice is visible without saving. */
.tone-preview {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 4px;
  margin-left: 8px;
  vertical-align: -2px;
  border: 1px solid rgba(16, 37, 66, 0.16);
}

/* What is actually happening with this notice, in one line. Deriving it from a
   toggle, a date and a location is three deductions too many — and a notice
   nobody can see looks identical to one everybody can. */
.visibility {
  margin: 0 0 18px;
  padding: 10px 13px;
  border-radius: var(--radius-control);
  font-size: 13.5px;
  font-weight: 500;
}
.visibility.is-live { background: var(--ok-fill); border: 1px solid var(--ok-edge); color: var(--ok-ink); }
.visibility.is-hidden { background: var(--warn-fill); border: 1px solid var(--warn-edge); color: var(--warn-ink); }

.version {
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--raised);
  color: var(--muted);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
}

/* ── Appearance settings ──────────────────────────────────────────────────── */

.color-row { display: flex; align-items: center; gap: 10px; }
.color-row input[type=color] {
  flex: 0 0 auto;
  width: 46px;
  height: 40px;
  padding: 2px;
  margin: 0;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  cursor: pointer;
}
.color-row input[name=backgroundColor] {
  margin-top: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* ── The palette ──────────────────────────────────────────────────────────── */

.palette-grid {
  display: grid;
  gap: 10px;
  margin-bottom: 14px;
}

.palette-row {
  display: grid;
  grid-template-columns: 1fr 46px 108px 74px;
  align-items: center;
  gap: 10px;
}

.palette-row > .palette-name {
  min-width: 0;
  font-size: 13.5px;
  font-weight: 550;
}
.palette-row .palette-name em {
  display: block;
  font-style: normal;
  font-weight: 400;
  font-size: 12px;
  color: var(--muted);
}

.palette-row input[type=color] {
  width: 46px;
  height: 36px;
  padding: 2px;
  margin: 0;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  cursor: pointer;
}

.palette-row input[type=text] {
  margin: 0;
  min-height: 36px;
  padding: 6px 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
}

/* The contrast ratio of this colour against the surface it lands on. Shown
   because "is this readable" is the only question that matters here and it is
   not one anybody can answer by looking at two hex codes. */
.palette-ratio {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  text-align: right;
  white-space: nowrap;
}
.palette-ratio[data-low="1"] { color: var(--danger); font-weight: 600; }
.palette-ratio[data-auto="1"]::after {
  content: " auto";
  font-size: 10.5px;
  letter-spacing: 0.04em;
  opacity: 0.65;
}

@media (max-width: 900px) {
  .palette-row { grid-template-columns: 1fr 46px 96px; }
  .palette-ratio { grid-column: 1 / -1; text-align: left; }
}

/* ── Live preview ─────────────────────────────────────────────────────────── */
/* Every colour below comes from a custom property set on .palette-preview by
   palette.js — the same call the guide makes. Nothing here picks a colour. */

.palette-preview {
  padding: 18px 16px 20px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--bg);
}

.pp-masthead { margin-bottom: 12px; }
.pp-brand {
  display: block;
  font-size: 16px;
  font-weight: 700;
  color: var(--on-bg);
}
.pp-tagline {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  color: var(--on-bg-muted);
}

.pp-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 14px 15px 16px;
}

.pp-head { margin: 0 0 8px; font-size: 16px; color: var(--head); }
.pp-prose { margin: 0 0 6px; font-size: 13.5px; color: var(--prose); }
.pp-muted { margin: 0 0 10px; font-size: 12.5px; color: var(--muted); }

.pp-var {
  display: inline-block;
  padding: 2px 7px;
  border-radius: 5px;
  background: var(--accent-fill);
  border: 1px solid var(--accent-edge);
  color: var(--accent-ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
}

.pp-buttons { display: flex; gap: 8px; margin-top: 14px; }
.pp-btn,
.pp-btn-secondary {
  flex: 1;
  text-align: center;
  padding: 9px 12px;
  border-radius: var(--radius-control);
  font-size: 13.5px;
  font-weight: 600;
}
.pp-btn { background: var(--btn-bg); border: 1px solid var(--btn-bg); color: var(--btn-ink); }
.pp-btn-secondary { background: transparent; border: 1px solid var(--line); color: var(--muted); }

.bg-slot { margin-bottom: 18px; }
.bg-slot-label {
  display: block;
  margin-bottom: 7px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.bg-slot-label em { font-weight: 400; font-style: normal; color: var(--muted); }

/* Fixed aspect boxes, so the two previews show the shape each image is for —
   a wide image looks fine until you see it beside a portrait slot. */
.bg-preview {
  border: 1px dashed var(--line);
  border-radius: var(--radius-control);
  background: var(--raised) center / cover no-repeat;
  margin-bottom: 8px;
}
.bg-slot:nth-of-type(1) .bg-preview { width: 96px; aspect-ratio: 9 / 16; }
.bg-slot:nth-of-type(2) .bg-preview { width: 220px; aspect-ratio: 16 / 9; }

/* The logo slot. A wide, short box rather than the background slots' aspect
   frames — a logo has no fixed shape, and the checkerboard is there because most
   of these are transparent PNGs and a transparent mark on a flat panel is
   indistinguishable from a missing one. */
.logo-slot { margin-bottom: 6px; }
.logo-preview {
  display: grid;
  place-items: center;
  width: 240px;
  height: 76px;
  padding: 8px;
  margin-bottom: 8px;
  border: 1px dashed var(--line);
  border-radius: var(--radius-control);
  /* Checkerboard, because most logos are transparent PNGs and a transparent
     mark on a flat panel looks exactly like a missing one. */
  background: repeating-conic-gradient(#eceef1 0% 25%, #f8f9fa 0% 50%) 0 0 / 16px 16px;
}
.logo-preview img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

.pp-logo {
  display: block;
  max-width: min(200px, 55%);
  width: auto;
  height: auto;
  object-fit: contain;
  object-position: left center;
  margin-bottom: 8px;
}

.inline-label input[type=range] { flex: 1; margin: 0; }
.inline-label output {
  flex: 0 0 auto;
  min-width: 44px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  color: var(--muted);
}
/* Too little scrim to guarantee white text over an unknown photograph. */
.inline-label output[data-low="1"] { color: var(--danger); font-weight: 600; }

/* The native resize handle is nearly invisible on a dark panel, so say it exists. */
.tree-resize-hint {
  margin: 10px 4px 0;
  font-size: 11.5px;
  color: var(--muted);
}

/* ── Formatting toolbar ───────────────────────────────────────────────────── */

/* One strip, not five buttons.
   
   Each button used to carry its own border and its own rounded top, which at
   32px apiece made a row of five little tabs above the field — five outlines
   competing with the one that matters. The strip has the border now and the
   buttons live inside it, divided by hairlines, so it reads as the top edge of
   the field it belongs to. */
.formatbar {
  display: flex;
  align-items: stretch;
  gap: 0;
  width: max-content;
  max-width: 100%;
  margin: 7px 0 0;
  background: var(--raised);
  border: 1px solid var(--field-line);
  border-bottom: 0;
  border-radius: var(--radius-control) var(--radius-control) 0 0;
  overflow: hidden;
}

button.fmt {
  width: 34px;
  height: 30px;
  padding: 0;
  font-size: 13.5px;
  font-weight: 700;
  line-height: 1;
  background: transparent;
  border: 0;
  border-radius: 0;
  color: var(--muted);
}
button.fmt + button.fmt { border-left: 1px solid var(--field-line); }
button.fmt:hover:not(:disabled) {
  background: var(--panel);
  color: var(--ink);
  filter: none;
}
button.fmt:active:not(:disabled) { background: var(--accent-fill); color: var(--accent-ink); }
/* Inset, because an outline outside a button flush against the field's top edge
   is drawn half underneath it. */
button.fmt:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--accent-ring);
  color: var(--accent-ink);
}

/* Each mark shown in the thing it does, so the row is read rather than
   deciphered — F is only bold because it is set bold. */
button.fmt-b { font-weight: 800; }
button.fmt-i { font-style: italic; font-family: Georgia, "Times New Roman", serif; }
button.fmt-u { text-decoration: underline; text-underline-offset: 2px; }
button.fmt-h { font-size: 12px; letter-spacing: 0.02em; }
button.fmt-ul { font-size: 17px; }

/* The field joins the strip: square the corner they share and drop the gap.
   Two classes deep because the field it joins may carry its own margin from a
   more specific rule — .settings-page .faq-answer-field did, and the 9px it set
   left the strip floating a hairline above the field it belongs to. */
.settings-page .formatbar + textarea,
.settings-page .formatbar + input,
.formatbar + textarea,
.formatbar + input { border-top-left-radius: 0; margin-top: 0; }

/* The strip and the field it belongs to, as one thing.
   
   They share an edge, so they have to share the focus ring: drawn around the
   field alone it ran between the two and made them look like they had come
   apart, with a blue line under a strip that is supposed to be the field's own
   top edge. */
.formatted-field {
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-control);
}
/* Full width, so the strip really is the field's top edge rather than a tab
   sitting on it. Narrower, the field had to keep its own top border to close
   the gap either side of the strip — and that border ran between the two as a
   line, which is the thing that looked wrong. */
.formatted-field > .formatbar {
  align-self: stretch;
  width: auto;
  border-radius: var(--radius-control) var(--radius-control) 0 0;
}
/* The strip supplies it. */
.formatted-field > textarea,
.formatted-field > input {
  border-top: 0;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.formatted-field:focus-within {
  outline: 3px solid var(--accent-ring);
  outline-offset: 1px;
}
/* The border follows the ring, on both halves, so the outline of the pair is
   one colour rather than an accent box round a grey one. */
.formatted-field:focus-within > .formatbar,
.formatted-field:focus-within > textarea,
.formatted-field:focus-within > input { border-color: var(--accent); }

/* And the field gives its own up, having lent it to the wrapper. */
.formatted-field > textarea:focus-visible,
.formatted-field > input:focus-visible { outline: none; }

/* Inside a row, the strip is not the top edge of anything — it sits beside the
   field as a small group of its own. So it closes: all four borders, all four
   corners, and none of the negative-space tricks that make sense above a
   textarea. Without this it read as a tab with its bottom edge missing. */
.splash-row-top > .formatbar {
  margin: 0;
  flex: none;
  align-self: center;
  border: 1px solid var(--field-line);
  border-radius: var(--radius-control);
}
.splash-row-top > .formatbar button.fmt { width: 28px; height: 26px; font-size: 12.5px; }

/* ── Saved, on the button ─────────────────────────────────────────────────── */

button.is-saved {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  background: var(--ok-ink);
  border-color: var(--ok-ink);
  color: #ffffff;
  opacity: 1;
  cursor: default;
}

.tick {
  width: 17px;
  height: 17px;
  flex: none;
}
.tick path {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* The line is drawn rather than faded in: a tick that draws itself reads as
     something having just happened, where one that appears reads as something
     that was always there. 30 is a little longer than the path, so the dash
     clears it completely. */
  stroke-dasharray: 30;
  stroke-dashoffset: 30;
  animation: tick-draw 0.32s ease-out 0.04s forwards;
}

@keyframes tick-draw {
  to { stroke-dashoffset: 0; }
}

button.is-saved span {
  animation: saved-in 0.25s ease-out 0.14s both;
}

@keyframes saved-in {
  from { opacity: 0; transform: translateX(-3px); }
  to { opacity: 1; transform: none; }
}

/* Motion is the whole point of this control, so with reduced motion it still
   changes — it just arrives rather than draws. */
@media (prefers-reduced-motion: reduce) {
  .tick path { animation: none; stroke-dashoffset: 0; }
  button.is-saved span { animation: none; }
}

/* ── Asking ───────────────────────────────────────────────────────────────── */

.confirm {
  width: min(420px, calc(100vw - 32px));
  padding: 24px 24px 20px;
  border: var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 24px 60px rgba(16, 37, 66, 0.18);
  background: var(--panel);
  color: var(--ink);
}
.confirm::backdrop { background: rgba(16, 37, 66, 0.42); }

.confirm h2 { margin: 0 0 8px; font-size: 18px; }
.confirm p { margin: 0 0 20px; color: var(--muted); font-size: 14.5px; }

.confirm-foot { display: flex; justify-content: flex-end; gap: 10px; }

/* Deleting gets a solid red button, not the quiet outlined one used in a
   toolbar. In a toolbar the outline says "careful"; here the question has
   already been asked and the answer needs to look like what it does. */
.confirm.is-danger [data-confirm-ok] {
  background: var(--danger);
  border-color: var(--danger);
  color: #ffffff;
}

/* ── A question's answers ─────────────────────────────────────────────────── */

.option-row {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 8px;
}
.option-row input { margin: 0; }
.option-row button { flex: 0 0 auto; }

/* A step that belongs to one answer, marked in the tree — otherwise the only
   way to see the shape of a branch is to open every step in turn. */
.chip-branch { background: var(--warn-fill); color: var(--warn-ink); }
.chip-question { background: var(--accent-fill); color: var(--accent-ink); }

/* ── Log ──────────────────────────────────────────────────────────────────── */

.log-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-bottom: 14px;
}

/* Segmented, because the three are one choice. Loose buttons would read as
   three independent filters that could be combined. */
.log-scopes {
  display: inline-flex;
  border: 1px solid var(--field-line);
  border-radius: var(--radius-control);
  overflow: hidden;
  background: var(--panel);
}

.log-scope {
  appearance: none;
  border: 0;
  border-right: 1px solid var(--field-line);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  padding: 7px 14px;
  cursor: pointer;
}

.log-scope:last-child { border-right: 0; }
.log-scope:hover { background: var(--raised); color: var(--ink); }

.log-scope.is-on {
  background: var(--accent-fill);
  color: var(--accent-ink);
  font-weight: 600;
}

.log-search {
  flex: 1 1 220px;
  min-width: 180px;
}

.log-list {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  overflow: hidden;
}

.log-list[aria-busy='true'] { opacity: 0.5; }

/* Four columns so the eye can run down one of them. The detail is last and
   allowed to be long; everything before it stays aligned. */
.log-row {
  display: grid;
  grid-template-columns: 88px minmax(140px, 1fr) minmax(150px, 1fr) minmax(0, 1.6fr);
  gap: 12px;
  align-items: baseline;
  padding: 9px 14px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}

.log-row:last-child { border-bottom: 0; }
.log-row:nth-child(even) { background: var(--raised); }

.log-when {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.log-who {
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.log-what { color: var(--ink); font-weight: 600; }
.log-detail { color: var(--muted); overflow-wrap: anywhere; }

/* State on the verb, not the whole row: a red stripe across a list of ordinary
   activity makes the ordinary lines look like findings too. */
.log-row.is-bad .log-what { color: var(--danger); }
.log-row.is-good .log-what { color: var(--ok-ink); }

.log-empty {
  margin: 0;
  padding: 26px 14px;
  text-align: center;
  color: var(--muted);
}

.log-foot { justify-content: center; }

@media (max-width: 760px) {
  /* Stacked: four columns inside a narrow pane turns every cell into a
     one-word-per-line column. */
  .log-row {
    grid-template-columns: 84px minmax(0, 1fr);
    row-gap: 2px;
  }
  .log-when { grid-row: span 3; }
  .log-detail { grid-column: 2; }
}

/* ── The step dialog ──────────────────────────────────────────────────────── */

.step-dialog {
  /* Wide enough for the editor and the preview beside it, and capped so neither
     becomes a single line of text stretched across a large monitor. */
  width: min(1120px, calc(100vw - 32px));
  max-height: calc(100vh - 48px);
  padding: 0;
  border: var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 30px 70px rgba(16, 37, 66, 0.22);
  background: var(--panel);
  color: var(--ink);
  overflow: hidden;
}

.step-dialog::backdrop { background: rgba(16, 37, 66, 0.42); }

/* Editor left, preview right. They stack on a narrow screen with the preview
   underneath: a 300px column beside a 300px form helps nobody. */
.step-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  align-items: start;
}

/* The pane has no padding of its own — in the editor that comes from the
   surrounding column — so the dialog supplies it, and the form scrolls inside
   rather than growing past the window. */
.step-dialog .pane {
  max-width: none;
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  /* No padding at the top: the sticky header supplies it. Sticky pins to the
     padding edge, so 24px of padding here left 24px of form scrolling past
     above a header that was holding still — visible as a sliver of the last
     field sliding along the top of the dialog. */
  padding: 0 24px 24px;
}

/* The footer stays reachable in a long form: an editor whose Gem is four
   scrolls below the fold is one people stop trusting.
   No negative margins to bleed it edge to edge — the pane's padding is the
   dialog's, and pulling 24px out of a box that has none is what put a
   horizontal scrollbar under the whole form. */
/* Edge to edge inside the dialog, so nothing scrolls through the gap beside a
   header that is holding still. */
/* Inside the dialog the ground IS a card, so here the header takes the card's
   colour for the same reason it does not in the pane. */
.step-dialog .pane-head {
  top: 0;
  margin: 0 -24px 18px;
  padding: 16px 24px 14px;
  background: var(--panel);
}

.step-dialog .pane-foot {
  position: sticky;
  bottom: -24px;
  background: var(--panel);
  border-top: var(--hairline);
  margin-top: 22px;
  padding: 14px 0 24px;
}

/* ── The tree ─────────────────────────────────────────────────────────────── */

.board-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 28px;
  padding-top: 20px;
  border-top: var(--hairline);
}

.board-head h2 { margin: 0; font-size: 15px; }
.board-head .field-hint { margin: 0; }

.board { margin-top: 14px; }

.board-empty {
  margin: 0;
  padding: 24px;
  text-align: center;
  color: var(--muted);
  border: 1px dashed var(--field-line);
  border-radius: var(--radius);
}

/* One level of the tree: a spine down the left, and everything at this level
   hanging off it. Nesting a .flow inside a .branch indents it and gives it its
   own spine, which is the whole mechanism — there is no separate "merge"
   element because the trunk simply carrying on IS the merge. */
.flow {
  position: relative;
  display: flex;
  flex-direction: column;
  padding-left: 26px;
}

/* The spine. Stops at the last node's centre rather than running to the bottom
   of the box: a line trailing past the final step suggests something below it. */
.flow::before {
  content: "";
  position: absolute;
  left: 10px;
  top: 20px;
  bottom: 20px;
  width: 2px;
  background: var(--line);
}

/* No spine at all when there is nothing on it. top: 20px against bottom: 20px
   on a box only forty pixels tall left a two-pixel stub sitting next to the
   drop zone, which read as a rendering fault rather than as a line. The elbow
   above already shows what this branch hangs from. */
.flow.is-empty::before { display: none; }

.card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px 7px 8px;
  border: var(--hairline);
  border-radius: var(--radius-control);
  background: var(--panel);
  cursor: pointer;
  text-align: left;
}

/* The node itself, on the spine, level with the card it belongs to. This is
   what the view was missing: without it the spine is a line beside a stack of
   boxes rather than a tree with things on it.

   The numbers here are content-box, deliberately: `*` does not match pseudo
   elements, so the border-box rule at the top of this file never reached these
   and the borders add to the width. 8px of content plus two 2px borders is a
   12px dot — which is why the tick below starts at -9 and not at -12. Getting
   that wrong drew the tick four pixels inside the circle. */
.card::before {
  content: "";
  position: absolute;
  left: -21px;
  top: 50%;
  width: 8px;
  height: 8px;
  margin-top: -6px;
  border-radius: 50%;
  background: var(--panel);
  border: 2px solid var(--line);
}

/* The tick joining node to card, starting where the node's outer edge ends. */
.card::after {
  content: "";
  position: absolute;
  left: -9px;
  top: 50%;
  width: 9px;
  height: 2px;
  margin-top: -1px;
  background: var(--line);
}

.card:hover { border-color: var(--accent-edge); background: var(--accent-fill); }
.card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.card.is-dragging { opacity: 0.4; }

/* A question is where the tree divides, so its node is filled rather than
   hollow — the one place on the spine where something happens. */
.card.is-question { border-color: var(--accent-edge); }
/* Filled rather than larger. A different size would need its own offsets to
   stay centred on the spine, and colour already says everything size would. */
.card.is-question::before {
  background: var(--accent);
  border-color: var(--accent);
}

.card-grip { color: var(--muted); cursor: grab; user-select: none; font-size: 12px; }

.card-n {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--raised);
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-variant-numeric: tabular-nums;
}

/* Small, because it is for recognising a step you already know rather than for
   looking at. A chapter of screenshots at full size is a scroll, not a map. */
.card-thumb {
  flex: none;
  width: 26px;
  height: 34px;
  object-fit: cover;
  object-position: top;
  border-radius: 4px;
  border: var(--hairline);
  background: var(--raised);
}

/* One line. The title and its chips share a row so a step costs ~40px instead
   of ~78, which is what decides whether a long chapter can be seen at all. */
.card-main {
  min-width: 0;
  flex: 1;
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.card-title {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-meta {
  margin: 0;
  flex: none;
  display: flex;
  gap: 5px;
  align-items: center;
}

.card-meta .chip { font-size: 10.5px; }

/* ── Branches ─────────────────────────────────────────────────────────────── */

/* Hangs off the parent spine. Its own .flow inside indents again and brings its
   own spine, so depth is just nesting. */
.branch { position: relative; padding-left: 26px; }

/* The elbow: down from the PARENT spine, then across to this branch's own.
   Negative, because the parent spine sits in the padding this element was
   indented by — measured from the parent's line, not from this box's edge. */
.branch::before {
  content: "";
  position: absolute;
  left: -16px;
  top: -6px;
  width: 26px;
  height: 22px;
  border-left: 2px solid var(--accent-edge);
  border-bottom: 2px solid var(--accent-edge);
  border-bottom-left-radius: 7px;
}

.branch-label {
  margin: 0 0 6px;
  display: inline-block;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--accent-ink);
  background: var(--accent-fill);
  border: 1px solid var(--accent-edge);
  border-radius: 999px;
  padding: 2px 10px;
}

/* The branch's own spine is tinted, so at a glance you can tell which part of a
   chapter everybody walks and which part only some people see. */
.branch > .flow::before { background: var(--accent-edge); }
.branch > .flow > .card::before { border-color: var(--accent-edge); }
.branch > .flow > .card::after { background: var(--accent-edge); }

.branch-empty {
  display: block;
  width: 100%;
  margin: 0;
  padding: 10px 12px;
  font-size: 12.5px;
  font-family: inherit;
  text-align: left;
  color: var(--muted);
  border: 1px dashed var(--field-line);
  border-radius: var(--radius-control);
  background: var(--raised);
  cursor: pointer;
}

.branch-empty:hover {
  border-color: var(--accent-edge);
  border-style: solid;
  background: var(--accent-fill);
  color: var(--accent-ink);
}

.branch-empty:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── Adding a step where you are looking ──────────────────────────────────── */

/* The gap between two steps IS the control. It occupies the space the flow's
   gap used to, so nothing moves when it appears — a target that pushes the
   list around as you approach it is a target you chase. */
.insert {
  position: relative;
  display: block;
  width: 100%;
  height: 10px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}

/* The line, showing exactly where the step would land. */
.insert::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 2px;
  margin-top: -1px;
  border-radius: 1px;
  background: var(--accent);
  opacity: 0;
  transition: opacity 0.12s ease;
}

/* Centred on the spine, where the new step's own node will be. */
.insert-plus {
  position: absolute;
  left: -24px;
  top: 50%;
  width: 18px;
  height: 18px;
  margin-top: -9px;
  border-radius: 50%;
  background: var(--accent);
  color: #ffffff;
  font-size: 13px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  opacity: 0;
  transform: scale(0.6);
  transition: opacity 0.12s ease, transform 0.12s ease;
}

.insert:hover::after,
.insert:focus-visible::after { opacity: 1; }

.insert:hover .insert-plus,
.insert:focus-visible .insert-plus { opacity: 1; transform: scale(1); }

.insert:focus-visible { outline: none; }

@media (prefers-reduced-motion: reduce) {
  .insert::after, .insert-plus { transition: none; }
}

@media (max-width: 640px) {
  /* Indentation is the one thing that cannot be given up here — it is what
     says "only some people see this" — so the cards give up their padding
     instead. */
  .flow, .branch { padding-left: 20px; }
  .card { padding-left: 6px; }
  .card-thumb { display: none; }
}

/* ── The right-click menu ─────────────────────────────────────────────────── */

.menu {
  position: fixed;
  z-index: 50;
  min-width: 176px;
  padding: 5px;
  border: var(--hairline);
  border-radius: var(--radius-control);
  background: var(--panel);
  box-shadow: 0 12px 32px rgba(16, 37, 66, 0.16);
}

/* Same trap as everywhere else in this file: an author rule that sets display
   beats the UA's [hidden], so the attribute has to be honoured explicitly. */
.menu[hidden] { display: none; }

.menu-item {
  display: block;
  width: 100%;
  padding: 7px 12px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  text-align: left;
  cursor: pointer;
}

.menu-item:hover:not(:disabled) { background: var(--accent-fill); color: var(--accent-ink); }
.menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.menu-item:disabled { color: var(--muted); opacity: 0.55; cursor: default; }
.menu-item.is-danger { color: var(--danger); }
.menu-item.is-danger:hover:not(:disabled) { background: var(--danger-fill); color: var(--danger); }

.menu-line { height: 1px; margin: 5px 8px; background: var(--line); }


/* ── The step preview ─────────────────────────────────────────────────────── */

.preview {
  position: sticky;
  top: 0;
  align-self: start;
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  padding: 24px 24px 24px 0;
  border-left: var(--hairline);
  padding-left: 20px;
}

.preview-label {
  margin: 0 0 10px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The ground the card sits on, so the customer's background colour is part of
   what is being previewed rather than something the builder invents. */
.preview-frame {
  border-radius: var(--radius);
  padding: 14px;
  background: var(--bg-preview, var(--raised));
  border: var(--hairline);
}

/* Painted by palette.js with the guide's own derivation, so these read from
   the same variables the real page does. */
.preview-card {
  border-radius: 14px;
  padding: 16px 14px 14px;
  background: var(--card, #ffffff);
  color: var(--ink, #102542);
  text-align: center;
  font-size: 13px;
  line-height: 1.5;
}

.pv-eyebrow {
  margin: 0 0 2px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent, #376af2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pv-title {
  margin: 0 0 10px;
  font-size: 16px;
  line-height: 1.25;
  color: var(--head, inherit);
  overflow-wrap: anywhere;
}

.pv-media { margin: 0 0 10px; }
.pv-media[hidden] { display: none; }
/* Definite width with height:auto — the same pairing the guide needs, and the
   same one that stretched every image when it was got wrong there. */
.pv-media img {
  display: block;
  width: 100%;
  height: auto;
  max-width: 190px;
  margin: 0 auto;
  border-radius: 8px;
  border: 1px solid var(--line, #e5e7eb);
}

/* Media after the text, when the step says so. */
.preview-card.is-media-after { display: flex; flex-direction: column; }
.preview-card.is-media-after .pv-media { order: 3; margin: 10px 0 0; }
.preview-card.is-media-after .pv-prose { order: 2; }
.preview-card.is-media-after .pv-extra { order: 4; }
.preview-card.is-media-after .pv-foot { order: 5; }

.pv-prose p, .pv-prose ul { margin: 0 0 8px; }
.pv-prose p:last-child, .pv-prose ul:last-child { margin-bottom: 0; }
.pv-prose h4 { margin: 10px 0 4px; font-size: 13px; }
.pv-prose ul { padding-left: 18px; text-align: left; }
.pv-prose li { margin: 0 0 2px; }

.pv-var {
  background: var(--accent-fill, #eef3fe);
  color: var(--accent-ink, #1c3f8f);
  border-radius: 4px;
  padding: 0 3px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px;
  overflow-wrap: anywhere;
}

.pv-extra { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }

.pv-option {
  border: 1px solid var(--line, #e5e7eb);
  border-radius: 9px;
  padding: 8px 10px;
  font-size: 12.5px;
  font-weight: 600;
}

.pv-note {
  margin: 0;
  border: 1px dashed var(--line, #e5e7eb);
  border-radius: 9px;
  padding: 8px 10px;
  font-size: 11.5px;
  color: var(--muted, #69748a);
}

.pv-foot {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px solid var(--line, #e5e7eb);
}

/* --btn-bg and --btn-ink, which is the pair the guide's own buttons use.
   --accent-text was the wrong one and looked right until you read it: it is the
   colour for accent-coloured text ON THE CARD, so putting it on a filled accent
   button gives dark blue on blue and a label nobody can see. */
.pv-btn {
  border-radius: 999px;
  padding: 6px 16px;
  font-size: 12px;
  font-weight: 600;
  background: var(--btn-bg, #376af2);
  color: var(--btn-ink, #ffffff);
}

.pv-btn.is-quiet {
  background: transparent;
  color: var(--muted, #69748a);
  border: 1px solid var(--line, #e5e7eb);
}

.pv-btn[hidden] { display: none; }

@media (max-width: 900px) {
  .step-split { grid-template-columns: minmax(0, 1fr); }
  .preview {
    position: static;
    max-height: none;
    border-left: 0;
    border-top: var(--hairline);
    padding: 18px 24px 24px;
  }
  .preview-card { max-width: 340px; margin: 0 auto; }
}


/* ── Dropping a picture ───────────────────────────────────────────────────── */

/* The field is a drop target at all times; this is only what it looks like with
   something over it. Outline rather than border, so nothing reflows and the
   field does not jump as the pointer crosses it. */
.media-field[data-media-zone] { transition: background 0.12s ease; }

.media-field.is-drop {
  outline: 2px dashed var(--accent);
  outline-offset: 2px;
  background: var(--accent-fill);
}

.media-drophint {
  align-self: center;
  font-size: 12px;
  color: var(--muted);
}

@media (prefers-reduced-motion: reduce) {
  .media-field[data-media-zone] { transition: none; }
}

.menu-head {
  margin: 4px 0 2px;
  padding: 0 12px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
}

/* A button whose whole label is one character still needs to look like the
   others beside it, so it is squared off rather than left narrow. */
button.small.icon {
  min-width: 34px;
  padding-left: 0;
  padding-right: 0;
  font-size: 16px;
  line-height: 1;
}

/* The two arrows read as one control, so they sit together rather than as two
   more buttons in a row of unrelated ones. */
.step-nav { display: inline-flex; gap: 4px; }
.step-nav button:disabled { opacity: 0.4; cursor: default; }

/* The step open in the editor, marked in the tree behind it. Ring rather than
   fill: the card still has to read as itself, and a filled row in a list of
   unfilled ones looks like a different kind of thing rather than the same kind
   being pointed at. */
.card.is-active {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

/* ── Live ─────────────────────────────────────────────────────────────────── */

.live-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 16px;
}

/* Each tile is the filter for the category it counts. */
.live-tile {
  flex: 1 1 110px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 10px 12px;
  border: var(--hairline);
  border-radius: var(--radius-control);
  background: var(--panel);
  font: inherit;
  text-align: left;
  cursor: pointer;
  /* Stated, not inherited. The base `button` rule paints white on accent, so
     the moment these became buttons every number without a tone class of its
     own turned white on a white tile and vanished. */
  color: var(--ink);
}

.live-tile strong { color: var(--ink); }
.live-tile:hover { border-color: var(--accent-edge); }
.live-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The chosen one, and the list below it, are the same statement — so it is
   filled rather than merely outlined. */
.live-tile.is-on {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
  background: var(--accent-fill);
}
.live-tile.is-on span { color: var(--accent-ink); }

.live-tile strong { font-size: 20px; font-variant-numeric: tabular-nums; }
.live-tile span { font-size: 11.5px; color: var(--muted); }
.live-tile.is-good strong { color: var(--ok-ink); }
.live-tile.is-bad strong { color: var(--danger); }
.live-tile.is-bad { border-color: var(--danger-edge); background: var(--danger-fill); }
.live-tile.is-bad strong { color: var(--danger); }

.live-list {
  border: var(--hairline);
  border-radius: var(--radius);
  background: var(--panel);
  overflow: hidden;
}

/* Name, how long, where they are, how far. Four columns so the eye can run down
   any one of them — the whole point of a list four hundred rows long. */
.live-row {
  display: grid;
  /* Names get room to be names — "Maria Heiberg Taulbjerg Be…" was the column
     apologising for its own width. The progress bar takes what is left, since
     it reads at any size and a name does not. */
  grid-template-columns: minmax(220px, 2fr) 96px minmax(240px, 2fr) minmax(160px, 1fr);
  gap: 12px;
  align-items: center;
  padding: 9px 14px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}

.live-row:last-child { border-bottom: 0; }
.live-row:nth-child(even) { background: var(--raised); }

/* The state, down the edge. Colour on the left of the row is what makes a
   category findable by running an eye down a list of forty; the chip beside the
   name is what names it once you have stopped. */
.live-row { border-left: 3px solid transparent; }
.live-row.state-i-gang { border-left-color: var(--accent-edge); }
.live-row.state-har-en-kode { border-left-color: #d8b872; }
.live-row.state-venter-på-inddragelse,
.live-row.state-kode-udløber-selv { border-left-color: #d8b872; }
/* --ok-ink, because --ok is not one of this stylesheet's tokens: an unknown
   custom property makes the declaration invalid, and border-left-color then
   falls back to currentColor — which painted "finished" in the same dark navy
   as the text. */
.live-row.state-færdig { border-left-color: var(--ok-ink); }
.live-row.state-ikke-begyndt { border-left-color: var(--line); }

/* Sorted to the top already; the tint is so it is obvious you have reached the
   end of the ones worth looking at. */
.live-row.needs-look { background: var(--warn-fill); }

.live-who { min-width: 0; }
.live-name { margin: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.live-mail { margin: 1px 0 0; color: var(--muted); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.live-when { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Quarter of an hour without a click, on somebody who is not finished. Not an
   alarm — the attention rule upstairs decides that — just enough weight that a
   run of them is visible while scrolling. */
.live-when[data-stale='1'] { color: var(--warn-ink); font-weight: 600; }

/* Three different kinds of thing sat 5px apart at the same weight: what they
   are doing, where they are, and how long they have held a credential. Given
   room, and each made to look like its own kind. */
.live-where { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; min-width: 0; }

.live-chapter {
  color: var(--muted);
  font-size: 12.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.live-state {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 3px 9px;
}

/* The same colour as the row's stripe, so the two read as one signal. */
.live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.live-state.is-færdig { background: var(--ok-fill); color: var(--ok-ink); }
.live-state.is-har-en-kode,
.live-state.is-venter-på-inddragelse,
.live-state.is-kode-udløber-selv { background: var(--warn-fill); color: var(--warn-ink); }
.live-state.is-i-gang { background: var(--accent-fill); color: var(--accent-ink); }
.live-state.is-ikke-begyndt { background: var(--raised); color: var(--muted); }
.chip.is-held { background: var(--warn-fill); color: var(--warn-ink); font-size: 10.5px; }

/* Seen registering. Green, because it is the one thing on the row that says the
   morning worked — and quiet, because it is the expected outcome and a list
   where the ordinary case shouts is a list nobody scans. */
.chip.is-mfa { background: var(--ok-fill); color: var(--ok-ink); }
/* Finished the guide with no registration on record. Not red: this is an
   observation about what was seen, not a verdict about the account. */
.chip.is-no-mfa {
  background: var(--panel);
  color: var(--muted);
  box-shadow: inset 0 0 0 1px var(--field-line);
}

/* Column names above the list. Same grid and the same horizontal offsets as a
   row — including the 3px the state stripe takes on the left, which is padding
   here rather than a border so the header carries no stripe of its own. */
.live-head {
  display: grid;
  grid-template-columns: minmax(220px, 2fr) 96px minmax(240px, 2fr) minmax(160px, 1fr);
  gap: 12px;
  padding: 0 14px 6px 17px;
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.live-progress-wrap { display: flex; align-items: center; gap: 8px; }
.live-progress {
  flex: 1;
  height: 6px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}
.live-progress > div { height: 100%; background: var(--accent); border-radius: 999px; }
.live-progress-wrap span { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

.live-empty { margin: 0; padding: 26px 14px; text-align: center; color: var(--muted); }

@media (max-width: 760px) {
  .live-row { grid-template-columns: minmax(0, 1fr) 66px; row-gap: 6px; }
  .live-where, .live-progress-wrap { grid-column: 1 / -1; }
}

.live-tile.is-bad + .live-unblock-all,
.live-unblock-all { align-self: center; margin-left: auto; }
.chip.is-blocked { background: var(--danger-fill); color: var(--danger); font-size: 10.5px; }
.live-unblock { padding: 3px 9px; font-size: 11.5px; }

.live-filters {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 12px;
}

.live-filters .inline-label { margin: 0; }
.live-filters select { min-width: 190px; }
.live-count { font-size: 12.5px; color: var(--muted); }

/* The location, beside the address. Given its own weight so it is findable in a
   column of email addresses that all look alike. */
.live-place { color: var(--ink); font-weight: 600; }

/* Why a row is highlighted, said on the row. */
.chip.is-why { background: var(--warn-fill); color: var(--warn-ink); font-size: 10.5px; }

/* ── The settings page ─────────────────────────────────────────────────────
   
   A window of its own, under the top bar. The builder's layout is hidden while
   this is open rather than laid over — there is nothing behind it to see, and a
   scrim over a full-height editor is just a darker editor. */

.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  background: none;
  /* Inherited rather than set: the gear is a control in a row of links, and it
     should read as the same weight as the text either side of it. */
  color: var(--muted);
  cursor: pointer;
}
.icon-button:hover { color: var(--ink); background: var(--raised); border-color: var(--line); }
.icon-button:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 1px; }
.icon-button[aria-expanded='true'] {
  color: var(--accent-ink);
  background: var(--accent-fill);
  border-color: var(--accent-edge);
}

.settings-page {
  min-height: calc(100dvh - 54px);
  display: flex;
  flex-direction: column;
}

.settings-head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 26px;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 54px;
  z-index: 4;
}
.settings-head h1 { font-size: 19px; margin: 0; flex: 1; }
.settings-head-actions { display: flex; align-items: center; gap: 12px; }

.settings-dirty {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--warn-ink);
  background: var(--warn-fill);
  border: 1px solid var(--warn-edge);
  border-radius: 999px;
  padding: 3px 10px;
}

.settings-shell {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  /* minmax(0, …) rather than 1fr alone: a grid item's automatic minimum size is
     its content, so one wide colour row would push the rail off the screen. */
  flex: 1 1 auto;
  align-items: start;
}

.settings-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 18px 12px;
  border-right: 1px solid var(--line);
  background: var(--panel);
  position: sticky;
  /* Under the top bar and the settings head together. Measured on open rather
     than written down: the head's height is whatever its buttons happen to be,
     and a number typed here was already 5px short of the truth — enough to slide
     the first category under the head when the page is scrolled. */
  top: var(--settings-top, 117px);
  /* Not stretch. A sticky box as tall as the column it sits in has nowhere to
     stick to and simply scrolls away — which is what it did. Given the height of
     what is actually visible instead, the same shape the chapter tree uses, it
     stays put and fills the panel to the bottom of the window. */
  align-self: start;
  height: calc(100dvh - var(--settings-top, 117px));
  overflow-y: auto;
}

.settings-nav-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 9px 11px;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  background: none;
  cursor: pointer;
  /* A button does not inherit the page's font, and without this the rail was
     set in the browser's default while everything beside it was not. */
  font: inherit;
  color: var(--ink);
}
.settings-nav-item:hover { background: var(--raised); }
.settings-nav-item.is-active {
  background: var(--accent-fill);
  border-color: var(--accent-edge);
  color: var(--accent-ink);
}
.settings-nav-item:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 1px; }
.settings-nav-label { display: block; font-weight: 600; font-size: 14px; }
.settings-nav-hint {
  display: block;
  font-size: 12px;
  color: var(--muted);
  margin-top: 1px;
}
.settings-nav-item.is-active .settings-nav-hint { color: var(--accent-ink); opacity: 0.75; }

.settings-main { padding: 22px 26px 40px; }
/* The same measure the editor panes use. Prose and single-column forms are read,
   not scanned, and a text field the width of a monitor is a field nobody can
   see both ends of. */
.settings-section { max-width: 660px; }

/* ── Sending one by hand ─────────────────────────────────────────────────── */

.pane-note.sms-live {
  background: var(--danger-fill);
  border-color: var(--danger-edge);
  color: var(--danger);
}
/* Nothing leaves the machine on a console install, so the warning would be a
   lie in the one direction that trains people to ignore warnings. */
.pane-note.sms-live.is-console {
  background: var(--warn-fill);
  border-color: var(--warn-edge);
  color: var(--warn-ink);
}

.sms-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.sms-foot-spacer { flex: 1 1 auto; }
.sms-count {
  font-size: 12.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.sms-count[data-split='1'] { color: var(--warn-ink); }

.settings-facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 6px 18px;
  margin: 0;
  font-size: 13.5px;
}
.settings-facts dt {
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  align-self: center;
}
.settings-facts dd { margin: 0; overflow-wrap: anywhere; }

/* A credit, not a control. Centred under the panels it belongs to, at the size
   of a colophon — present when looked for, quiet when not. */
.settings-credit {
  margin: 22px 0 0;
  text-align: center;
  font-size: 12.5px;
  color: var(--muted);
}
.settings-credit strong { color: var(--ink); font-weight: 600; }

.field-hint[data-bad='1'] { color: var(--danger); }

/* Not a hint. It is the difference between a greeting and four braces arriving
   on somebody's telephone, so it is given the weight of the thing it stops. */
.settings-page [data-sms-var-warning] {
  margin-top: -8px;
  padding: 9px 12px;
  border-radius: var(--radius-control);
  background: var(--warn-fill);
  border: 1px solid var(--warn-edge);
  color: var(--warn-ink);
}

/* Narrow: the rail becomes a row above the content rather than a column beside
   it. The builder is a desktop tool, but a settings page gets opened from a
   laptop on a train. */
@media (max-width: 800px) {
  .settings-shell { grid-template-columns: minmax(0, 1fr); }
  .settings-nav {
    flex-direction: row;
    overflow-x: auto;
    overflow-y: hidden;
    height: auto;
    border-right: none;
    border-bottom: 1px solid var(--line);
    position: static;
    padding: 12px;
  }
  .settings-nav-item { width: auto; white-space: nowrap; }
  .settings-nav-hint { display: none; }
  .settings-main { padding: 18px 16px 40px; }
}

/* ── Sending to a group ────────────────────────────────────────────────────
   
   A list you scan and tick, so the row is dense and the checkbox is the widest
   target on it. The panel is not the whole page — it lives inside the settings
   measure — so the list scrolls rather than the page growing to four hundred
   rows. */

.bulk-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  align-items: center;
  margin-bottom: 10px;
}
/* The shared .inline-label draws a rule above itself and takes the full width;
   in a toolbar of three controls neither is wanted. */
.settings-page .bulk-filter {
  margin: 0;
  padding-top: 0;
  border-top: 0;
  flex: 1 1 190px;
}
/* The global field rule is a long :not() chain and outweighs a single class,
   so the reset for a field sitting in a toolbar has to be as specific. */
.settings-page .bulk-filter input,
.settings-page .bulk-filter select { margin-top: 0; }
.bulk-only-picked { flex: 0 0 auto; }

.bulk-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-bottom: 10px;
}
.bulk-tally {
  margin-left: auto;
  font-size: 12.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.bulk-list {
  border: 1px solid var(--field-line);
  border-radius: var(--radius-control);
  /* Tall enough to see a cohort in, short enough that the message field below
     it stays on screen while you tick. */
  max-height: 300px;
  overflow-y: auto;
  background: #ffffff;
  margin-bottom: 16px;
}

/* Two classes deep on purpose. `.settings-page label` is (0,1,1) and sets
   display, size, weight, case and colour for stacked form labels — a one-class
   `.bulk-row` loses to it, and every name in the list came out as small grey
   uppercase stacked in a column. This is (0,2,0), which wins on class count. */
.settings-page .bulk-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  margin: 0;
  padding: 7px 11px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  color: var(--ink);
  cursor: pointer;
}
.settings-page .bulk-row:last-child { border-bottom: 0; }
.settings-page .bulk-row:hover { background: var(--raised); }
.settings-page .bulk-row input { width: auto; margin: 0; }
.settings-page .bulk-row.is-unreachable { color: var(--muted); cursor: default; }
.settings-page .bulk-row.is-unreachable:hover { background: none; }

.bulk-who { min-width: 0; display: flex; flex-direction: column; }
.bulk-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bulk-row.is-unreachable .bulk-name { font-weight: 400; }
.bulk-mail {
  color: var(--muted);
  font-size: 11.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bulk-phone {
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.bulk-why { font-size: 11px; color: var(--muted); font-style: italic; white-space: nowrap; }

.bulk-empty { margin: 0; padding: 18px 12px; text-align: center; color: var(--muted); font-size: 13px; }
.bulk-empty.is-bad { color: var(--danger); }

.bulk-progress {
  margin-top: 14px;
  padding: 12px 13px;
  border-radius: var(--radius-control);
  background: var(--raised);
  border: 1px solid var(--line);
}
.bulk-progress-line { margin: 0 0 8px; font-size: 13px; font-weight: 600; }
.bulk-bar { height: 6px; border-radius: 999px; background: var(--line); overflow: hidden; }
.bulk-bar > div { height: 100%; background: var(--accent); transition: width 0.3s ease; }
.bulk-fail-head { margin: 12px 0 4px; font-size: 12.5px; font-weight: 600; color: var(--danger); }
.bulk-fails { margin: 0; padding-left: 18px; font-size: 12.5px; color: var(--muted); }
.bulk-fails li { margin-bottom: 2px; }

/* ── Påmindelser ───────────────────────────────────────────────────────────
   
   A list of short things, so each is a row rather than a page: the sentence on
   top where the eye starts, and the four small decisions about it underneath. */

.splash-rows { display: flex; flex-direction: column; gap: 10px; margin-bottom: 14px; }

.splash-row {
  padding: 11px 12px;
  border: 1px solid var(--field-line);
  border-radius: var(--radius-control);
  background: var(--raised);
}
/* Switched off, and still there. Faded rather than hidden, because the reason
   somebody turns one off is usually to turn it on again next week. */
.splash-row.is-off { opacity: 0.55; }

.splash-row-top {
  display: flex;
  align-items: center;
  gap: 10px;
}
.settings-page .splash-row-top input[type='checkbox'] { width: auto; margin: 0; flex: none; }
.settings-page .splash-message { margin: 0; flex: 1 1 auto; min-width: 0; }

.splash-row-bottom {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 9px;
  flex-wrap: wrap;
}
.settings-page .splash-row-bottom select { margin: 0; width: auto; flex: 0 1 auto; min-width: 0; }
.settings-page .splash-value { margin: 0; width: 84px; flex: none; }
.splash-unit { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.splash-row-bottom button { margin-left: auto; }

.splash-row-hint { margin: 8px 0 0; }
.splash-tally { margin: 0; align-self: center; }

/* ── Trying one out ────────────────────────────────────────────────────────
   
   On the page rather than over it. This is somebody comparing six animations,
   not somebody being interrupted, and a modal to dismiss for each one is its own
   small penance. */
.splash-stage {
  position: relative;
  overflow: hidden;
  display: grid;
  place-items: center;
  min-height: 168px;
  padding: 20px;
  border-radius: var(--radius-control);
  /* The guide's ground, not the builder's, so the card is judged against the
     colour it will actually sit on. */
  background: var(--ink);
  background-image: radial-gradient(circle at 50% 40%, rgba(255, 255, 255, 0.08), transparent 60%);
}

.splash-preview {
  max-width: 20rem;
  width: 100%;
  padding: 20px 20px 15px;
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: 0 16px 40px -16px rgba(0, 0, 0, 0.5);
  text-align: center;
}
.splash-preview-text {
  margin: 0 0 14px;
  font-size: 15px;
  line-height: 1.45;
  color: var(--ink);
  text-wrap: balance;
}
.splash-preview-ok {
  display: block;
  padding: 9px;
  border-radius: var(--radius-control);
  background: var(--accent);
  color: #ffffff;
  font-size: 13.5px;
  font-weight: 600;
}

/* The same six keyframes the guide uses, under the same names — one copy per
   stylesheet, because the two pages share no CSS file. */
.splash-pop     { animation: splash-pop 320ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }
.splash-slide   { animation: splash-slide 300ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.splash-fade    { animation: splash-fade 420ms ease both; }
.splash-bounce  { animation: splash-bounce 620ms cubic-bezier(0.28, 0.84, 0.42, 1) both; }
.splash-wiggle  { animation: splash-wiggle 600ms ease both; }
.splash-confetti { animation: splash-pop 320ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }

@keyframes splash-pop {
  from { opacity: 0; transform: scale(0.86) }
  to   { opacity: 1; transform: scale(1) }
}
@keyframes splash-slide {
  from { opacity: 0; transform: translateY(38px) }
  to   { opacity: 1; transform: translateY(0) }
}
@keyframes splash-fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes splash-bounce {
  0%   { opacity: 0; transform: translateY(-90px) }
  55%  { opacity: 1; transform: translateY(10px) }
  75%  { transform: translateY(-6px) }
  100% { transform: translateY(0) }
}
@keyframes splash-wiggle {
  0%   { opacity: 0; transform: scale(0.92) rotate(0deg) }
  40%  { opacity: 1; transform: scale(1) rotate(-2.4deg) }
  60%  { transform: rotate(2deg) }
  80%  { transform: rotate(-1deg) }
  100% { transform: rotate(0deg) }
}

.confetti-preview {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.confetti-preview span {
  position: absolute;
  top: -12px;
  width: 7px;
  height: 11px;
  border-radius: 1px;
  opacity: 0;
  animation: splash-fall 1.5s ease-in forwards;
}
@keyframes splash-fall {
  0%   { opacity: 0; transform: translateY(0) rotate(0deg) }
  10%  { opacity: 1 }
  100% { opacity: 0; transform: translateY(200px) rotate(320deg) }
}

@media (prefers-reduced-motion: reduce) {
  .splash-preview, .confetti-preview span { animation-duration: 1ms !important; }
}

/* The FAQ rows reuse the reminder row's frame — same kind of thing, same shape
   — with the answer given room underneath. */
.faq-rows { display: flex; flex-direction: column; gap: 10px; margin-bottom: 14px; }

.settings-page .faq-answer-field {
  margin-top: 9px;
  min-height: 64px;
  font-size: 14px;
}

/* Square, so two of them beside a text field read as one control with two
   directions rather than as two more buttons. */
.faq-move {
  flex: none;
  width: 30px;
  padding: 6px 0;
  text-align: center;
  font-size: 12px;
}
.faq-move:disabled { opacity: 0.35; cursor: default; }

/* ── Who else is in here ───────────────────────────────────────────────────
   
   Two people editing the same guide at once is not prevented by anything — the
   last save wins and neither is told — so the least the bar can do is say
   somebody else is about. It lives in the gap between the wordmark and the
   links, which was the only empty part of it. */

.whos-here {
  position: relative;
  display: flex;
  align-items: center;
  margin-left: 22px;
  min-width: 0;
}

/* The circles and the words, as one control. It has to look like part of the
   bar rather than like a button dropped into it, so it borrows nothing but the
   pointer and a focus ring. */
.here-trigger {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px 3px 3px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.here-trigger:hover { background: var(--raised); border-color: var(--line); }
.here-trigger:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 1px; }

/* Two initials are not a name, so the names are one hover away rather than one
   second-long tooltip away. Hover, focus and tap all open it — the last because
   a touchscreen has no hover to give. */
.here-panel {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 20;
  /* Wide enough for an address and "sidst aktiv for 14 min siden" on one line.
     Narrower, the line that says how long ago truncated to "…min sid…", which
     is the half of it that carries the information. */
  min-width: 330px;
  max-width: min(420px, calc(100vw - 32px));
  padding: 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 14px 40px -14px rgba(16, 37, 66, 0.35);
  /* Not display:none — visibility keeps it measurable and lets it fade, and it
     is taken out of the tab order by the same token. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity 0.12s ease, transform 0.12s ease, visibility 0.12s;
}
.whos-here:hover .here-panel,
.whos-here:focus-within .here-panel,
.whos-here.is-open .here-panel {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.here-panel-head {
  margin: 0 0 8px;
  padding: 0 4px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.here-row {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 5px 4px;
}
.here-row + .here-row { border-top: 1px solid var(--line); }
.here-row .here-dot { box-shadow: none; margin: 0; }

.here-who { min-width: 0; display: flex; flex-direction: column; }
.here-name { font-size: 13.5px; font-weight: 600; color: var(--ink); }
.here-when {
  font-size: 11.5px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

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

.here-dot {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 50%;
  /* A ring in the bar's own colour, so overlapping is not needed for them to
     read as separate and a row of them does not become a caterpillar. */
  box-shadow: 0 0 0 2px var(--panel);
  color: #ffffff;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  /* Initials from a directory name: never wrapped, never selected by a
     double-click meant for the wordmark beside it. */
  white-space: nowrap;
  user-select: none;
}
.here-dot + .here-dot { margin-left: -8px; }

.here-more {
  background: var(--raised);
  color: var(--muted);
  box-shadow: 0 0 0 2px var(--panel), inset 0 0 0 1px var(--line);
}

.here-label {
  margin-left: 4px;
  font-size: 12.5px;
  color: var(--muted);
  white-space: nowrap;
}

/* Narrow: the circles say it, the words are the first thing to go. */
@media (max-width: 1100px) {
  .here-label { display: none; }
  .whos-here { margin-left: 14px; }
}

/* ── One person from the Live list ─────────────────────────────────────────
   
   The list answers "who needs a look". This answers what comes straight after
   it, usually with a telephone already ringing: which screen is in front of
   them. So the step they are on gets the top of the dialog and everything else
   is context for it. */

/* The name block is a control now — it opens this — but it must go on looking
   like the rest of the row rather than like a button dropped into it. */
button.live-who {
  display: block;
  width: 100%;
  padding: 0;
  text-align: left;
  font: inherit;
  background: none;
  border: 0;
  border-radius: var(--radius-control);
  color: inherit;
  cursor: pointer;
}
button.live-who:hover .live-name { color: var(--accent-ink); text-decoration: underline; }
button.live-who:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }
.live-row { cursor: pointer; }
/* The row's own controls keep their own pointer: they do something else. */
.live-row button:not(.live-who), .live-row a { cursor: pointer; }

.person-dialog {
  width: min(560px, calc(100vw - 32px));
  max-height: min(78vh, 760px);
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  box-shadow: 0 24px 70px -24px rgba(16, 37, 66, 0.5);
  overflow: hidden;
  /* The dialog is the scroll container's parent, not the scroller: the head
     stays while the steps move. */
  display: flex;
  flex-direction: column;
}
.person-dialog::backdrop { background: rgba(16, 37, 66, 0.4); }

.person-head {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 18px 18px 14px;
  border-bottom: 1px solid var(--line);
  flex: none;
}
.person-head h2 { margin: 0; font-size: 17px; }
.person-sub {
  margin: 3px 0 0;
  font-size: 12.5px;
  color: var(--muted);
  overflow-wrap: anywhere;
}
.person-head .icon-button { margin-left: auto; flex: none; }

.person-body { padding: 16px 18px 20px; overflow-y: auto; }

/* Where they are, given the weight of the thing everything else explains. */
.person-now {
  padding: 13px 14px;
  border-radius: var(--radius-control);
  background: var(--accent-fill);
  border: 1px solid var(--accent-edge);
}
.person-eyebrow {
  margin: 0 0 4px;
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent-ink);
}
.person-step { margin: 0; font-size: 16px; font-weight: 600; color: var(--ink); text-wrap: balance; }
.person-now .chip { margin-top: 8px; display: inline-block; }

.person-note { margin: 12px 0 0; font-size: 13px; color: var(--muted); }
.person-tally { font-variant-numeric: tabular-nums; }

.person-chapter { margin-top: 18px; }
.person-chapter-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 6px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.person-chapter-count { margin-left: auto; font-variant-numeric: tabular-nums; }

.person-steps { list-style: none; margin: 0; padding: 0; }
.person-step-row {
  display: flex;
  align-items: baseline;
  gap: 9px;
  padding: 4px 8px;
  border-radius: 7px;
  font-size: 13.5px;
  color: var(--ink);
}
.person-step-row.is-done { color: var(--muted); }
/* The one row the dialog was opened to find. */
.person-step-row.is-current {
  background: var(--accent-fill);
  color: var(--accent-ink);
  font-weight: 600;
}
.person-mark { flex: none; width: 12px; text-align: center; font-size: 11px; color: var(--muted); }
.person-step-row.is-done .person-mark { color: var(--ok-ink); }
.person-step-row.is-current .person-mark { color: var(--accent); }

/* The thing you do about it, at the foot of the screen that told you. */
.person-foot {
  flex: none;
  padding: 12px 18px 14px;
  border-top: 1px solid var(--line);
  background: var(--raised);
}
.person-compose { margin-top: 10px; }
.settings-page .person-compose textarea,
.person-compose textarea { margin-top: 0; }
.person-compose .sms-foot { margin: 8px 0 0; }

/* The step's own button in the preview, drawn as a button rather than
   described as one. It used to be a fixed line of prose naming wording that was
   not even the guide's, and it went on saying that however the label was
   edited. */
.pv-action {
  margin: 10px 0 0;
  padding: 9px 12px;
  border-radius: var(--radius-control);
  background: var(--accent);
  color: #ffffff;
  font-size: 12.5px;
  font-weight: 600;
  text-align: center;
  /* A long label wraps rather than pushing the preview card wider than the
     phone it is standing in for. */
  overflow-wrap: anywhere;
}
