:root {
  color-scheme: light;
  --ink: #14181f; --muted: #5f6b7c; --line: #e2e6ec; --bg: #f7f8fa; --card: #fff;
  --accent: #1d4ed8; --warn: #b45309; --warn-bg: #fef3c7; --good: #047857;
  --chip: #eef2f7; --chip-ink: #334155; --hover: #fafbfd; --paper: #fcfcfd;
  --note: #eef2ff; --note-ink: #1e3a8a; --note-line: #c7d2fe;
  --good-bg: #d1fae5; --danger: #991b1b; --danger-bg: #fee2e2;
  --editor: #dbeafe; --editor-ink: #1e40af; --operator: #fef3c7; --operator-ink: #92400e; --admin: #ede9fe; --admin-ink: #5b21b6;
  --on-accent: #fff;
  /* The letter is paper: white in both themes, because that is what prints.
     The rest of the editor's colours follow the theme like everything else. */
  --sheet: #fff; --sheet-ink: #111;
  --fixed: #c0392b; --fixed-ink: #a5301f; --fixed-wash: rgba(192,57,43,.09);
  --movable: #2f7d4f; --movable-wash: rgba(47,125,79,.06);
  --hole: #b07b26; --hole-bg: #fff5e3; --hole-ink: #7a5310;
  --named: #2f7d4f; --named-bg: #e6f4ea; --named-ink: #1d5c37;
}

/* Dark. Follows the operating system unless someone has chosen, in which case
   data-theme on <html> wins -- see the toggle in base.html. Only the tokens
   change; no rule below is duplicated. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --ink: #e6e9ef; --muted: #9aa4b2; --line: #2a3140; --bg: #12151c; --card: #1a1f29;
    --accent: #7aa2f7; --warn: #fbbf24; --warn-bg: #3a2f12; --good: #34d399;
    --chip: #242c3a; --chip-ink: #c3cbd8; --hover: #1f2532; --paper: #161b24;
    --note: #1b2440; --note-ink: #c7d6ff; --note-line: #2f3d66;
    --good-bg: #14342a; --danger: #fca5a5; --danger-bg: #3a1d1d;
    --sheet: #fff; --sheet-ink: #111;
    --fixed: #e06c5b; --fixed-ink: #f2a196; --fixed-wash: rgba(224,108,91,.12);
    --movable: #57b07c; --movable-wash: rgba(87,176,124,.10);
    --hole: #d0a050; --hole-bg: #2e2510; --hole-ink: #e8c88a;
    --named: #57b07c; --named-bg: #14301f; --named-ink: #86d3a4;
    --editor: #1e2a44; --editor-ink: #a8c5ff; --operator: #3a2e16; --operator-ink: #fcd34d; --admin: #2a2242; --admin-ink: #d0bcff;
    --on-accent: #0e1116;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --ink: #e6e9ef; --muted: #9aa4b2; --line: #2a3140; --bg: #12151c; --card: #1a1f29;
  --accent: #7aa2f7; --warn: #fbbf24; --warn-bg: #3a2f12; --good: #34d399;
  --chip: #242c3a; --chip-ink: #c3cbd8; --hover: #1f2532; --paper: #161b24;
  --note: #1b2440; --note-ink: #c7d6ff; --note-line: #2f3d66;
  --good-bg: #14342a; --danger: #fca5a5; --danger-bg: #3a1d1d;
  --editor: #1e2a44; --editor-ink: #a8c5ff; --operator: #3a2e16; --operator-ink: #fcd34d; --admin: #2a2242; --admin-ink: #d0bcff;
  --on-accent: #0e1116;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--ink);
  font: 15px/1.5 "Segoe UI", system-ui, -apple-system, sans-serif; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
code, .mono { font-family: "Cascadia Mono", Consolas, ui-monospace, monospace; font-size: 0.88em; }

/* 2.4.1 Bypass Blocks. Off-screen until focused, then it lands over the
   header where it can be seen -- `display: none` would take it out of the tab
   order entirely and defeat the point. */
/* Visible to a screen reader, not on screen. `display: none` would hide it
   from both -- a column header that exists only for announcement still has
   to be in the accessibility tree. */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  border: 0; }

.skip { position: absolute; left: -9999px; top: 0; z-index: 10;
  background: var(--card); color: var(--ink); padding: 10px 16px;
  border: 2px solid var(--accent); border-radius: 0 0 var(--radius, 6px) 0; }
.skip:focus { left: 0; }

/* 2.4.11 Focus Not Obscured (Minimum), new in WCAG 2.2. The header is sticky
   at the top, so an element scrolled to by Tab can come to rest underneath it
   and be invisible while focused. Reserving the header's height when the
   browser scrolls to something keeps the focused control in view. */
html { scroll-padding-top: 72px; }

/* 2.4.7 / 2.4.13. One visible ring everywhere rather than per-control rules,
   so a new control cannot be added without one. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; }
/* `main` takes focus from the skip link; a ring around the whole page is
   noise, and the point is only that focus has moved there. */
main:focus { outline: none; }

header { display: flex; align-items: center; gap: 24px; padding: 0 24px; height: 56px;
  background: var(--card); border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 5; }
.brand { color: var(--ink); display: flex; align-items: center; gap: 7px; }
/* Company over product, the book beside both. Every number is measured off
   the reference John sent on 2026-09-22 (190x36): book 30x34, a 7px gap, the
   wordmark at 12px ink height over `Deliver` at 16px cap, 3px between them.
   Cap height is ~0.71em in this face, so 16px cap means a 22px font. */
