/* Eumaeus dashboard.
 *
 * The palette is defined once as custom properties and referenced by role
 * everywhere else, including inside the server-rendered SVG — which is why the
 * charts follow the theme without the Go code knowing a single hex value.
 *
 * Dark mode is a selected set of steps for the dark surface, not an automatic
 * inversion: a categorical hue that clears contrast on near-white does not
 * necessarily clear it on near-black, so each one is re-picked.
 *
 * There is no theme toggle, deliberately. A toggle needs JavaScript, and the
 * page ships a Content-Security-Policy of default-src 'none' with no script-src
 * at all. Following the operating system is worth more than the switch.
 */

:root {
  color-scheme: light;

  --surface-1: #fcfcfb;
  --page: #f9f9f7;
  --text-primary: #0b0b0b;
  --text-secondary: #52514e;
  --text-muted: #898781;
  --grid: #e1e0d9;
  --axis: #c3c2b7;
  --border: rgba(11, 11, 11, 0.1);

  /* Categorical slots 1 and 2. The order is the colourblind-safety
   * mechanism, not a cosmetic choice — these two are separated far enough to
   * stay distinguishable under deuteranopia and protanopia. */
  --series-1: #2a78d6;
  --series-2: #eb6834;

  --pos: #006300;
  --neg: #52514e;
  --warn-bg: #fdf6e3;
  --warn-border: #eda100;
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    --surface-1: #1a1a19;
    --page: #0d0d0d;
    --text-primary: #ffffff;
    --text-secondary: #c3c2b7;
    --text-muted: #898781;
    --grid: #2c2c2a;
    --axis: #383835;
    --border: rgba(255, 255, 255, 0.1);

    --series-1: #3987e5;
    --series-2: #d95926;

    --pos: #0ca30c;
    --neg: #c3c2b7;
    --warn-bg: #2a2418;
    --warn-border: #c98500;
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  background: var(--page);
  color: var(--text-primary);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 15px;
  line-height: 1.5;
}

/* Topbar ------------------------------------------------------------------ */

.topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 1.5rem;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface-1);
}

.brand {
  font-weight: 600;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  text-decoration: none;
}

.topbar nav {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
}

.topbar nav a {
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.9rem;
  padding-bottom: 2px;
  border-bottom: 2px solid transparent;
}

.topbar nav a:hover {
  color: var(--text-primary);
}

.topbar nav a.current {
  color: var(--text-primary);
  border-bottom-color: var(--series-1);
}

main {
  max-width: 880px;
  margin: 0 auto;
  padding: 1.5rem;
}

footer {
  max-width: 880px;
  margin: 0 auto;
  padding: 0 1.5rem 3rem;
  color: var(--text-muted);
  font-size: 0.8rem;
}

/* Panels ------------------------------------------------------------------ */

.panel {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.25rem;
}