.brand .stack { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
/* `Deliver` stays in the accent, not ink. The stacked reference showed it
   white, but that was the white-on-transparent export rather than a choice,
   and John confirmed the accent on 2026-09-22. So the mark and the word both
   move with the product switcher. */
.brand b { font-size: 22px; line-height: 1; font-weight: 400; color: var(--accent); }
/* Masks, not images. The artwork is white on transparent, so only its alpha
   matters -- the colour is painted here. That is the whole reason the mark
   can follow the product switcher and the wordmark can stay ink. */
.brand .mark, .brand .wordmark {
  display: block; mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat;
  mask-size: contain; -webkit-mask-size: contain;
  mask-position: center; -webkit-mask-position: center;
}
.brand .mark { width: 30px; height: 34px; background: var(--product); }
.brand .wordmark { width: 100px; height: 12px; background: var(--ink); }
/* The top bar: what crosses products. */
header nav.shared { display: flex; gap: 18px; margin-right: auto; margin-left: 12px; }
header nav.shared a { color: var(--muted); padding: 4px 0; border-bottom: 2px solid transparent; }
header nav.shared a.on { color: var(--ink); border-bottom-color: var(--product); }
.who { color: var(--muted); font-size: 13px; }

/* A button that reads as a link. Remove-a-box and restore-a-version are both
   POSTs -- they change something, so they cannot be anchors -- but they sit
   inline among text and a full button shouts over the thing it acts on. */
.linkish { background: none; border: none; padding: 0; font: inherit;
  color: var(--accent); cursor: pointer; text-decoration: underline; }
.linkish:hover { text-decoration: none; }
.linkish.danger { color: var(--danger, #b91c1c); }
form.inline { display: inline; }

/* Rail beside content. The rail scrolls with its own sticky offset rather
   than the page, so the products stay reachable down a long table -- 325
   districts is several screens. 56px is the header's height. */
.shell { display: flex; align-items: flex-start; }
.rail { flex: 0 0 186px; position: sticky; top: 56px; align-self: flex-start;
  max-height: calc(100vh - 56px); overflow-y: auto;
  display: flex; flex-direction: column; gap: 1px;
  padding: 14px 10px; border-right: 1px solid var(--line); }
.rail a { display: block; border-radius: 6px; color: var(--muted); }
.rail a:hover { text-decoration: none; background: var(--card); }
/* The product itself: the same pill the old switcher used, so the thing that
   says which product you are in did not change its appearance, only place. */
.rail a.product { padding: 7px 11px; font-size: 14px; margin-top: 6px; }
.rail a.product:first-child { margin-top: 0; }
.rail a.product.on { background: var(--product); color: #fff; font-weight: 600; }
/* Its screens, indented under it and marked down the side rather than along
   the bottom -- a bottom rule in a vertical list reads as a divider. */
.rail a.section { margin: 0 0 0 12px; padding: 5px 10px; font-size: 13.5px;
  border-left: 2px solid transparent; border-radius: 0 6px 6px 0; }
.rail a.section.on { color: var(--ink); border-left-color: var(--product); background: var(--card); }
/* In the column the wrapper must not exist: the sections are laid out by the
   rail itself, directly under the product they belong to. */
.rail .subnav { display: contents; }

main { flex: 1; min-width: 0; max-width: 1180px; margin: 0 auto; padding: 24px; }

/* Narrow: the rail lies down above the content rather than squeezing it.
   Sticky comes off with it -- a horizontal bar that follows you down costs
   height the content needs more. */
@media (max-width: 860px) {
  .shell { display: block; }
  .rail { position: static; max-height: none; flex-direction: row; flex-wrap: wrap;
    align-items: center; gap: 4px; width: auto;
    border-right: none; border-bottom: 1px solid var(--line); padding: 10px 16px; }
  .rail a.product { margin-top: 0; }
  /* Two rows, not one. Flattened into a single row the hierarchy is lost and
     `Campaigns` reads as a sibling of `Merge fields` -- which is the exact
     ambiguity the rail exists to remove. `order` puts every product first
     whichever one is open, and the wrapper takes a full line of its own. */
  .rail a.product { order: 0; }
  .rail .subnav { order: 1; display: flex; flex-basis: 100%; flex-wrap: wrap;
    gap: 4px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); }
  .rail a.section { margin-left: 0; border-left: none; border-radius: 6px;
    border-bottom: 2px solid transparent; }
  .rail a.section.on { border-bottom-color: var(--product); }
}
h1 { font-size: 22px; margin: 0 0 6px; }
h2 { font-size: 16px; margin: 0 0 10px; }
h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 0 0 6px; }
.stats { color: var(--muted); margin: 0 0 18px; }
.crumb { color: var(--muted); font-size: 13px; margin: 0 0 4px; }
.muted { color: var(--muted); }
.plain { list-style: none; padding: 0; }

.banner { margin: 0; padding: 10px 24px; background: var(--note); color: var(--note-ink);
  border-bottom: 1px solid var(--note-line); font-size: 13px; }
.banner.warn { background: var(--warn-bg); color: var(--warn); border-color: var(--warn);
  margin: 0 0 16px; border-radius: 6px; border: 1px solid var(--warn); padding: 10px 14px; }

.card { background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  padding: 16px 18px; margin-bottom: 16px; }
.card.narrow { max-width: 460px; margin: 40px auto; }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th { text-align: left; font-weight: 600; color: var(--muted); font-size: 12px;
  text-transform: uppercase; letter-spacing: .03em; padding: 6px 8px; border-bottom: 1px solid var(--line); }
td { padding: 7px 8px; border-bottom: 1px solid var(--line); vertical-align: top; }
td.n, th.n { text-align: right; font-variant-numeric: tabular-nums; }
tbody tr:hover { background: var(--hover); }

.search input { width: 320px; padding: 8px 10px; border: 1px solid var(--line);
  border-radius: 6px; background: var(--card); color: var(--ink);
  font-size: 14px; margin-bottom: 14px; }

.lang { display: inline-block; padding: 1px 6px; margin-right: 3px; border-radius: 4px;
  background: var(--chip); color: var(--chip-ink); font-size: 12px; }
.tag { display: inline-block; padding: 1px 6px; border-radius: 4px; background: var(--chip);
  color: var(--muted); font-size: 11px; margin-left: 4px; }
/* The district code beside its name. Quiet, because the name is what is
   being read and the code is only there to identify it in Attend. */
.code { margin-left: 6px; font-size: 12px; }
.pill { display: inline-block; min-width: 20px; padding: 1px 6px; border-radius: 10px;
  background: var(--chip); color: var(--chip-ink); font-size: 12px; text-align: center; }
.pill.warn { background: var(--warn-bg); color: var(--warn); }
.good { color: var(--good); }
.conf-likely { background: var(--good-bg); color: var(--good); }
.conf-review { background: var(--warn-bg); color: var(--warn); }

.role { display: inline-block; padding: 1px 7px; border-radius: 4px; font-size: 11px;
  text-transform: uppercase; letter-spacing: .04em; margin-right: 6px; }
.role-viewer { background: var(--chip); color: var(--chip-ink); }
.role-editor { background: var(--editor); color: var(--editor-ink); }
.role-operator { background: var(--operator); color: var(--operator-ink); }
.role-admin { background: var(--admin); color: var(--admin-ink); }

.filters { display: flex; gap: 22px; flex-wrap: wrap; background: var(--card);
  border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px; margin-bottom: 16px; }
.filters > div { max-width: 380px; }
/* 2.5.8 Target Size (Minimum), new in WCAG 2.2: an interactive target is at
   least 24x24. These were 2px of padding on a 15px line -- about 20px tall. */
.chip { display: inline-block; padding: 2px 8px; margin: 0 4px 4px 0; border-radius: 12px;
  min-height: 24px; line-height: 20px; box-sizing: border-box;
  background: var(--chip); color: var(--chip-ink); font-size: 12px; }
.chip.on { background: var(--accent); color: var(--on-accent); }
.chip.clear { background: var(--danger-bg); color: var(--danger); }

button { padding: 5px 11px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--card); color: var(--ink); font-size: 13px; cursor: pointer; }
button:hover:enabled { border-color: var(--accent); color: var(--accent); }
button:disabled { color: var(--muted); opacity: .6; cursor: not-allowed; }
.button { display: inline-block; padding: 8px 14px; border-radius: 6px;
  background: var(--accent); color: var(--on-accent); }
form.inline { display: flex; gap: 6px; }
select, form label input { padding: 5px 8px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--card); color: var(--ink); font-size: 13px; }
form label { display: block; margin-bottom: 10px; color: var(--muted); font-size: 13px; }
form label input { display: block; width: 100%; margin-top: 4px; font-size: 14px; color: var(--ink); }

.variants td { border-bottom: none; }
.bodyrow td { padding-top: 0; border-bottom: 1px solid var(--line); }
.bodyrow summary { cursor: pointer; color: var(--muted); font-size: 13px; }
.letter { background: var(--paper); border: 1px solid var(--line); border-radius: 6px;
  padding: 14px 18px; margin-top: 8px; max-width: 62ch; }
.letter p { margin: 0 0 9px; }
.noterow td { padding-top: 0; font-size: 13px; }
.cond { color: var(--muted); margin-top: 3px; }
hr { border: none; border-top: 1px solid var(--line); margin: 18px 0; }

/* The theme toggle itself. A button, not a link: it changes nothing on the
   server and must not be followed. */
.theme { background: none; border: 1px solid var(--line); border-radius: 6px;
  color: var(--muted); cursor: pointer; font-size: 13px; line-height: 1;
  padding: 5px 8px; margin-left: 12px; }
.theme:hover { color: var(--ink); border-color: var(--accent); }

/* The product switch moved into the rail on 2026-09-22; `header nav.products`
   no longer exists. Its appearance did not change, only its place -- see
   `.rail a.product`. */

/* One accent per product. Two products share a shell, and the only thing that
   said which you were in was one highlighted word in a row of words -- so the
   selected switch, the underline on the current screen and every accent on the
   page move together with it.

   The hue carries the meaning, so it is set once here rather than per rule,
   and dark mode gets a lighter one because the same blue on near-black reads
   as grey. */