.panel h2 {
  margin: 0 0 1rem;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.empty-state h2 {
  font-size: 1.1rem;
}

.empty-state p {
  color: var(--text-secondary);
}

pre {
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.75rem 1rem;
  overflow-x: auto;
  font-size: 0.85rem;
}

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* Hero — exactly one per view --------------------------------------------- */

.hero {
  text-align: left;
}

.hero-label {
  margin: 0;
  color: var(--text-secondary);
  font-size: 0.85rem;
}

/* Proportional figures, not tabular: at display sizes tabular-nums gives every
 * digit the width of a zero and the number reads loose. */
.hero-value {
  margin: 0.25rem 0;
  font-size: 3rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.hero-note {
  margin: 0;
  color: var(--text-secondary);
  font-size: 0.85rem;
}

/* Stat tiles -------------------------------------------------------------- */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}

.tile {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.9rem 1rem;
}

.tile-label {
  margin: 0;
  color: var(--text-secondary);
  font-size: 0.78rem;
}

.tile-value {
  margin: 0.15rem 0 0;
  font-size: 1.4rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* Filters ----------------------------------------------------------------- */

.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin-bottom: 1.25rem;
}

.filters label {
  color: var(--text-secondary);
  font-size: 0.8rem;
}

.filters select,
.filters input {
  background: var(--surface-1);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.35rem 0.5rem;
  font: inherit;
  font-size: 0.85rem;
}

.filters button {
  background: var(--series-1);
  color: #ffffff;
  border: 0;
  border-radius: 6px;
  padding: 0.4rem 0.9rem;
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.filters .reset {
  color: var(--text-secondary);
  font-size: 0.85rem;
}

/* Charts ------------------------------------------------------------------ */

.chart-wrap {
  overflow-x: auto;
}

.chart {
  width: 100%;
  height: auto;
  min-width: 480px;
  display: block;
}

.chart .grid {
  stroke: var(--grid);
  stroke-width: 1;
}

.chart .axis {
  stroke: var(--axis);
  stroke-width: 1;
}

/* Chart text wears text tokens, never a series colour: a light categorical
 * hue is illegible as type on the surface. Identity comes from the coloured
 * mark beside the label. */
.chart .tick,
.chart .value,
.chart .empty {
  fill: var(--text-muted);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.chart .tick-y {
  text-anchor: end;
}

.chart .tick-x {
  text-anchor: middle;
}

.chart .tick-row {
  text-anchor: end;
  fill: var(--text-secondary);
  font-variant-numeric: normal;
}

.chart .value {
  fill: var(--text-secondary);
}

.chart .empty {
  fill: var(--text-muted);
  font-size: 13px;
}

.chart .mark {
  cursor: default;
}

.chart .mark:hover path {
  opacity: 0.82;
}

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  list-style: none;
  margin: 0 0 0.5rem;
  padding: 0;
  font-size: 0.8rem;
  color: var(--text-secondary);
}

.legend li {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.swatch {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  display: inline-block;
}

/* Tables ------------------------------------------------------------------ */

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.87rem;
}

thead th {
  text-align: left;
  font-weight: 600;
  font-size: 0.78rem;
  color: var(--text-secondary);
  padding: 0.4rem 0.5rem;
  border-bottom: 1px solid var(--border);
}

tbody td {
  padding: 0.45rem 0.5rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

tbody tr:last-child td {
  border-bottom: 0;
}

/* Columns of figures align on the digit, which is what tabular-nums is for. */
.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.nowrap {
  white-space: nowrap;
}

.pos {
  color: var(--pos);
}

.neg {
  color: var(--neg);
}

.muted {
  color: var(--text-muted);
  font-weight: 400;
}

.raw {
  display: block;
  color: var(--text-muted);
  font-size: 0.76rem;
  margin-top: 0.1rem;
}

.tag {
  display: inline-block;
  font-size: 0.7rem;
  color: var(--text-secondary);
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.05rem 0.5rem;
  white-space: nowrap;
}

.note {
  color: var(--text-secondary);
  font-size: 0.82rem;
  margin: 0.75rem 0 0;
}

.note.warn {
  background: var(--warn-bg);
  border-left: 3px solid var(--warn-border);
  padding: 0.6rem 0.85rem;
  border-radius: 0 6px 6px 0;
}

.more {
  margin: 0.9rem 0 0;
  font-size: 0.85rem;
}

.more a,
.filters .reset {
  color: var(--series-1);
  text-decoration: none;
}

.table-view {
  margin-top: 1rem;
}

.table-view summary {
  cursor: pointer;
  color: var(--text-secondary);
  font-size: 0.82rem;
}

.table-view[open] summary {
  margin-bottom: 0.6rem;
}

/* Table view is not decoration: it is how the figures stay reachable for a
 * screen reader, for anyone who cannot separate the hues, and for print. */
@media print {
  .table-view[open] > summary,
  .table-view > summary {
    display: none;
  }

  .table-view > table {
    display: table;
  }
}

/* Source coverage ---------------------------------------------------------- */

.dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-right: 0.4rem;
  vertical-align: baseline;
}

/* Status is never carried by colour alone: the dot is accompanied by a
 * screen-reader word, and every cell shows a glyph as well as a hue. */
.dot-ok {
  background: var(--pos);
}

.dot-bad {
  background: var(--series-2);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

td.mon,
th.mon {
  text-align: center;
  padding-left: 0.2rem;
  padding-right: 0.2rem;
}

.cell {
  display: inline-block;
  min-width: 1.6rem;
  padding: 0.1rem 0.25rem;
  border-radius: 4px;
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
  border: 1px solid transparent;
}

.cell-data {
  background: color-mix(in oklab, var(--pos) 16%, var(--surface-1));
  border-color: color-mix(in oklab, var(--pos) 34%, var(--surface-1));
  color: var(--text-primary);
}

.cell-attested {
  background: color-mix(in oklab, var(--series-1) 12%, var(--surface-1));
  border-color: color-mix(in oklab, var(--series-1) 30%, var(--surface-1));
  color: var(--text-secondary);
}

.cell-missing {
  background: color-mix(in oklab, var(--series-2) 18%, var(--surface-1));
  border-color: color-mix(in oklab, var(--series-2) 42%, var(--surface-1));
  color: var(--text-primary);
  font-weight: 600;
}

.cell-pending {
  background: var(--page);
  border-color: var(--border);
  color: var(--text-muted);
}

.legend-cells {
  margin-top: 1rem;
  gap: 1.25rem;
}

.legend-cells .cell {
  margin-right: 0.35rem;
}

tr.detail td {
  border-bottom: 0;
  padding-top: 0;
  font-size: 0.78rem;
}

form.inline {
  display: inline;
  margin: 0;
}

button.quiet {
  background: var(--surface-1);
  color: var(--text-secondary);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.2rem 0.6rem;
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
  white-space: nowrap;
}

button.quiet:hover {
  color: var(--text-primary);
  border-color: var(--axis);
}

.note.ok {
  background: color-mix(in oklab, var(--pos) 10%, var(--surface-1));
  border-left: 3px solid var(--pos);
  padding: 0.6rem 0.85rem;
  border-radius: 0 6px 6px 0;
}

.dot-odd {
  background: var(--warn-border);
}

/* Snapshot collection ------------------------------------------------------ */

.dot-todo {
  background: var(--surface-1);
  border: 1px solid var(--axis);
}

input.amount {
  width: 7rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
  background: var(--surface-1);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.25rem 0.4rem;
  font: inherit;
  font-size: 0.85rem;
}

input.amount:focus {
  border-color: var(--series-1);
  outline: none;
}

/* A note the holder wrote against a line, printed under the label it explains.
   Under rather than beside, because the figures column has to stay a column: a
   note long enough to be worth writing would otherwise push the amounts about
   from one row to the next. */
.line-note {
  display: block;
  color: var(--text-secondary);
  font-size: 0.82rem;
  font-style: italic;
}

.line-note::before { content: "— "; }

.notes-form input[type="text"] {
  width: 100%;
  min-width: 14rem;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-primary);
  padding: 0.3rem 0.5rem;
  font: inherit;
}

.notes-form input[type="text"]:focus {
  border-color: var(--series-1);
  outline: none;
}

/* Styled like the filter bar's buttons rather than left to the browser: this is
   the one control on the page that changes what the statement says, and a
   default grey button reads as a debug affordance. */
.notes-form button {
  background: var(--series-1);
  color: #ffffff;
  border: 0;
  border-radius: 6px;
  padding: 0.4rem 0.9rem;
  margin-right: 0.6rem;
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.notes-form td:first-child { white-space: nowrap; }

/* ---------------------------------------------------------------------------
   Drill-downs on the monthly statement.

   The statement itself is four figures and a category table, because that is
   what the person it is filed with asked for. Everything behind those figures
   is still on the page, folded away: a total nobody can take apart is a total
   nobody can check, and the one person who will ever need to check these is the
   one who wrote them down.

   Closed by default and never printed. On paper the folds would either vanish
   silently or unfold into a twelve-page statement, and both are worse than the
   one page that was asked for.
   --------------------------------------------------------------------------- */

.drill {
  margin: 1rem 0 0;
  border-top: 1px solid var(--border);
  padding-top: 0.75rem;
}

.drill > summary {
  cursor: pointer;
  color: var(--text-secondary);
  font-size: 0.82rem;
}

.drill[open] > summary {
  margin-bottom: 0.4rem;
}

/* Set apart from the statement's own headings: these are working material, and
   a reader scanning the page should be able to tell at a glance which of the
   two they are looking at. */
.drill h3 {
  margin: 1.25rem 0 0.5rem;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-secondary);
}

.drill table {
  font-size: 0.85rem;
}

.drill .via {
  font-weight: 400;
}

/* ---------------------------------------------------------------------------
   Printing a month's statement.

   The report page doubles as the thing handed to someone else, so print is a
   first-class layout rather than whatever the screen rules happen to produce on
   paper. Navigation, filters and the month-over-month comparison are dropped:
   the first two do nothing on paper, and the third is working material for the
   person keeping the books rather than part of the statement itself.
*/
.print-only { display: none; }

@media print {
  /* The brand bar goes too. On screen it is how you know which program you are
     looking at; on a statement filed with somebody else it is a shaded box
     above the letterhead, and the statement heading already says what this
     is. */
  .no-print, nav, header.topbar, header.masthead, footer { display: none !important; }
  .print-only { display: block; }

  /* Ink, not screen: a dark panel background costs a page of toner and makes
     the figures harder to read on paper than plain black on white. */
  body { background: #fff; color: #000; font-size: 11pt; }
  .panel { background: #fff; border: none; border-bottom: 1pt solid #ccc;
           box-shadow: none; padding: 0 0 0.6rem; margin: 0 0 0.9rem; break-inside: avoid; }
  .hero { border: none; }
  .hero-value { color: #000 !important; }
  table { width: 100%; border-collapse: collapse; }
  th, td { padding: 0.15rem 0; }
  .neg, .pos { color: #000 !important; }
  a { text-decoration: none; color: #000; }

  /* Notes print: the whole reason to write one is that the person reading the
     paper asks why a line is what it is. */
  .line-note { color: #000; }

  .statement-head { margin-bottom: 1rem; }
  .statement-head h1 { font-size: 15pt; margin: 0; }
  .statement-head p { margin: 0.2rem 0 0; }

  /* A table that breaks mid-figure across a page is unreadable, and these are
     short enough that keeping each whole costs nothing. */
  tr, section { break-inside: avoid; }
}

/* Any total row, not just the ones inside a statement table: a summed row that
   reads like one more line item is the one way a short table can mislead. */
tr.total td { border-top: 1px solid var(--rule, #444); font-weight: 600; }

/* ---------------------------------------------------------------------------
   Snapshot: how each balance arrived.

   A collected line says which route produced its figure; an outstanding one
   says which route produced it last time, which is the most useful thing a
   checklist can offer about a number nobody has found yet.
   --------------------------------------------------------------------------- */

.via {
  display: block;
  font-size: 0.76rem;
  color: var(--text-muted);
  margin-bottom: 0.1rem;
}

/* Outstanding lines read as a prompt rather than a record, so the label is
   set apart from the ones stating what actually happened. */
.via.waiting {
  color: var(--text-secondary);
  font-style: italic;
}

.routes > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

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

.routes > summary h2 {
  display: inline;
  margin: 0;
}

.routes > summary::after {
  content: "show";
  font-size: 0.76rem;
  color: var(--text-muted);
}

.routes[open] > summary::after { content: "hide"; }

.routes > summary:focus-visible {
  outline: 2px solid var(--series-1);
  outline-offset: 3px;
  border-radius: 3px;
}

.route-list {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

.route-list > li {
  border-left: 2px solid var(--grid);
  padding-left: 0.9rem;
}

.route-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}

/* The numbering is the run order, not decoration: these commands write over
   one another's figures, so the sequence is the point. */
.route-head .step {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.route-head code {
  font-size: 0.84rem;
  color: var(--text-primary);
}

.route-head .route-for {
  font-size: 0.76rem;
  color: var(--text-muted);
}

.route-list > li p {
  margin: 0.35rem 0 0;
  font-size: 0.82rem;
  color: var(--text-secondary);
  max-width: 68ch;
}

/* Stepping through the series: back on the left, the rounds either side on the
   right, so the two kinds of movement do not read as one list of four links. */
.series-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem 1.25rem;
  margin: 0 0 1rem;
}

.series-steps {
  display: flex;
  gap: 1.25rem;
}

/* An account held with someone else: the row shows the whole balance, so the
   share has to be visible on the row itself or the column will not appear to
   add up to the total beneath it. */
.via.shared {
  color: var(--text-secondary);
  font-weight: 500;
}

/* A category no person looked at, marked in the receipt folds.
   Only the derived ones carry a mark: a decision somebody made is shown plainly,
   as the settled thing it is, and it is the annotation on the automatic ones
   that makes the difference visible without decorating the whole table. */
.derived {
  font-size: 0.78rem;
  color: var(--text-secondary);
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  border-radius: 3px;
  padding: 0.05rem 0.35rem;
  white-space: nowrap;
}

/* Correcting a row from the fold that showed it was wrong.
   Collapsed to a pencil until asked for: a control on every receipt would
   compete with the figures, and the figures are what the page is for. */
.fix > summary {
  cursor: pointer;
  color: var(--text-muted);
  font-size: 0.9rem;
  list-style: none;
}

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

.fix > summary:hover,
.fix[open] > summary {
  color: var(--series-1);
}

.fix-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0 0.2rem;
}

.fix-form input[type="text"] {
  background: var(--page);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.3rem 0.45rem;
  font: inherit;
  font-size: 0.85rem;
}

.fix-form button {
  background: var(--series-1);
  color: #ffffff;
  border: 0;
  border-radius: 6px;
  padding: 0.3rem 0.7rem;
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}

/* "Always" writes a rule that reaches other rows, so it does not look like the
   button beside it that changes one. */
.fix-form button[value="always"] {
  background: transparent;
  color: var(--series-1);
  border: 1px solid var(--series-1);
}

.fix-form a {
  color: var(--text-secondary);
  font-size: 0.8rem;
}

/* The line a correction just landed on, so the eye finds it after the reload. */
.just-changed {
  border-left: 3px solid var(--series-1);
  padding-left: 0.5rem;
}

/* A form that is the point of its own page, rather than a filter above a table. */
.stacked-form label {
  display: block;
  margin-top: 0.9rem;
  font-weight: 600;
}

/* Every single-line field, not only type="text". The messaging setup form uses
 * tel, url and password, and a rule that named one type left three of them
 * rendered as browser defaults beside styled siblings. */
.stacked-form input[type="text"],
.stacked-form input[type="tel"],
.stacked-form input[type="url"],
.stacked-form input[type="password"],
.stacked-form textarea {
  width: 100%;
  max-width: 40rem;
  background: var(--page);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.45rem 0.55rem;
  font: inherit;
}

.stacked-form textarea {
  resize: vertical;
  min-height: 6rem;
  line-height: 1.45;
}

.stacked-form input:focus,
.stacked-form textarea:focus,
.stacked-form select:focus {
  border-color: var(--series-1);
  outline: none;
}

.form-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 1.2rem;
}

.form-actions button {
  background: var(--series-1);
  color: #ffffff;
  border: 0;
  border-radius: 6px;
  padding: 0.45rem 1rem;
  font: inherit;
  cursor: pointer;
}

/* What the holder wrote against one row: under it, quieter than it, and never
   on paper — the printed sheet carries the category notes, and per-row
   commentary would change what the statement is. */
tr.row-note td {
  color: var(--text-secondary);
  font-style: italic;
  font-size: 0.85rem;
  padding-top: 0;
  border-top: 0;
}

tr.row-note td::before { content: "— "; }

/* The raw bank text, in the correction fold. Screen-only by virtue of living
   inside a no-print <details>: the printed sheet wants the tidied payee, and the
   evidence is for whoever is deciding, not whoever receives it. */
.raw {
  margin: 0.6rem 0 0;
  color: var(--text-secondary);
  font-size: 0.78rem;
  line-height: 1.45;
  word-break: break-word;
}

.raw-label {
  display: block;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.68rem;
}

/* The triage screen: a dozen or two decisions on one page. */
.triage td { vertical-align: top; }
.triage td:first-child { white-space: nowrap; }

.triage select,
.new-category input[type="text"] {
  background: var(--page);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.3rem 0.45rem;
  font: inherit;
  font-size: 0.9rem;
}

/* Wide enough that the longest label does not truncate, and fixed so the column
   does not jump width as rows are decided. */
.triage select { width: 15rem; max-width: 100%; }

/* The per-row note box. Quiet by default and only as loud as its contents: most
   rows never get one, and fourteen bright boxes would compete with the decision
   the screen exists for. */
.triage input.row-note {
  display: block;
  width: 100%;
  max-width: 32rem;
  margin-top: 0.35rem;
  background: transparent;
  color: var(--text-secondary);
  border: 1px solid transparent;
  border-bottom: 1px dashed var(--border);
  border-radius: 0;
  padding: 0.15rem 0.1rem;
  font: inherit;
  font-size: 0.8rem;
}

.triage input.row-note:hover { border-bottom-color: var(--text-muted); }

.triage input.row-note:focus {
  background: var(--page);
  color: var(--text-primary);
  border: 1px solid var(--series-1);
  border-radius: 6px;
  padding: 0.3rem 0.45rem;
  outline: none;
}

.triage input.row-note::placeholder { color: var(--text-muted); opacity: 0.55; }

/* The one free-text box on the page, kept out of the table so it does not read
   as a per-row field. */
.new-category {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--border);
}

.new-category label { color: var(--text-secondary); font-size: 0.9rem; }
.new-category .muted { flex: 1 1 18rem; font-size: 0.8rem; }

.muted-inline {
  color: var(--text-muted);
  font-size: 0.8rem;
  margin-left: 0.4rem;
}

/* Why the box was pre-filled, in a sentence somebody can check. "The model said
   so" is not evidence, and a suggestion nobody can check is one nobody should
   accept in bulk. */
.because {
  display: block;
  color: var(--text-secondary);
  font-size: 0.78rem;
}

/* Whether the statement is fit to hand in. Coloured, because it is the one panel
   on the page that is asking to be acted on rather than read. */
.warn-panel {
  border-left: 3px solid var(--warn-border);
}

.checks {
  margin: 0.6rem 0 0;
  padding-left: 1.2rem;
}

.checks li { margin-bottom: 0.4rem; }

/* What was actually in the box, under the charge it was settled with. */
tr.row-items td {
  padding-top: 0;
  border-top: 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

/* Product names out of an order history run to a hundred characters — "NoCry
   Clear Shooting Glasses for Men with Anti-Fog and Scratch Resistant..." — so
   they are truncated to keep the table a table, with the whole name on hover and
   in the title for anyone who needs it. */
.item {
  display: inline-block;
  max-width: 22rem;
  margin-right: 0.9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* The monthly close, as a checklist on the round that brackets it. */
ol.close {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  counter-reset: step;
}

ol.close li {
  display: grid;
  grid-template-columns: 1.6rem 1fr;
  gap: 0.1rem 0.5rem;
  padding: 0.55rem 0;
  border-top: 1px solid var(--grid);
}

ol.close li:first-child { border-top: 0; }

.step-mark {
  grid-row: 1 / span 3;
  color: var(--text-muted);
  text-align: center;
}

.step-done .step-mark { color: var(--pos); }
.step-todo .step-mark { color: var(--warn-border); }

.step-name { font-weight: 600; }
.step-done .step-name { color: var(--text-secondary); font-weight: 400; }

.step-detail {
  grid-column: 2;
  color: var(--text-secondary);
  font-size: 0.87rem;
}

ol.close a { grid-column: 2; font-size: 0.87rem; }

/* ---------------------------------------------------------------------------
   The sign-in page.

   Served by app/domain/authapp, styled here because it shares this stylesheet
   -- one file in the binary rather than two, and the page should look like the
   rest of the app rather than like a different program.
   --------------------------------------------------------------------------- */

body.signin {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  margin: 0;
}

.signin-panel {
  width: 100%;
  max-width: 22rem;
  padding: 2rem;
}

.signin-panel h1 {
  margin: 0 0 0.25rem;
  font-size: 1.5rem;
}

.signin-sub {
  margin: 0 0 1.5rem;
  color: var(--muted, #666);
  font-size: 0.9rem;
}

.signin-panel label {
  display: block;
  margin: 0.75rem 0 0.25rem;
  font-size: 0.85rem;
  font-weight: 600;
}

.signin-panel input[type="text"],
.signin-panel input[type="password"] {
  width: 100%;
  box-sizing: border-box;
  padding: 0.5rem;
  font: inherit;
}

.signin-panel button {
  width: 100%;
  margin-top: 1.25rem;
  padding: 0.6rem;
  font: inherit;
  cursor: pointer;
}

.signin-panel button[disabled] {
  cursor: not-allowed;
  opacity: 0.6;
}

/* A failed sign-in has to be obvious without relying on colour alone, so it
   carries a border and a symbol as well. */
.signin-problem {
  margin: 0 0 1rem;
  padding: 0.6rem 0.75rem;
  border-left: 3px solid #b00020;
  background: rgba(176, 0, 32, 0.06);
  font-size: 0.9rem;
}

.signin-problem::before {
  content: "! ";
  font-weight: 700;
}

.signin-note {
  margin-top: 1.5rem;
  color: var(--muted, #666);
  font-size: 0.8rem;
  line-height: 1.4;
}

/* Sessions and tokens ------------------------------------------------------
 *
 * Served by app/domain/authapp, which has no stylesheet of its own: the login
 * page already borrows this one, and a second stylesheet is a second place for
 * the dark surface to be defined slightly differently. */

/* A two-column table of one thing's properties, rather than a list of many
 * things. The row header carries the label, so a screen reader announces it
 * with each value instead of leaving the second column unattributed. */
.detail-rows tbody th[scope="row"] {
  text-align: left;
  font-weight: 600;
  font-size: 0.8rem;
  color: var(--text-secondary);
  padding: 0.45rem 0.75rem 0.45rem 0;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  width: 8rem;
}

.detail-rows tbody tr:last-child th[scope="row"] {
  border-bottom: 0;
}

/* Checkbox and radio labels inside a stacked form, which otherwise makes every
 * label a block and puts the box on its own line above its own text. */
.stacked-form label.choice {
  display: block;
  margin-top: 0.5rem;
  font-weight: 400;
  color: var(--text-secondary);
  font-size: 0.87rem;
}

.stacked-form label.choice input {
  margin-right: 0.4rem;
}

.stacked-form select {
  background: var(--page);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.45rem 0.55rem;
  font: inherit;
}

.stacked-form fieldset {
  margin-top: 0.9rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.5rem 0.75rem 0.75rem;
}

.stacked-form legend {
  font-weight: 600;
  font-size: 0.87rem;
  padding: 0 0.35rem;
}

/* A secret meant to be selected and copied, once. Large, monospaced, and
 * allowed to wrap: a token that overflows its box invisibly is one that gets
 * pasted half-copied. */
.secret {
  display: block;
  margin: 0.35rem 0 1rem;
  padding: 0.6rem 0.75rem;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9rem;
  overflow-wrap: anywhere;
  user-select: all;
}

/* The footer gains a sign-out button when the app is mounted behind a login,
 * so it needs to lay a form out beside a paragraph. */
footer form.inline {
  margin-right: 0.75rem;
}

/* ---------------------------------------------------------------------------
   What a message will cost.

   A text message is billed per segment, and how many a string takes depends on
   which alphabet it fits in: 160 characters in the 7-bit alphabet, 70 in
   UTF-16, and one curly apostrophe is the whole of the difference. This block
   is where that arithmetic is shown before the send button is pressed.

   It exists because the page ships no script -- the app serves
   default-src 'none' with no script-src at all -- so there is no live counter
   and the figure arrives by a round trip. Given that it costs a request, it is
   worth showing properly rather than as a number in the corner.
   --------------------------------------------------------------------------- */

.cost {
  margin-top: 1rem;
  padding: 0.75rem 0.9rem;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: 6px;
}

.cost p {
  margin: 0 0 0.5rem;
  font-size: 0.9rem;
}

.cost p:last-child {
  margin-bottom: 0;
}

/* The message as the recipient will actually see it, including the unsubscribe
   notice where one is appended. Pre-wrapped rather than in a <pre> that
   scrolls: the line breaks are part of what is being previewed, and a preview
   that needs scrolling sideways is not one. */
.cost pre {
  margin: 0.4rem 0 0;
  padding: 0.6rem 0.75rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 0.85rem;
}

/* A "Check" button sits beside "Send" and must not look like it. The quiet
   class already does that; this only keeps the pair from touching. */
.form-actions button.quiet {
  padding: 0.45rem 1rem;
  font-size: 0.9rem;
}