:root { --product: #1d4ed8; }                 /* Notifications */
body[data-product="ai"] { --product: #7c3aed; --accent: #7c3aed; }
:root:not([data-theme="light"]) body[data-product="ai"] { --product: #a78bfa; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --product: #60a5fa; }
  :root:not([data-theme="light"]) body[data-product="ai"] { --product: #a78bfa; --accent: #a78bfa; }
}
:root[data-theme="dark"] { --product: #60a5fa; }
:root[data-theme="dark"] body[data-product="ai"] { --product: #a78bfa; --accent: #a78bfa; }

/* Forms: a third colour, far enough from both to be told apart at a glance in
   a tab strip -- which is the only job this token has. */
body[data-product="forms"] { --product: #0f766e; --accent: #0f766e; }
:root:not([data-theme="light"]) body[data-product="forms"] { --product: #2dd4bf; --accent: #2dd4bf; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body[data-product="forms"] { --product: #2dd4bf; --accent: #2dd4bf; }
}
:root[data-theme="dark"] body[data-product="forms"] { --product: #2dd4bf; --accent: #2dd4bf; }

/* --- the form editor ---------------------------------------------------------
   The page is a PICTURE of the artwork with the fillable parts drawn on top of
   it, in the same coordinate space. An <iframe> of the PDF would show the same
   artwork and take the overlay with it, and the overlay is the whole point of
   the screen. */
.form-page { position: relative; display: inline-block; background: var(--sheet);
             box-shadow: 0 1px 4px rgba(0,0,0,.18); }
.form-page img { display: block; width: 100%; height: auto; }
.form-hole { position: absolute; border: 1px solid var(--named);
             background: var(--named-bg); opacity: .55; border-radius: 2px;
             cursor: pointer; }
.form-hole:hover, .form-hole.on { opacity: .9; outline: 2px solid var(--accent); }
.form-hole.unbound { border-color: var(--fixed); background: var(--warn-bg); }
.form-hole.checkbox { border-radius: 50%; }
.form-list { max-height: 34rem; overflow-y: auto; }
.form-list .row { display: flex; gap: 6px; align-items: baseline;
                  padding: 5px 6px; border-bottom: 1px solid var(--line); }
.form-list .row.on { background: var(--hover); }
.form-list .row .kind { font-size: 10px; text-transform: uppercase;
                        letter-spacing: .05em; color: var(--muted); min-width: 4.2rem; }
.form-list .row .cryptic { color: var(--muted); font-style: italic; }
.form-pages { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; }

.nowrap { white-space: nowrap; }
.chips { margin: 0 0 14px; }

/* --- slot binding ----------------------------------------------------------
   The sentence is the evidence for the decision, so it gets the width and the
   slot is drawn where it actually sits. Everything else on the row is small. */
.progress { margin: 0 0 18px; }
.progress .bar { height: 8px; border-radius: 4px; background: var(--line); overflow: hidden; }
.progress .bar span { display: block; height: 100%; background: var(--good);
  transition: width .2s ease; }
.progress p { margin: 6px 0 0; font-size: 13px; }

table.bindings td { vertical-align: middle; }
table.bindings .num { text-align: right; white-space: nowrap; width: 1%; }
table.bindings .sentence { font-size: 13px; line-height: 1.5; }
/* The words either side are normalised -- lowercased, markup replaced by `.`
   -- so they are shown quietly. They are a key, not copy. */
table.bindings .ctx { color: var(--muted); font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; }
table.bindings .hole { display: inline-block; margin: 0 6px; padding: 1px 7px;
  border-radius: 9px; background: var(--warn-bg); color: var(--warn);
  font-size: 11px; vertical-align: 1px; }
table.bindings .means { white-space: nowrap; font-size: 13px; }
table.bindings .pick select { max-width: 260px; padding: 4px 6px;
  border: 1px solid var(--line); border-radius: 5px;
  background: var(--card); color: var(--ink); font: inherit; font-size: 13px; }
table.bindings tr.is-bound .hole { background: transparent; color: var(--good); }
p.note { max-width: 70ch; font-size: 13px; }

/* --- mail ------------------------------------------------------------------
   An operations surface: scanned and acted on, not read. State is in the shape
   as well as the number, so what needs attention reads before the text does. */
.spine { display: flex; flex-wrap: wrap; gap: 0; margin: 0 0 22px;
  border: 1px solid var(--line); background: var(--card); }
.stage-dot { flex: 1 1 8rem; padding: 12px 14px; border-right: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 4px; position: relative; }
.stage-dot:last-child { border-right: 0; }
.stage-dot .dot { width: 9px; height: 9px; border-radius: 50%;
  border: 2px solid var(--line); background: transparent; }
.stage-dot .label { font-weight: 600; font-size: 14px; }
.stage-dot .state { font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); }
.stage-dot.done .dot { background: var(--good); border-color: var(--good); }
.stage-dot.done .state { color: var(--good); }
.stage-dot.running .dot { background: var(--accent); border-color: var(--accent); }
.stage-dot.running .state { color: var(--accent); }
.stage-dot.failed .dot { background: var(--warn); border-color: var(--warn); }
.stage-dot.failed .state { color: var(--warn); }

.figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1px; background: var(--line); border: 1px solid var(--line); margin-bottom: 20px; }
.figures > div { background: var(--card); padding: 12px 14px; }
.figures dt { font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); font-weight: 600; margin-bottom: 4px; }
.figures dd { margin: 0; font-size: 20px;
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-variant-numeric: tabular-nums; }
.figures.small dd { font-size: 16px; }

/* Three narrow code tables side by side on the validation census -- DPV,
   footnotes and NCOA read as one answer, and stacked they look like three
   unrelated sections. Collapses to one column before the tables start
   wrapping inside themselves. */
.columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 22px; align-items: start; margin-bottom: 20px; }
.columns h4 { margin: 0 0 8px; font-size: 13px; letter-spacing: .04em; }
.columns table { margin: 0; }

.banner.attention { border-left: 3px solid var(--warn); }
/* A refused action, shaped like `.banner.warn` because it is the same kind of
   thing: a sentence about why the last click did not do what it said. Red
   rather than amber -- nothing happened, as against something to look at. */
.banner.danger { background: var(--danger-bg); color: var(--danger);
  margin: 0 0 16px; border: 1px solid var(--danger); border-radius: 6px;
  padding: 10px 14px; }
.actions { display: flex; gap: 10px; align-items: center; margin: 0 0 20px; flex-wrap: wrap; }
.warn { color: var(--warn); }

.site { margin: 0 0 26px; }
.site-head { display: flex; align-items: center; gap: 10px; margin: 0 0 8px; }
.site-head h3 { margin: 0; }
.tag.reason { background: var(--warn-bg); color: var(--warn); }
td.addr { font-size: 13px; color: var(--muted); }
td.small, .mono.small { font-size: 11px; }
.banner.running-now { border-left: 3px solid var(--accent); }
.tag.running { background: var(--accent); color: #fff; }
form.inline { display: inline-flex; gap: 6px; align-items: center; margin: 0; }
form.inline select { padding: 5px 7px; border: 1px solid var(--line); border-radius: 5px;
  background: var(--card); color: var(--ink); font: inherit; font-size: 13px; }
.chip.chip-attention { border-color: var(--warn); color: var(--warn); }
.tag.good-tag { background: var(--good); color: #fff; }

/* --- what the engine did ---------------------------------------------------
   The command line and the vendor's own output, shown as they are. */
.invocation { border: 1px solid var(--line); background: var(--card);
  padding: 14px 16px; margin: 0 0 18px; }
.invocation.failed { border-left: 3px solid var(--warn); }
.invocation-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 10px; }
.invocation-head h3 { margin: 0; font-size: 15px; }
h4.sub { margin: 14px 0 6px; font-size: 12px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted); }
.panel { background: var(--bg); border: 1px solid var(--line); padding: 10px 12px;
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-size: 12px;
  line-height: 1.6; white-space: pre-wrap; word-break: break-all; overflow-x: auto;
  max-height: 22rem; overflow-y: auto; }
.panel.error { border-left: 3px solid var(--warn); color: var(--warn); }

/* --- the template editor ---------------------------------------------------
   The letter is drawn at print size: a page in inches, with the reserved areas
   where the address sheet lands and the body region it cannot reach. */
.editor { display: grid; grid-template-columns: auto 330px; gap: 22px; align-items: start; }
@media (max-width: 1100px) { .editor { grid-template-columns: 1fr; } }
.sheet-wrap { overflow: auto; }
.sheet { position: relative; background: var(--sheet); color: var(--sheet-ink);
         box-shadow: 0 2px 18px rgba(0,0,0,.28);

  /* The sheet is PAPER. It is white with dark ink in both themes, because that
     is what prints -- so every token used inside it is redefined here to its
     light value and stops following the theme.

     Without this, dark mode inverted everything drawn on a page that stayed
     white: the merge-field chips went dark-on-white, the zone washes went
     muddy, and focusing a paragraph to edit it painted a black bar across the
     line. Redefining the tokens rather than rewriting the rules means anything
     added to the sheet later gets this for free. */
  --ink: #14181f; --muted: #5f6b7c; --line: #e2e6ec; --hover: #fafbfd;
  --accent: #1d4ed8; --chip: #eef2f7; --chip-ink: #334155;
  --warn: #b45309; --warn-bg: #fef3c7;
  --fixed: #c0392b; --fixed-ink: #a5301f; --fixed-wash: rgba(192,57,43,.09);
  --movable: #2f7d4f; --movable-wash: rgba(47,125,79,.06);
  --hole: #b07b26; --hole-bg: #fff5e3; --hole-ink: #7a5310;
  --named: #2f7d4f; --named-bg: #e6f4ea; --named-ink: #1d5c37;
}
.zone { position: absolute; border-radius: 2px; pointer-events: none; }
.zone.postal { border: 1px solid var(--fixed); background:
    repeating-linear-gradient(45deg, var(--fixed-wash) 0 6px, transparent 6px 12px); }
.zone.brand { border: 1px dashed var(--movable); background: var(--movable-wash); }
/* The label sits ABOVE the box, not in it. A reserved area is the one thing on
   this page whose edges mean something -- nothing may move into it and nothing
   printed may spill out of it -- so a caption inside it reads as content that
   will print, and a caption that wraps reads as content about to go wrong. */
.zlabel { position: absolute; bottom: 100%; left: 0; margin-bottom: 2px;
          font-size: 9px; line-height: 1.2; white-space: nowrap;
          letter-spacing: .04em; text-transform: uppercase; }
.zone.postal .zlabel { color: var(--fixed-ink); }
.zone.brand .zlabel { color: var(--movable); }
/* The same typography the composer sets, because the whole reason to draw the
   page at print size is seeing what will print. These numbers are measured, not
   chosen: the Riverside proof's spans are ArialMT at 10pt, 1,000 of the 1,289
   across 60 pages. If `mailproc.compose.HOUSE` moves, this moves with it. */
.letter-body { position: absolute; overflow: hidden;
               font: 10pt/1.35 Arial, Helvetica, sans-serif; }
.letter-body p { margin: 0 0 6pt; }

/* The heading, as Inspire set it: bold and centred. The size is 11pt rather
   than the body's 10 -- a deliberate departure, so it stands out. */
.letter-body p.title { font-family: Arial, Helvetica, sans-serif; font-weight: 700;
                       font-size: 11pt; text-align: center; margin-bottom: 10pt; }
button.hole { font: inherit; cursor: pointer; border-radius: 3px; padding: 0 4px;
              border: 1px dashed var(--hole); background: var(--hole-bg); color: var(--hole-ink); }
button.hole.named { border: 1px solid var(--named); background: var(--named-bg); color: var(--named-ink);
                    font-family: ui-monospace, Consolas, monospace; font-size: .9em; }
button.hole.here { outline: 2px solid var(--accent); outline-offset: 1px; }
.layout-note { margin-top: 8px; }
.editor aside { position: sticky; top: 14px; border: 1px solid var(--line);
                border-radius: 10px; padding: 14px 16px; }
.editor aside h3 { margin: 0 0 6px; font-size: 14px; }
.sentence { font-size: 13px; color: var(--muted); margin: 0 0 12px;
            border-left: 2px solid var(--line); padding-left: 9px; }
.suggestion { border: 1px solid var(--named); border-radius: 8px; padding: 9px 11px;
              margin-bottom: 12px; }
.spread { border: 1px solid var(--accent); border-radius: 8px; padding: 10px 12px;
          margin-top: 12px; font-size: 13px; }
.row { display: flex; gap: 6px; margin: 8px 0 0; flex-wrap: wrap; }
#search { width: 100%; box-sizing: border-box; margin-bottom: 8px; }
#list { max-height: 280px; overflow: auto; border: 1px solid var(--line);
        border-radius: 8px; }
#list .g { font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
           color: var(--muted); padding: 7px 10px 3px; }
#list button { display: block; width: 100%; text-align: left; border: 0;
               background: none; padding: 5px 10px; font: inherit; cursor: pointer;
               color: inherit; }
#list button:hover { background: var(--chip); }
#list .n { font-family: ui-monospace, Consolas, monospace; font-size: .9em; }
#list .l { color: var(--muted); font-size: 12px; margin-left: 6px; }
#float { position: absolute; z-index: 9; }
.small { font-size: 12px; }
.progress-line { font-size: 13px; color: var(--muted); }

/* --- making a district, and a letter --------------------------------------- */
form.stack fieldset { border: 1px solid var(--line); border-radius: 10px;
                      padding: 12px 16px; margin: 0 0 16px; }
form.stack legend { padding: 0 6px; font-size: 13px; color: var(--muted); }
.chiplabel { display: inline-flex; align-items: baseline; gap: 5px; padding: 4px 9px;
             border: 1px solid var(--line); border-radius: 999px; margin: 0 6px 6px 0; }
form.paste textarea { width: 100%; box-sizing: border-box; font: inherit;
                      border: 1px solid var(--line); border-radius: 8px; padding: 10px; }

/* Layout mode. Visible from across the room, so nobody leaves it unlocked. */
.layoutbar { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
             margin-top: 12px; padding: 9px 12px; border: 1px solid var(--line);
             border-radius: 8px; }
.sheet.unlocked { outline: 3px solid var(--warn); }
.sheet.unlocked .zone.brand { pointer-events: auto; cursor: move;
                              border-style: solid; border-width: 2px;
                              border-color: var(--warn); }

/* The district's logo, drawn at the size it prints. `contain`, never `cover`:
   a squashed logo on a letter is a complaint from the district. */
.logo-img { width: 100%; height: 100%; object-fit: contain; display: block; }

/* A district's logo and signatures. Sized so a wall of 40 signatures is
   scannable rather than a scroll. */
.assets { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 14px; }
.asset { margin: 0; width: 190px; border: 1px solid var(--line); border-radius: 8px;
         padding: 8px; }
.asset img { width: 100%; height: 72px; object-fit: contain; background: var(--sheet); }
.asset .missing { height: 72px; display: flex; align-items: center; justify-content: center;
                  color: var(--muted); font-size: 12px; border: 1px dashed var(--line); }
.asset figcaption { font-size: 12px; margin-top: 6px; word-break: break-all; }

/* The library pager. `off` is a link that would go nowhere -- still a link, so
   nothing moves when it becomes live again. */
.pager { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
         margin-top: 14px; }
.pager .spacer { flex: 1; }
.pager .chip.off { opacity: .4; pointer-events: none; }

/* Composed at print: the signature and the signer's lines. They are not in the
   stored body -- like the address sheet, they are added during processing --
   so they are drawn in the flow where they land, and marked as not yours. */
/* The signature and the note sit side by side: the block is page content and
   the note is about the page, and stacking them made the second read as copy
   that would print. The paper to the right of a 2.1in signature is empty, so
   that is where the explanation goes. */
.composed { margin-top: 14px; padding-left: 10px; display: flex; gap: 16px;
            align-items: flex-start; border-left: 2px dashed var(--line); }
.composed .composed-block { flex: 0 0 auto; }
.composed .sig { display: block; width: 2.096in; height: .885in;
                 object-fit: contain; }
.composed .sig.none { display: flex; align-items: center; font-size: 11px;
                      color: var(--muted); border: 1px dashed var(--line); }
.composed .signer { margin: 4px 0 0; line-height: 1.9; }

/* A merge field that is not a token in the body: the signer's name and title
   are filled from the notification like any other field, but they are composed
   at print rather than stored, so there is nothing to click. Same chip as a
   named hole, without the pointer. */
.field { border: 1px solid var(--named); background: var(--named-bg);
         color: var(--named-ink); border-radius: 3px; padding: 0 4px;
         font-family: ui-monospace, Consolas, monospace; font-size: .82em; }
/* On the panel now, not on the sheet, so it is sized for the panel: the inch
   width and the italics were there to keep it from reading as copy beside the
   signature, and off the page it needs neither. */
.composed-note { margin: 0 0 14px; padding-bottom: 14px; font-size: 12px;
                 line-height: 1.5; color: var(--muted);
                 border-bottom: 1px solid var(--line); }

/* Editing, in place. The paragraph stays a paragraph and stays where it is --
   the reason to draw the page at print size is seeing the notification while
   you change it. Only the caret and a quiet rule say you are in the mode. */
.letter-body p.editing { outline: none; border-left: 2px solid transparent;
                         padding-left: 6px; margin-left: -8px; }
.letter-body p.editing:hover { border-left-color: var(--line); }
.letter-body p.editing:focus { border-left-color: var(--accent);
                               background: var(--hover); }
.savebar { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
           margin-top: 12px; padding: 9px 12px; border: 1px solid var(--accent);
           border-radius: 8px; font-size: 13px; }
.savebar .spacer { flex: 1; }

/* What changed between two versions. */
.diff p { margin: 0 0 6px; padding: 4px 8px; border-radius: 6px; font-size: 14px; }
.diff .same { color: var(--muted); }
.diff .added { background: var(--good-bg); }
.diff .removed { background: var(--danger-bg); }
.diff .changed { background: var(--warn-bg); }
.diff .mark { font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
              color: var(--muted); margin-right: 6px; }
.diff del { text-decoration: line-through; opacity: .7; }
.diff ins { text-decoration: none; font-weight: 600; }
table.versions td { padding: 4px 6px; vertical-align: top; }
table.versions tr.here { background: var(--hover); }

/* The district's return address, drawn inside the overlay band. Small and
   quiet: it is a sample from the feed at an estimated position, and it should
   not read as a measured fact. */
/* What the block actually holds, drawn at the size it prints: 8pt on 9.9,
   which is what 0.8334in of block and five rows come to. `inset` and the clip
   are the guarantee -- whatever goes in here, nothing crosses the red line. */
.zone .fill { position: absolute; inset: 0; overflow: hidden; }
/* 9.6pt of leading where the composer sets 9.9. On paper the five rows come to
   0.8325in inside a 0.8334in block -- nine ten-thousandths of an inch of slack,
   which a printer holds and a browser cannot: at some zoom levels the last row
   landed within a third of a pixel of the red line, and a row touching that
   line reads as a piece about to print wrong. The rows here are a third of a
   point tighter so there is visible air at every zoom. */
.sample-return { font: 8pt/9.6pt Arial, sans-serif; color: var(--sheet-ink); }

/* The barcode, to scale: 65 bars, 0.020in wide, 22 to the inch, 0.145in tall.
   A stand-in for the bars the sort assigns -- the real ones cannot exist until
   a piece does -- but at the size and in the place they will occupy. */
.zone .bars { height: 0.145in; width: 2.929in;
              background: repeating-linear-gradient(90deg,
                  var(--fixed-ink) 0 0.02in, transparent 0.02in 0.04545in); }
.sample-return.none { font-style: italic; color: var(--fixed-ink); }

/* The guardian's address, shown as the merge fields that fill it. Fainter than
   the return address, which is a real value from the feed -- these are names of
   things that arrive per notification. */
.sample-return.muted-sample { color: var(--muted); font-style: normal; }

/* Replacing an image in place. Deliberately small: the common case is new
   bytes under a name that does not change, and it should not look like a
   decision. */
.replace { margin-top: 4px; font-size: 11px; }
.replace input[type=file] { width: 100%; font-size: 10px; }

/* Which build is running. Quiet, and always there: "is the fix deployed?"
   should not be answered by recognising a font in a screenshot. */
.build { margin: 28px 0 0; font-size: 11px; color: var(--muted); text-align: right;
         font-family: ui-monospace, Consolas, monospace; }

/* --- running a batch by hand ---------------------------------------------------- */
/* The ids are pasted out of a grid as often as typed, so a column of them does
   not have to be joined up first. */
form.batchform { margin: 14px 0 18px; max-width: 560px; }
form.batchform textarea { width: 100%; box-sizing: border-box; font: 13px/1.5
    ui-monospace, Consolas, monospace; border: 1px solid var(--line);
    border-radius: 6px; padding: 8px; margin: 4px 0 8px; resize: vertical; }
tr.quiet td { opacity: .55; }
.ok { color: var(--movable); font-weight: 600; }

/* The two actions, kept apart on purpose. One makes a PDF; the other puts mail
   in the post and can only ever be done once for a batch. They should not read
   as two settings of the same button. */
.actions-panel { margin: 18px 0; padding: 12px 14px; border: 1px solid var(--line);
                 border-radius: 8px; }
.actions-panel h3 { margin: 0 0 4px; font-size: 14px; }
.actions-panel p { margin: 0 0 10px; }
.actions-panel.danger { border-color: var(--fixed); background: var(--fixed-wash); }
.actions-panel .choice { display: flex; gap: 8px; align-items: flex-start;
                         margin: 0 0 8px; font-size: 13px; max-width: 640px; }
button.danger { background: var(--fixed); border-color: var(--fixed); color: #fff; }
button.danger:disabled { opacity: .5; }

/* --- campaigns ------------------------------------------------------------ */
/* The campaign's own piece, which every district's copy is made from. Marked
   rather than merely first: the row means something different from the ones
   under it. */
tr.default-piece td { background: var(--hover); }
.crumbs { font-size: 13px; color: var(--muted); margin: 0 0 4px; }
.crumbs a { color: var(--muted); }

/* A panel being edited. Narrower than a notification because a panel is a
   block on a design rather than a page of copy, and giving it the width of a
   letter makes every line look wrong. */
.panelbody { max-width: 5.2in; border: 1px solid var(--line); border-radius: 6px;
             padding: 10px 12px; background: var(--sheet); color: var(--sheet-ink);
             font: 10pt/1.45 Arial, Helvetica, sans-serif; }
.panelbody p { margin: 0 0 8pt; }
.panelbody:focus { outline: 2px solid var(--accent); outline-offset: 2px; }

/* A campaign piece is panels on a sheet. Inspire lays them out and those
   coordinates are not in the store, so they are stacked in reading order and
   labelled -- what is known, drawn as what is known. */
/* Panels run together as one document, because that is what they are: the
   letter, in order. The label is for editing -- when somebody is changing
   words they need to know which block they are in, and when they are reading
   they do not. */
.piece-panel { margin: 0 0 6pt; }
.piece-panel-label { display: none; font-size: 9px; letter-spacing: .04em;
               text-transform: uppercase; color: var(--muted); margin: 0 0 3px;
               font-family: ui-monospace, Consolas, monospace; }
.piece-panel.editing .piece-panel-label { display: block; }
.piece-panel.editing { border-left: 2px solid var(--line); padding-left: 8px;
                 margin-left: -10px; }
/* `.letter-body` is `position: absolute; overflow: hidden` because a
   notification's body is one block placed on a page. A campaign panel is a
   block in a flow of them, so both have to go -- without this each panel drew
   as a tall scrolling box with one line at the bottom of it. */
.piece-copy { position: static; overflow: visible; height: auto; }
.piece-panel.editing .piece-copy { outline: none; border-left: 2px solid transparent;
                             padding-left: 6px; margin-left: -8px; }
.piece-panel.editing .piece-copy:hover { border-left-color: var(--line); }
.piece-panel.editing .piece-copy:focus { border-left-color: var(--accent);
                                   background: var(--hover); }
.panelsave { margin: 4px 0 0; font-size: 12px; }

/* The sheet holds the panels directly, so it needs the padding a letter's body
   region gave it. */
.sheet .piece-panel { margin-left: .6in; margin-right: .6in; }
.sheet .piece-panel:first-of-type { margin-top: .5in; }
.unsized-note { position: absolute; top: 8px; left: 8px; right: 8px;
                font-size: 11px; padding: 6px 8px; border-radius: 6px;
                background: var(--warn-bg); color: var(--warn); }

.field-option { display: block; width: 100%; text-align: left; border: 0;
                background: none; padding: 3px 4px; cursor: pointer; font: inherit;
                border-radius: 4px; }
.field-option:hover { background: var(--hover); }
.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
           margin: 0 0 12px; }

/* A campaign piece's panels are the whole master -- both languages, every
   block -- and Inspire's coordinates are not in the store, so they cannot be
   put where they belong. Fifteen panels of a real one come to 45 inches of
   copy: a fixed 8.5 x 11 box either clips it or is overflowed by it, and both
   read as broken.
   
   So the sheet keeps the trim WIDTH, which is what makes the line lengths
   true, and grows down. A dashed rule marks where the page would end. */
.sheet.grows { height: auto !important; min-height: 2in; padding-bottom: 24px; }
.trim-mark { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--fixed);
             pointer-events: none; }
.trim-mark span { position: absolute; right: 4px; top: 2px; font-size: 9px;
                  letter-spacing: .04em; text-transform: uppercase;
                  color: var(--fixed-ink); background: var(--sheet); padding: 0 4px; }


/* --- approvals --------------------------------------------------------------
   Two audiences, and only one of them works here.

   The staff block sits under the editor and looks like the rest of the
   application. The review page does not: it is the one screen somebody outside
   the company ever sees, it arrives from an email, and it is as likely to be
   read on a phone in a car park as at a desk. So it is one column, generously
   set, with the two buttons large enough to hit with a thumb. */
.approval { margin-top: 28px; border-top: 1px solid var(--line); padding-top: 12px; }
.approval h2 { margin: 0 8px 0 0; font-size: 15px; }
.approval form.bar { gap: 10px; align-items: end; flex-wrap: wrap; }
.approval form.bar label { display: flex; flex-direction: column; gap: 2px; }
.approval table.small { margin-top: 8px; }
.pill.ok { background: var(--good-bg); color: var(--good); }

/* The link, shown once. Wide and monospaced because the thing people do with
   it is select it, and a wrapped token that looks like two lines gets copied
   as one. */
.token { display: flex; gap: 8px; align-items: center; margin: 12px 0; }
.token input { flex: 1; font-family: var(--mono, ui-monospace, monospace); font-size: 13px;
               padding: 9px 10px; border: 1px solid var(--accent); border-radius: 8px;
               background: var(--card); color: var(--ink); }

body.review-page { background: var(--bg); }
main.review { max-width: 42rem; margin: 0 auto; padding: 28px 16px 64px; }
.review-brand { margin: 0 0 4px; font-size: 12px; letter-spacing: .08em;
                text-transform: uppercase; color: var(--muted); }
main.review h1 { margin: 0 0 4px; font-size: 24px; line-height: 1.25; }
main.review h2 { font-size: 16px; margin: 24px 0 8px; }
main.review section { margin-top: 28px; }

/* The answer, when there is one. First thing on the page, because arriving at
   a link that has already been used should take one line to understand. */
.verdict { border: 1px solid var(--line); border-left: 3px solid var(--muted);
           border-radius: 8px; padding: 12px 14px; margin-top: 16px;
           background: var(--card); }
.verdict h2 { margin: 0 0 4px; }
.verdict p { margin: 0; }
.verdict.approved { border-left-color: var(--good); background: var(--good-bg); }
.verdict.changes { border-left-color: var(--warn); background: var(--warn-bg); }
.verdict.stale { border-left-color: var(--danger); background: var(--danger-bg); }
.verdict blockquote { margin: 8px 0 0; padding-left: 10px;
                      border-left: 2px solid var(--line); color: var(--muted); }

/* The letter itself. Not the composed page -- a reviewer is approving words,
   and a 8.5x11 sheet scaled to a phone is words nobody can read. */
.sheet.plain { position: static; width: auto; height: auto; padding: 22px 24px;
               box-shadow: none; border: 1px solid var(--line); border-radius: 8px;
               font-size: 15px; line-height: 1.55; }
.sheet.plain p { margin: 0 0 12px; }
.sheet.plain p.title { font-size: 17px; font-weight: 700; margin-bottom: 14px; }
.sheet.plain .field { font-size: .92em; }

.decide label { display: block; margin-bottom: 12px; font-weight: 600; }
.decide input, .decide textarea { display: block; width: 100%; margin-top: 4px;
    padding: 10px; font: inherit; font-weight: 400; border: 1px solid var(--line);
    border-radius: 8px; background: var(--card); color: var(--ink); }
.decide .buttons { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 4px; }
.decide .buttons .button { padding: 12px 18px; font-size: 15px; }
.button.quiet { background: var(--card); color: var(--ink); border: 1px solid var(--line); }
main.review .build { margin-top: 40px; text-align: left; }

@media (max-width: 480px) {
  main.review { padding: 18px 16px 48px; }
  .decide .buttons .button { width: 100%; }
}

/* A send that worked. `.banner.warn` already covers the one that did not. */
.banner.good { background: var(--good-bg); color: var(--good); border-color: var(--good); }
.approval label.check { flex-direction: row; align-items: center; gap: 5px; }

/* What a district actually said. Full width under the verdict rather than
   truncated beside it: the note is the work -- "use families rather than
   parents" -- and a screen that cuts it off sends somebody into the editor to
   read one sentence. */
.said { margin-top: 4px; padding: 6px 8px; border-left: 2px solid var(--line);
        background: var(--hover); border-radius: 0 6px 6px 0; font-size: 13px;
        max-width: 34rem; white-space: pre-wrap; }
.bar.views { gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
.bar.views .pill { margin-left: 4px; }

/* A refusal the editor works out for itself, beside the field list rather than
   in an alert: the notification is the thing being looked at. */
.said { margin: 0 0 10px; padding: 8px 10px; font-size: 12px; line-height: 1.45;
        border: 1px solid var(--bad-line, #d99); border-radius: 4px;
        background: var(--bad-bg, #fdf3f3); color: var(--bad-ink, #833); }

/* --- the boxes beside the copy -------------------------------------------------
   Drawn on the sheet at the rectangle the COMPOSER will use, so what is on the
   screen is what prints. A box nobody has placed sits at its role's default and
   is marked: the difference between "somebody put it here" and "we guessed"
   is the whole reason `placed` exists. */
.zone.block { position: absolute; border: 1px solid var(--block-line, #a35bb0);
              background: var(--block-fill, rgba(163, 91, 176, .05));
              overflow: hidden; }
.zone.block.unplaced { border-style: dashed; }
/* The bar at the top is the drag handle. It used to be a floating label and
   the only grabbable part of the box was five pixels of border, because the
   copy covers the rest so that typing works. */
.zone.block .zlabel { position: absolute; top: 0; left: 0; right: 0; height: 14px;
                      font-size: 9px; line-height: 14px; padding: 0 4px;
                      text-transform: lowercase; cursor: move;
                      background: var(--block-line, #a35bb0); color: #fff; }
.zone.block .block-copy { position: absolute; inset: 16px 5px 5px;
                          font-size: 8pt; line-height: 1.45; overflow: auto; }
.zone.block .block-copy p { margin: 0 0 .5em; }
.zone.block .block-copy[contenteditable="true"]:focus { outline: 1px solid var(--accent, #2f6fdb); }
.zone.block .grip { position: absolute; right: 0; bottom: 0; width: 10px;
                    height: 10px; cursor: nwse-resize;
                    background: var(--block-line, #a35bb0); opacity: .55; }
.sheet.unlocked .zone.block { cursor: move; }

/* Adding one, and which languages already have one. */
.boxes { margin: 0 0 14px; padding-bottom: 14px;
         border-bottom: 1px solid var(--line); }
.boxes h3 { margin: 0 0 6px; font-size: 14px; }
.boxes .addbox { display: flex; align-items: baseline; gap: 8px; margin: 4px 0; }
.boxes .addbox button { font-size: 12px; padding: 2px 8px; }
.boxes .lacking { color: var(--bad-ink, #833); }
.hidden-form { display: none; }

/* Emphasis, by the paragraph. The editor shows what will print: the composer
   swaps to the bold face for the one `insert_textbox` call the paragraph
   already takes, which is why this costs nothing in layout. */
.letter-body .copy p.em-bold { font-weight: 700; }
.letter-body .copy p.em-italic { font-style: italic; }
#float .em { margin-left: 4px; min-width: 26px; padding: 2px 6px; }
