/* The operator console.
 *
 * Design notes, so the next person changing this knows what the rules are:
 *
 * No web fonts, no CDN, no external request of any kind. This console shows
 * live money movement; every origin it talks to is an origin that learns
 * when volume spikes. The type stack is what the operating system already
 * has, chosen and tuned rather than inherited.
 *
 * Colour carries two separate jobs and they never share a hue. The accent
 * (teal) means "interactive" -- links, focus, the selected thing. The
 * semantic set (ok / warn / bad / info) means "state of the money" and is
 * never used for decoration. When something on screen is green it is because
 * it settled, not because green looked nice there.
 *
 * Every number that can line up in a column is tabular. On a ledger screen
 * that is not a nicety: proportional digits make a column of amounts
 * impossible to scan for an outlier, which is most of the job.
 */

/* ---------------------------------------------------------------- tokens */
/* One place for every colour, and three theme states rather than two: an
   explicit choice stamps data-theme on the root, and the default "system"
   setting stamps nothing at all -- so most people see the un-stamped
   document, where only prefers-color-scheme separates light from dark.
   Every token is defined on bare :root first; a colour whose only
   definition sits inside a media query is invisible to half the readers. */

:root {
  color-scheme: light;

  /* Neutrals carry a slight sage bias so they sit with the accent rather
     than beside it. A pure grey here reads as unconsidered. */
  --ground:      #edf0ec;
  --surface:     #ffffff;
  --surface-2:   #f5f7f3;
  --surface-3:   #e9ede7;
  --line:        #dde4d9;
  --line-soft:   #e9eee6;
  --line-strong: #c4cdbf;

  /* Inputs sit slightly recessed from the card they are on, with a border a
     shade stronger than the card's -- otherwise a form is a set of hairline
     rectangles and reads as empty rather than as something to fill in. */
  --field:       #fbfcfa;
  --field-line:  #ccd6c7;
  --field-hover: #b3c1ad;

  --ink:    #0f1a17;
  --ink-2:  #4b5a54;
  --ink-3:  #77857e;

  --accent:       #07806d;
  --accent-hover: #066556;
  --accent-soft:  #d8efe8;
  --accent-line:  #a9d9cd;
  --accent-ink:   #ffffff;

  --ok:    #14795a;
  --warn:  #96600a;
  --bad:   #ad3232;
  --info:  #2a6a9f;
  --clay:  #9c5a36;

  --ok-soft:   #dcefe5;
  --warn-soft: #f7ebd2;
  --bad-soft:  #f6e1e1;
  --info-soft: #dfeaf5;

  /* Layered rather than a single blur: a near-contact shadow for the edge
     and a wide soft one for the lift. One blur at one opacity is what makes
     a card look stuck on rather than resting. */
  --shadow-sm: 0 1px 1px rgba(15, 26, 23, .04),
               0 1px 2px rgba(15, 26, 23, .04);
  --shadow-md: 0 1px 2px rgba(15, 26, 23, .04),
               0 8px 20px -8px rgba(15, 26, 23, .14);
  --shadow-lg: 0 2px 4px rgba(15, 26, 23, .05),
               0 24px 56px -20px rgba(15, 26, 23, .24);
  --shadow-focus: 0 0 0 3px var(--accent-soft);

  --radius-lg:   16px;
  --radius:      12px;
  --radius-sm:   8px;
  --radius-pill: 999px;

  --rail-w: 236px;

  /* No web font: the console makes no external request of any kind, because
     every origin it talks to is an origin that learns when volume spikes.
     So the stack is tuned instead -- the newer system faces first, each one
     a real improvement on the fallback after it. */
  --sans: "Inter var", "Inter", ui-sans-serif, system-ui, -apple-system,
          "Segoe UI Variable Text", "Segoe UI", Roboto, "Helvetica Neue",
          Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", "Cascadia Code", "Cascadia Mono",
          "JetBrains Mono", Consolas, "Liberation Mono", monospace;

  --ease: cubic-bezier(.2, .7, .3, 1);
}

/* The un-stamped default: only the OS setting separates the two. Guarded so
   an explicit light choice still beats a dark OS. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --ground:      #0b100f;
    --surface:     #131b18;
    --surface-2:   #1a2420;
    --surface-3:   #232f2a;
    --line:        #26332e;
    --line-soft:   #1d2723;
    --line-strong: #3a4a44;

    --field:       #0d1412;
    --field-line:  #303e39;
    --field-hover: #44564f;

    --ink:    #e9f0ec;
    --ink-2:  #a5b5ae;
    --ink-3:  #7e8e87;

    --accent:       #37bda2;
    --accent-hover: #4ccdb3;
    --accent-soft:  #10312a;
    --accent-line:  #1d4d43;
    --accent-ink:   #04211c;

    --ok:    #47bf92;
    --warn:  #d9a441;
    --bad:   #e2766b;
    --info:  #5aa6dd;
    --clay:  #cc8b62;

    --ok-soft:   #103128;
    --warn-soft: #33290f;
    --bad-soft:  #371f1d;
    --info-soft: #132a3c;

    --shadow-sm: 0 1px 1px rgba(0, 0, 0, .3), 0 1px 2px rgba(0, 0, 0, .25);
    --shadow-md: 0 1px 2px rgba(0, 0, 0, .35),
                 0 10px 24px -10px rgba(0, 0, 0, .6);
    --shadow-lg: 0 2px 4px rgba(0, 0, 0, .4),
                 0 28px 64px -24px rgba(0, 0, 0, .8);
    --shadow-focus: 0 0 0 3px rgba(55, 189, 162, .22);
  }
}

/* And again for the explicit choice, so the toggle wins in both directions. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --ground:      #0b100f;
  --surface:     #131b18;
  --surface-2:   #1a2420;
  --surface-3:   #232f2a;
  --line:        #26332e;
  --line-soft:   #1d2723;
  --line-strong: #3a4a44;

  --field:       #0d1412;
  --field-line:  #303e39;
  --field-hover: #44564f;

  --ink:    #e9f0ec;
  --ink-2:  #a5b5ae;
  --ink-3:  #7e8e87;

  --accent:       #37bda2;
  --accent-hover: #4ccdb3;
  --accent-soft:  #10312a;
  --accent-line:  #1d4d43;
  --accent-ink:   #04211c;

  --ok:    #47bf92;
  --warn:  #d9a441;
  --bad:   #e2766b;
  --info:  #5aa6dd;
  --clay:  #cc8b62;

  --ok-soft:   #103128;
  --warn-soft: #33290f;
  --bad-soft:  #371f1d;
  --info-soft: #132a3c;

  --shadow-sm: 0 1px 1px rgba(0, 0, 0, .3), 0 1px 2px rgba(0, 0, 0, .25);
  --shadow-md: 0 1px 2px rgba(0, 0, 0, .35),
               0 10px 24px -10px rgba(0, 0, 0, .6);
  --shadow-lg: 0 2px 4px rgba(0, 0, 0, .4),
               0 28px 64px -24px rgba(0, 0, 0, .8);
  --shadow-focus: 0 0 0 3px rgba(55, 189, 162, .22);
}

/* ------------------------------------------------------------- structure */

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 14px/1.5 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

.shell {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  min-height: 100vh;
}

/* ------------------------------------------------------------------ rail */

.rail {
  background: var(--surface);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 16px 12px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 8px 16px;
}

.brand-mark {
  width: 30px; height: 30px;
  border-radius: 8px;
  background: linear-gradient(145deg, var(--accent), color-mix(in oklab, var(--accent) 62%, var(--clay)));
  color: var(--accent-ink);
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: -.02em;
  flex: none;
  box-shadow: var(--shadow-sm);
}

.brand-name {
  font-weight: 650;
  letter-spacing: -.015em;
  font-size: 14.5px;
  line-height: 1.15;
}

.brand-env {
  font: 600 9.5px/1 var(--mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.nav-group {
  font: 600 10px/1 var(--sans);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 16px 10px 7px;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  font-weight: 500;
  line-height: 1.2;
  position: relative;
}

.nav-item:hover {
  background: var(--surface-2);
  color: var(--ink);
  text-decoration: none;
}

.nav-item[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

.nav-item[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: -12px; top: 50%;
  transform: translateY(-50%);
  width: 3px; height: 18px;
  border-radius: 0 2px 2px 0;
  background: var(--accent);
}

.nav-item svg { width: 16px; height: 16px; flex: none; opacity: .85; }

.nav-count {
  margin-left: auto;
  font: 600 11px/1 var(--mono);
  font-variant-numeric: tabular-nums;
  padding: 3px 6px;
  border-radius: 20px;
  background: var(--surface-3);
  color: var(--ink-2);
}

.nav-count.urgent { background: var(--bad-soft); color: var(--bad); }
.nav-count.due    { background: var(--warn-soft); color: var(--warn); }

.rail-foot { margin-top: auto; padding-top: 16px; }

.who {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--line);
}

.avatar {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--surface-3);
  border: 1px solid var(--line-strong);
  display: grid;
  place-items: center;
  font: 600 10.5px/1 var(--sans);
  letter-spacing: .02em;
  color: var(--ink-2);
  flex: none;
}

.who-name {
  font-weight: 600;
  font-size: 12.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.who-role {
  font: 500 10px/1.3 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ---------------------------------------------------------------- topbar */

.main { min-width: 0; display: flex; flex-direction: column; }

.topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 28px;
  background: color-mix(in srgb, var(--ground) 88%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 20;
}

.search {
  position: relative;
  flex: 1;
  max-width: 460px;
}

.search input {
  width: 100%;
  padding: 8px 12px 8px 34px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
}

.search input::placeholder { color: var(--ink-3); }
.search input:focus { border-color: var(--accent); outline: none;
                      box-shadow: 0 0 0 3px var(--accent-soft); }

.search svg {
  position: absolute;
  left: 10px; top: 50%;
  transform: translateY(-50%);
  width: 15px; height: 15px;
  color: var(--ink-3);
  pointer-events: none;
}

.search kbd {
  position: absolute;
  right: 8px; top: 50%;
  transform: translateY(-50%);
  font: 500 10px/1 var(--mono);
  color: var(--ink-3);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 3px 5px;
  pointer-events: none;
}

.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.utc {
  font: 500 12px/1 var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
  padding: 6px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  white-space: nowrap;
}

.utc span { color: var(--ink-3); margin-left: 5px; font-size: 10px;
            letter-spacing: .08em; }

.icon-btn {
  width: 32px; height: 32px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
  padding: 0;
}

.icon-btn:hover { color: var(--ink); border-color: var(--line-strong); }
.icon-btn svg { width: 15px; height: 15px; }

.live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: 500 11px/1 var(--sans);
  letter-spacing: .04em;
  color: var(--ink-3);
  text-transform: uppercase;
}

.live-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok) 60%, transparent);
  animation: pulse 2.4s ease-out infinite;
}

@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok) 55%, transparent); }
  70%  { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* ------------------------------------------------------------------ page */

.page { padding: 24px 28px 72px; max-width: 1560px; width: 100%; }

.page-head {
  display: flex;
  align-items: flex-end;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}

.page-head h1 {
  font-size: 22px;
  font-weight: 650;
  letter-spacing: -.022em;
  margin: 0 0 3px;
  text-wrap: balance;
}

.page-head .sub {
  margin: 0;
  color: var(--ink-2);
  max-width: 68ch;
  font-size: 13.5px;
}

.page-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }

h2.section {
  font-size: 12px;
  font-weight: 650;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 32px 0 12px;
}

/* ------------------------------------------------------------------ grid */

.grid { display: grid; gap: 14px; }
.grid.tiles { grid-template-columns: repeat(auto-fit, minmax(232px, 1fr)); }
.grid.halves { grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); }
.grid.main-split { grid-template-columns: minmax(0, 2.1fr) minmax(300px, 1fr); }
.grid.thirds { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }

@media (max-width: 1080px) {
  .grid.main-split { grid-template-columns: minmax(0, 1fr); }
}

/* ------------------------------------------------------------------ tile */

.tile {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 15px 16px 13px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  position: relative;
  overflow: hidden;
}

.tile-label {
  font: 600 10.5px/1 var(--sans);
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: flex;
  align-items: center;
  gap: 7px;
}

.tile-value {
  font-size: 27px;
  font-weight: 620;
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.tile-value .unit {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--ink-3);
  font-family: var(--mono);
}

.tile-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: space-between;
  margin-top: auto;
}

.tile .spark { width: 96px; height: 26px; flex: none; opacity: .9; }

.delta {
  font: 600 11.5px/1 var(--sans);
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 3px 6px;
  border-radius: 20px;
}

.delta.up   { color: var(--ok);   background: var(--ok-soft); }
.delta.down { color: var(--bad);  background: var(--bad-soft); }
.delta.flat { color: var(--ink-3); background: var(--surface-2); }

.tile-note { font-size: 11.5px; color: var(--ink-3); }

/* ----------------------------------------------------------------- panel */

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.panel-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}

.panel-head h2, .panel-head h3 {
  margin: 0;
  font-size: 13.5px;
  font-weight: 640;
  letter-spacing: -.01em;
}

.panel-head .meta { font-size: 11.5px; color: var(--ink-3); }
.panel-head .right { margin-left: auto; display: flex; gap: 8px; align-items: center; }

.panel-body { padding: 16px; min-width: 0; }
.panel-body.flush { padding: 0; }
.panel-body.tight { padding: 10px 16px; }

.panel-foot {
  padding: 10px 16px;
  border-top: 1px solid var(--line);
  background: var(--surface-2);
  font-size: 12px;
  color: var(--ink-2);
  display: flex;
  gap: 10px;
  align-items: center;
}

/* ----------------------------------------------------------------- table */

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

table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

table.data th {
  text-align: left;
  font: 600 10px/1 var(--sans);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--surface-2);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 1;
}

table.data td {
  padding: 11px 14px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}

table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover td { background: var(--surface-2); }

table.data td.num, table.data th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.mono { font-family: var(--mono); font-size: 12.5px; letter-spacing: -.01em; }
.num  { font-variant-numeric: tabular-nums; }
.sub-line { display: block; font-size: 11px; color: var(--ink-3); margin-top: 2px; }
.muted { color: var(--ink-3); }
.nowrap { white-space: nowrap; }

.amount { font-variant-numeric: tabular-nums; font-weight: 560; }
.amount .ccy { font: 500 10.5px/1 var(--mono); color: var(--ink-3); margin-left: 4px; }

/* ------------------------------------------------------------------ pill */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: 600 10.5px/1 var(--sans);
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 4px 8px;
  border-radius: 20px;
  background: var(--surface-3);
  color: var(--ink-2);
  white-space: nowrap;
}

.pill.ok    { background: var(--ok-soft);   color: var(--ok); }
.pill.warn  { background: var(--warn-soft); color: var(--warn); }
.pill.bad   { background: var(--bad-soft);  color: var(--bad); }
.pill.info  { background: var(--info-soft); color: var(--info); }
.pill.muted { background: var(--surface-3); color: var(--ink-3); }

.pill::before {
  content: "";
  width: 5px; height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.pill.plain::before { display: none; }

.tag {
  display: inline-block;
  font: 500 11px/1 var(--mono);
  padding: 3px 6px;
  border-radius: 4px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--ink-2);
  white-space: nowrap;
}

/* ---------------------------------------------------------------- alerts */

.alerts { display: flex; flex-direction: column; gap: 1px; background: var(--line); }

.alert {
  display: flex;
  gap: 12px;
  padding: 13px 16px;
  background: var(--surface);
  border-left: 3px solid var(--ink-3);
  align-items: flex-start;
}

.alert.critical { border-left-color: var(--bad); }
.alert.warning  { border-left-color: var(--warn); }
.alert.notice   { border-left-color: var(--info); }

.alert-icon {
  width: 26px; height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  flex: none;
  margin-top: 1px;
}

.alert.critical .alert-icon { background: var(--bad-soft);  color: var(--bad); }
.alert.warning  .alert-icon { background: var(--warn-soft); color: var(--warn); }
.alert.notice   .alert-icon { background: var(--info-soft); color: var(--info); }
.alert-icon svg { width: 14px; height: 14px; }

.alert-title { font-weight: 600; font-size: 13px; letter-spacing: -.005em; }
.alert-detail { font-size: 12px; color: var(--ink-2); margin-top: 2px; max-width: 62ch; }
.alert a.alert-go { margin-left: auto; flex: none; font-size: 12px; font-weight: 600;
                    white-space: nowrap; align-self: center; }

.all-clear {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 18px 16px;
  background: var(--surface);
}

.all-clear .alert-icon { background: var(--ok-soft); color: var(--ok); }

/* ------------------------------------------------------------------ feed */

.feed { display: flex; flex-direction: column; max-height: 520px; overflow-y: auto; }

.feed-item {
  display: flex;
  gap: 11px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--line);
  align-items: flex-start;
}

.feed-item:last-child { border-bottom: 0; }

.feed-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  flex: none;
  margin-top: 6px;
  background: var(--ink-3);
  position: relative;
}

.feed-dot.ok   { background: var(--ok); }
.feed-dot.bad  { background: var(--bad); }
.feed-dot.warn { background: var(--warn); }
.feed-dot.info { background: var(--info); }

.feed-body { min-width: 0; flex: 1; }
.feed-line { font-size: 12.5px; line-height: 1.45; }
.feed-line .who-did { font-weight: 600; }
.feed-meta { font-size: 11px; color: var(--ink-3); margin-top: 1px;
             display: flex; gap: 8px; flex-wrap: wrap; }
.feed-time { font: 500 11px/1 var(--mono); color: var(--ink-3); flex: none;
             white-space: nowrap; margin-top: 2px; }

/* ---------------------------------------------------------------- charts */

.chart { width: 100%; height: auto; display: block; overflow: visible; }
.chart .grid { stroke: var(--line); stroke-width: 1; }
.chart .axis, .heat .axis {
  fill: var(--ink-3);
  font: 500 10px/1 var(--mono);
}
.chart .hit { fill: transparent; cursor: crosshair; }
.chart .hit:hover { fill: var(--ink); fill-opacity: .04; }
.chart .endpoint { filter: drop-shadow(0 0 4px color-mix(in srgb, var(--accent) 50%, transparent)); }

.chart-legend { display: flex; gap: 16px; flex-wrap: wrap; }
.legend-item { display: inline-flex; align-items: center; gap: 6px;
               font-size: 11.5px; color: var(--ink-2); }
.legend-swatch { width: 9px; height: 9px; border-radius: 2px; flex: none; }

.chart-frame { position: relative; }

.chart-tip {
  position: absolute;
  pointer-events: none;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  padding: 8px 10px;
  font-size: 12px;
  opacity: 0;
  transform: translate(-50%, -8px);
  transition: opacity .12s ease;
  z-index: 5;
  white-space: nowrap;
}

.chart-tip.on { opacity: 1; }
.chart-tip .tip-label { font: 600 10px/1 var(--sans); letter-spacing: .08em;
                        text-transform: uppercase; color: var(--ink-3);
                        margin-bottom: 4px; }
.chart-tip .tip-value { font-variant-numeric: tabular-nums; }

.donut { width: 132px; height: 132px; flex: none; }
.donut-total { fill: var(--ink); font: 640 20px/1 var(--sans);
               font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.donut-cap { fill: var(--ink-3); font: 600 9px/1 var(--sans); letter-spacing: .12em; }

.heat { width: 100%; height: auto; max-width: 420px; }

.mix { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; }
.mix-legend { display: flex; flex-direction: column; gap: 7px; flex: 1; min-width: 150px; }
.mix-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
.mix-row .n { margin-left: auto; font-variant-numeric: tabular-nums;
              font-weight: 600; color: var(--ink-2); }

/* ----------------------------------------------------------------- meter */

.meter {
  display: inline-block;
  width: 78px;
  height: 5px;
  border-radius: 3px;
  background: var(--surface-3);
  overflow: hidden;
  vertical-align: middle;
}

.meter-fill { display: block; height: 100%; border-radius: 3px; background: var(--ok); }
.meter-none { background: repeating-linear-gradient(90deg,
              var(--line) 0 4px, transparent 4px 8px); }

/* ----------------------------------------------------------------- forms */

label, .field-label {
  display: block;
  font: 600 10.5px/1 var(--sans);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 6px;
}

input:not([type]),
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=file], select, textarea {
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
}

input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

input[type=file] { padding: 7px 9px; font-size: 12.5px; color: var(--ink-2); }

.field { margin-bottom: 14px; }
.field .hint { font-size: 11.5px; color: var(--ink-3); margin: 5px 0 0; }

button, .btn {
  font: 600 13px/1 var(--sans);
  padding: 9px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

button:hover, .btn:hover { filter: brightness(1.08); text-decoration: none; }

button.secondary, .btn.secondary {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--line-strong);
}

button.secondary:hover, .btn.secondary:hover { background: var(--surface-2); filter: none; }

button.danger, .btn.danger {
  background: transparent;
  color: var(--bad);
  border-color: color-mix(in srgb, var(--bad) 45%, transparent);
}

button.danger:hover { background: var(--bad-soft); filter: none; }

button.ghost {
  background: transparent;
  border-color: transparent;
  color: var(--ink-2);
}

button.ghost:hover { background: var(--surface-2); color: var(--ink); filter: none; }

button.small, .btn.small { padding: 6px 10px; font-size: 12px; }

button[disabled] { opacity: .45; cursor: not-allowed; filter: none; }

.actions { display: flex; gap: 7px; align-items: center; flex-wrap: wrap; }
.actions form { margin: 0; }

/* segmented filter control */
.seg {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface);
}

.seg a {
  padding: 6px 11px;
  font-size: 12px;
  font-weight: 550;
  color: var(--ink-2);
  border-right: 1px solid var(--line);
}

.seg a:last-child { border-right: 0; }
.seg a:hover { background: var(--surface-2); text-decoration: none; color: var(--ink); }
.seg a[aria-current] { background: var(--accent); color: var(--accent-ink); }

/* ---------------------------------------------------------------- flashes */

.flashes {
  position: fixed;
  top: 14px;
  right: 18px;
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: min(420px, calc(100vw - 36px));
}

.flash {
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-left: 3px solid var(--ok);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  padding: 11px 14px;
  font-size: 13px;
  animation: slide-in .22s cubic-bezier(.2, .8, .3, 1);
}

.flash.error { border-left-color: var(--bad); }

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

/* ----------------------------------------------------------------- empty */

.empty {
  padding: 44px 24px;
  text-align: center;
  color: var(--ink-3);
}

.empty-title { font-weight: 600; color: var(--ink-2); font-size: 13.5px; margin-bottom: 3px; }
.empty-sub { font-size: 12.5px; max-width: 46ch; margin: 0 auto; }

/* ------------------------------------------------------------------ auth */

.auth-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background:
    radial-gradient(1100px 520px at 12% -8%,
      color-mix(in srgb, var(--accent) 12%, transparent), transparent 62%),
    radial-gradient(800px 420px at 92% 106%,
      color-mix(in srgb, var(--clay) 10%, transparent), transparent 60%),
    var(--ground);
}

.auth-card {
  width: 100%;
  max-width: 380px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  padding: 28px;
}

.auth-card .brand { padding: 0 0 20px; }
.auth-card h1 { font-size: 18px; font-weight: 640; letter-spacing: -.02em; margin: 0 0 4px; }
.auth-card .sub { color: var(--ink-2); font-size: 13px; margin: 0 0 20px; }
.auth-card button { width: 100%; justify-content: center; margin-top: 4px; }
.auth-alt { margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--line); }
.auth-foot { text-align: center; margin-top: 18px; font-size: 11.5px; color: var(--ink-3); }

.code-input {
  font: 600 20px/1 var(--mono) !important;
  letter-spacing: .34em;
  text-align: center;
  padding: 12px !important;
}

.secret-block {
  display: block;
  font: 500 13px/1.6 var(--mono);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 12px;
  margin: 10px 0;
  word-break: break-all;
  user-select: all;
}

.codes {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin: 14px 0;
}

.codes li {
  list-style: none;
  font: 500 13px/1 var(--mono);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 10px;
  text-align: center;
  user-select: all;
}

/* ------------------------------------------------------------ misc utils */

.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.spread { display: flex; gap: 12px; align-items: center; justify-content: space-between; }
.stack { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.grow { flex: 1; min-width: 0; }
.right { text-align: right; }
.note { font-size: 12px; color: var(--ink-3); }
.big-note { font-size: 12.5px; color: var(--ink-2); line-height: 1.6; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.kv { display: grid; grid-template-columns: auto 1fr; gap: 8px 18px; font-size: 13px; }
.kv dt { color: var(--ink-3); font-size: 11.5px; text-transform: uppercase;
         letter-spacing: .08em; font-weight: 600; align-self: center; }
.kv dd { margin: 0; font-variant-numeric: tabular-nums; }

.ledger-line {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px dashed var(--line);
  font-size: 12.5px;
  align-items: center;
}

.ledger-line:last-child { border-bottom: 0; }
.ledger-line .acct { font-family: var(--mono); font-size: 12px; }
.ledger-line .dr { color: var(--info); font-variant-numeric: tabular-nums; }
.ledger-line .cr { color: var(--clay); font-variant-numeric: tabular-nums; }

/* ----------------------------------------------------------- responsive */

@media (max-width: 940px) {
  .shell { grid-template-columns: 1fr; }
  .rail {
    position: static;
    height: auto;
    flex-direction: row;
    align-items: center;
    overflow-x: auto;
    border-right: 0;
    border-bottom: 1px solid var(--line);
    padding: 10px 14px;
    gap: 6px;
  }
  .brand { padding: 0 12px 0 0; }
  .nav-group, .rail-foot .who-text { display: none; }
  .nav-item { padding: 7px 9px; white-space: nowrap; }
  .nav-item[aria-current="page"]::before { display: none; }
  .rail-foot { margin: 0 0 0 auto; padding: 0; }
  .who { padding: 5px; border: 0; background: transparent; }
  .topbar { padding: 10px 16px; }
  .page { padding: 18px 16px 56px; }
  .utc { display: none; }
}

@media (max-width: 560px) {
  .search kbd { display: none; }
  .grid.tiles { grid-template-columns: 1fr; }
  .codes { grid-template-columns: 1fr; }
  .page-actions { width: 100%; margin-left: 0; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

@media print {
  .rail, .topbar, .page-actions, .flashes { display: none !important; }
  .shell { grid-template-columns: 1fr; }
  .panel, .tile { break-inside: avoid; border-color: #ccc; }
  body { background: #fff; }
}

/* The icon sprite is markup, not decoration: it must take no space. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Legend swatches. Classes rather than inline colours, because the console's
   content security policy does not allow style attributes in markup. */
.swatch-ok     { background: var(--ok); }
.swatch-bad    { background: var(--bad); }
.swatch-warn   { background: var(--warn); }
.swatch-info   { background: var(--info); }
.swatch-accent { background: var(--accent); }
.swatch-clay   { background: var(--clay); }
.swatch-muted  { background: var(--ink-3); }

/* A journal is one double-entry posting: its lines belong together and are
   separated from the next journal, so nobody reads across two of them and
   concludes the books do not balance. */
.journal {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin-bottom: 10px;
  background: var(--surface-2);
}

.journal:last-child { margin-bottom: 0; }

/* The crosshair the chart drops on the hovered bucket. */
.chart .marker { pointer-events: none; }


/* ==========================================================================
   REFINEMENT LAYER
   Typography, depth and motion. Everything below overrides a rule above it
   on purpose; nothing here introduces a new component.
   ========================================================================== */

/* ------------------------------------------------------------- typography */

body {
  font-feature-settings: "cv05" 1, "ss01" 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Digits that line up in columns must be the same width, or a table of money
   shimmers as it updates. */
.tile-value, .amount, .num, .kv dd, td.num, th.num,
.nav-count, .delta, .alert-count { font-variant-numeric: tabular-nums; }

h1 {
  font-size: clamp(21px, 2.1vw, 25px);
  font-weight: 640;
  letter-spacing: -.021em;
  line-height: 1.2;
}

h2 { letter-spacing: -.012em; font-weight: 640; }

.page-head .sub {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 68ch;
}

/* Section headings were tiny grey capitals, which is the single most dated
   thing on a dashboard. Same role, more presence: darker, a touch larger,
   and a hairline that carries the eye across. */
.section {
  font-size: 11.5px;
  font-weight: 650;
  letter-spacing: .085em;
  text-transform: uppercase;
  color: var(--ink-2);
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 34px 0 14px;
}

.section::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, var(--line), transparent);
}

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

/* Flex rather than grid, for one reason: with six tiles in a four-column
   grid the last row holds two and leaves a gap the width of two more.
   `auto-fit` does not help -- it collapses empty *tracks*, not empty space
   at the end of a row. Flex lets a short last row grow into it. */
.grid.tiles {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}

.grid.tiles > * { flex: 1 1 248px; min-width: 248px; }

.tile {
  border-radius: var(--radius);
  border-color: var(--line-soft);
  box-shadow: var(--shadow-sm);
  padding: 16px 17px 14px;
  transition: box-shadow .18s var(--ease), border-color .18s var(--ease);
}

.tile:hover { box-shadow: var(--shadow-md); border-color: var(--line); }

.tile-label {
  font-size: 10px;
  font-weight: 660;
  letter-spacing: .1em;
  color: var(--ink-3);
}

.tile-value {
  font-size: 31px;
  font-weight: 680;
  letter-spacing: -.035em;
  color: var(--ink);
}

.tile-value .unit {
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 620;
  letter-spacing: .01em;
}

.tile-note { font-size: 11.5px; line-height: 1.55; color: var(--ink-3); }

.tile .spark { width: 104px; height: 30px; opacity: 1; }

.delta {
  padding: 3px 7px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 650;
}

/* ----------------------------------------------------------------- panels */

.panel {
  border-radius: var(--radius);
  border-color: var(--line-soft);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.panel-head {
  padding: 13px 18px;
  border-bottom: 1px solid var(--line-soft);
  background: linear-gradient(to bottom, var(--surface-2), var(--surface));
}

.panel-head h2 { font-size: 14px; font-weight: 650; }
.panel-head .meta { font-size: 11.5px; color: var(--ink-3); }

.panel-foot {
  border-top: 1px solid var(--line-soft);
  background: var(--surface-2);
  font-size: 11.5px;
  color: var(--ink-3);
}

/* ----------------------------------------------------------------- alerts */

/* Severity has to be legible before the words are read, so it gets an edge
   and a tinted icon rather than only a coloured border. */
.alert {
  border-bottom: 1px solid var(--line-soft);
  padding: 15px 18px;
  gap: 13px;
  transition: background .15s var(--ease);
}

.alert:last-child { border-bottom: 0; }
.alert:hover { background: var(--surface-2); }

.alert-icon {
  width: 28px;
  height: 28px;
  border-radius: 9px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.alert-icon svg { width: 15px; height: 15px; }

.alert.critical .alert-icon { background: var(--bad-soft);  color: var(--bad); }
.alert.warning  .alert-icon { background: var(--warn-soft); color: var(--warn); }
.alert.notice   .alert-icon { background: var(--info-soft); color: var(--info); }

.alert-title {
  font-size: 13.5px;
  font-weight: 640;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.alert-detail {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-2);
  margin-top: 3px;
  max-width: 76ch;
}

/* How many things this one condition is true of. */
.alert-count {
  font-size: 10.5px;
  font-weight: 680;
  padding: 1px 7px;
  border-radius: var(--radius-pill);
  background: var(--surface-3);
  color: var(--ink-2);
}

/* The specifics, when one condition covers several. Five currency chips on
   one row instead of five rows that differ by three characters. */
.alert-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 8px;
}

.chip {
  font: 620 10.5px/1 var(--sans);
  letter-spacing: .04em;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  background: var(--surface-3);
  color: var(--ink-2);
  border: 1px solid var(--line-soft);
}

.alert-go {
  font-size: 12px;
  font-weight: 640;
  color: var(--accent);
  text-decoration: none;
  white-space: nowrap;
  padding: 5px 9px;
  border-radius: var(--radius-sm);
  transition: background .15s var(--ease);
}

.alert-go:hover { background: var(--accent-soft); text-decoration: none; }

/* ----------------------------------------------------------------- tables */

table.data th {
  font-size: 10px;
  font-weight: 660;
  letter-spacing: .09em;
  color: var(--ink-3);
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  padding: 10px 16px;
}

table.data td {
  padding: 13px 16px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 13px;
}

table.data tbody tr { transition: background .13s var(--ease); }
table.data tbody tr:hover { background: var(--surface-2); }
table.data tbody tr:last-child td { border-bottom: 0; }

.sub-line { font-size: 11.5px; color: var(--ink-3); margin-top: 2px; }

/* ------------------------------------------------------------ pills & tags */

/* These were monospaced, which made every label look like terminal output.
   Mono is for identifiers and money; a status is a word. */
.pill {
  font: 640 11px/1 var(--sans);
  letter-spacing: .015em;
  padding: 4px 9px;
  border-radius: var(--radius-pill);
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}

.tag {
  font: 620 10.5px/1 var(--sans);
  letter-spacing: .05em;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  background: var(--surface-3);
  color: var(--ink-2);
  border: 1px solid var(--line-soft);
}

/* The live/paused dot, so state reads before the word does. */
.pill.ok::before, .pill.warn::before, .pill.bad::before, .pill.info::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.pill.plain::before { display: none; }

/* ---------------------------------------------------------------- buttons */

button, .btn {
  border-radius: var(--radius-sm);
  font-weight: 640;
  letter-spacing: .005em;
  transition: filter .15s var(--ease), box-shadow .15s var(--ease),
              transform .08s var(--ease);
}

button:hover, .btn:hover { filter: brightness(1.06); }
button:active, .btn:active { transform: translateY(.5px); }

button:focus-visible, .btn:focus-visible, a:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ------------------------------------------------------------------ forms */

input, select, textarea {
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-size: 13.5px;
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}

input:focus, select:focus, textarea:focus {
  border-color: var(--accent);
  box-shadow: var(--shadow-focus);
  outline: none;
}

.field label {
  font-size: 12px;
  font-weight: 640;
  color: var(--ink-2);
  display: flex;
  align-items: center;
  gap: 7px;
}

.hint { font-size: 11.5px; line-height: 1.55; color: var(--ink-3); }

/* -------------------------------------------------------------------- rail */

.nav-item {
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 560;
  transition: background .14s var(--ease), color .14s var(--ease);
}

.nav-item:hover { background: var(--surface-3); }

.nav-item[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 650;
}

.nav-group {
  font-size: 9.5px;
  font-weight: 680;
  letter-spacing: .13em;
  color: var(--ink-3);
}

.nav-count {
  font-size: 10.5px;
  font-weight: 680;
  border-radius: var(--radius-pill);
  padding: 2px 7px;
}

/* ----------------------------------------------------------------- charts */

/* A hairline stroke on a white card disappears. Thicker, with the area
   beneath it filled, so the shape reads at a glance rather than on
   inspection. */
/* Targeting what charts.py actually emits. A presentation attribute like
   stroke-width="1.5" on the path loses to any CSS rule, which is what lets
   the weight stay a design decision rather than a number in a generator --
   the previous rules here named classes the SVG does not have, so they did
   nothing at all. */
.spark path, .chart path[stroke] {
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.9;
}

/* The last reading is the one being asked about, so it is marked -- ringed
   in the surface colour so it reads as a point rather than a thickening. */
.spark .spark-end { stroke: var(--surface); stroke-width: 1.4; }

/* ------------------------------------------------------------------ empty */

.empty { padding: 34px 20px; text-align: center; }
.empty-title { font-size: 14px; font-weight: 640; color: var(--ink-2); }
.empty-sub {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-3);
  max-width: 52ch;
  margin: 6px auto 0;
}

/* ------------------------------------------------------------------ motion */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
}


/* ==========================================================================
   FORMS
   A form is where somebody types the thing that moves money, so it gets the
   same care as the numbers do.
   ========================================================================== */

/* The placeholder had no rule at all, so it fell through to the browser
   default -- which on a dark ground computes to something close to
   invisible. "Where is INR?" is a fair question to ask of it. */
input::placeholder,
textarea::placeholder {
  color: var(--ink-3);
  opacity: 1;              /* Firefox dims placeholders again without this */
}

/* `input:not([type])` is load-bearing, not tidiness. An <input> with no type
   attribute is a text input to the browser, but [type=text] is an attribute
   selector and does not match an attribute that is absent -- so every
   <input id=... name=...> in the templates missed this whole block and
   rendered as an unstyled browser default: no width, no padding, no border,
   no background. Eighteen of them, across nine pages. */
input:not([type]),
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=search], input[type=url], select, textarea {
  width: 100%;
  padding: 11px 13px;
  border: 1px solid var(--field-line);
  border-radius: var(--radius-sm);
  background: var(--field);
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  line-height: 1.4;
  box-shadow: inset 0 1px 2px rgba(15, 26, 23, .04);
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease),
              background .15s var(--ease);
}

input[type=number] { font-variant-numeric: tabular-nums; }

input:hover:not(:focus), select:hover:not(:focus), textarea:hover:not(:focus) {
  border-color: var(--field-hover);
}

input:focus, select:focus, textarea:focus {
  outline: none;
  background: var(--surface);
  border-color: var(--accent);
  box-shadow: var(--shadow-focus);
}

/* The native arrow is drawn by the OS and ignores the theme, so it is
   replaced with one that does not. Two gradients rather than an image,
   because an image would be an external request. */
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 34px;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
    linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position:
    calc(100% - 17px) calc(50% + 1px),
    calc(100% - 12px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

/* Labels sit above the thing they name, so they are quiet -- but quiet is
   not the same as unreadable, and --ink-3 on a field was the latter. */
label, .field-label {
  font-size: 10.5px;
  font-weight: 680;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: 7px;
}

.field { margin-bottom: 18px; }
.field .hint { margin-top: 6px; }

/* A checkbox label is a sentence, not a column heading. */
label.row, .field label.row {
  text-transform: none;
  letter-spacing: 0;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--ink);
  gap: 9px;
}

input[type=checkbox] {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  flex: none;
  cursor: pointer;
}

/* -------------------------------------------------------------- buttons */

/* Flat fill reads as a coloured rectangle. A top highlight and a shadow in
   the accent's own hue read as a control -- the difference between a button
   that looks drawn and one that looks pressable. */
button, .btn {
  padding: 10px 17px;
  font-size: 13px;
  font-weight: 650;
  letter-spacing: .005em;
  border-radius: var(--radius-sm);
  border: 1px solid var(--accent);
  background-image: linear-gradient(to bottom,
      rgba(255, 255, 255, .13), rgba(255, 255, 255, 0));
  box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255, 255, 255, .16);
}

button:hover, .btn:hover {
  background-color: var(--accent-hover);
  border-color: var(--accent-hover);
  box-shadow: var(--shadow-md), inset 0 1px 0 rgba(255, 255, 255, .2);
  filter: none;
}

button:active, .btn:active {
  transform: translateY(1px);
  box-shadow: var(--shadow-sm);
}

button.secondary, .btn.secondary {
  background-image: none;
  background-color: var(--surface);
  border-color: var(--line-strong);
  color: var(--ink);
  box-shadow: var(--shadow-sm);
}

button.secondary:hover, .btn.secondary:hover {
  background-color: var(--surface-2);
  border-color: var(--ink-3);
}

button.danger, .btn.danger {
  background-color: var(--bad);
  border-color: var(--bad);
  color: #fff;
}

button.small, .btn.small { padding: 7px 12px; font-size: 12px; }

button[disabled] { opacity: .45; box-shadow: none; }

/* --------------------------------------------------------------- layout */

/* A form that ends with one orphan field on its own row looks unfinished.
   Letting fields grow closes the gap without stretching one to full width. */
.panel-body > form .grid.thirds,
.panel-body > form .grid.halves {
  display: flex;
  flex-wrap: wrap;
  gap: 0 18px;
}

.panel-body > form .grid.thirds > .field { flex: 1 1 210px; min-width: 190px; }
.panel-body > form .grid.halves > .field { flex: 1 1 280px; min-width: 240px; }
.panel-body > form .field.grow { flex: 2 1 320px; }

.actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 4px;
}


/* Grid stretches its children to the tallest in the row, which is how a
   panel holding one checkbox ends up as tall as one holding six fields --
   and a half-empty card is most of what makes a screen look unfinished.
   Sizing to content instead. */
.grid.halves, .grid.thirds, .grid.main-split { align-items: start; }


/* ==========================================================================
   BRAND
   ========================================================================== */

.brand {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 16px 14px 18px;
  text-decoration: none;
  color: inherit;
}

.brand-mark {
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 10px;
  background: var(--accent);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255, 255, 255, .22);
}

.brand-mark svg { width: 22px; height: 22px; }

.brand-name {
  font-size: 15px;
  font-weight: 680;
  letter-spacing: -.017em;
  color: var(--ink);
  line-height: 1.15;
}

.brand-env {
  font-size: 10.5px;
  font-weight: 560;
  letter-spacing: .02em;
  color: var(--ink-3);
  line-height: 1.3;
}

/* ==========================================================================
   MOTION
   One orchestrated entrance, and a chart that draws itself. Nothing else
   moves. Animation scattered across a page is the thing that makes an
   interface feel designed by a machine -- and on a screen somebody opens
   forty times a day, a flourish stops being a flourish by the third time.
   ========================================================================== */

@keyframes rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* Short, small, and over before it is noticed -- enough to make the page
   assemble rather than appear, not enough to wait for. */
.main > .page-head,
.main > .grid,
.main > .panel,
.main > .section {
  animation: rise .34s var(--ease) both;
}

/* Staggered by position. Delays live here rather than on the elements
   because the CSP forbids a style attribute, and a nth-child rule says the
   same thing without one. */
.main > *:nth-child(1) { animation-delay: 0ms; }
.main > *:nth-child(2) { animation-delay: 40ms; }
.main > *:nth-child(3) { animation-delay: 80ms; }
.main > *:nth-child(4) { animation-delay: 120ms; }
.main > *:nth-child(5) { animation-delay: 150ms; }
.main > *:nth-child(6) { animation-delay: 175ms; }
.main > *:nth-child(n+7) { animation-delay: 195ms; }

/* The line traces itself once. pathLength="1" is set in charts.py so the
   dash maths is in normalised units and does not depend on how long the
   path happens to be. */
@keyframes draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }

.chart path[stroke] {
  stroke-dasharray: 1;
  animation: draw 1.1s var(--ease) .18s both;
}

/* A number that has just changed is worth a glance; one that has not is
   not. Applied by console.js on the live counters only. */
@keyframes tick-flash {
  0%   { background: var(--accent-soft); }
  100% { background: transparent; }
}

.ticked { animation: tick-flash .9s var(--ease); border-radius: 4px; }

/* Everything above is a preference, not a requirement. Somebody who has
   asked their system for less motion has asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .main > .page-head,
  .main > .grid,
  .main > .panel,
  .main > .section,
  .chart path[stroke],
  .ticked {
    animation: none;
  }
  .chart path[stroke] { stroke-dasharray: none; }
}


/* ==========================================================================
   THE LEDGER
   Two ruled columns with totals underneath, which is how double-entry has
   been drawn since Pacioli wrote it down in 1494 -- and the reason it has
   lasted is that the balance is *visible*. A single column with a DR or CR
   label beside each line is a list that happens to balance; the reader is
   left adding it up to check.
   ========================================================================== */

.journal {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  margin-bottom: 14px;
  overflow: hidden;
}

.journal:last-child { margin-bottom: 0; }

.journal-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 16px;
  border-bottom: 1px solid var(--line-soft);
  background: linear-gradient(to bottom, var(--surface-2), var(--surface));
}

/* What the journal did, in words. The UUID is how the database knows it and
   is nobody's first question about it, so it goes in the footer. */
.journal-caption {
  font-size: 13px;
  font-weight: 650;
  color: var(--ink);
  letter-spacing: -.008em;
}

.journal-when {
  font: 500 11px/1 var(--mono);
  color: var(--ink-3);
  white-space: nowrap;
}

.tcols {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

/* The centre rule. One border between the two sides, and on a narrow screen
   it becomes the divider between them stacked. */
.tcol + .tcol { border-left: 1px solid var(--line); }

.tcol-head {
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding: 9px 16px 8px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 10px;
  font-weight: 680;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.tcol-hint {
  font-weight: 500;
  letter-spacing: .02em;
  text-transform: none;
  font-size: 10.5px;
  color: var(--ink-3);
  opacity: .8;
}

/* Each side is a column with the total pushed to its foot, so the two
   totals rules meet across the middle however many lines are on each side.
   Padding rows cannot do this -- a blank row is a fixed height and a real
   one grows with its memo, so the rules drift apart by exactly as much as
   the memos wrap. */
.tcol { display: flex; flex-direction: column; }

.tline {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 16px;
}

.tline-acct {
  font: 500 12px/1.35 var(--mono);
  color: var(--ink);
  min-width: 0;
  word-break: break-word;
}

.tline-memo {
  display: block;
  font: 400 11px/1.4 var(--sans);
  color: var(--ink-3);
  margin-top: 3px;
}

.tline-amt {
  font: 600 13.5px/1 var(--mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The two sides get opposing colour. Not decoration -- it is the one thing
   the reader has to tell apart at a glance. */
.tline-amt.dr { color: var(--clay); }
.tline-amt.cr { color: var(--accent); }

/* The totals rule: doubled, the way a ledger is ruled off. */
.tline.total {
  margin-top: auto;
  border-top: 2px solid var(--line-strong);
  background: var(--surface-2);
  min-height: 0;
  padding-top: 11px;
  padding-bottom: 11px;
}

.tline.total .tline-acct {
  font-family: var(--sans);
  font-size: 10.5px;
  font-weight: 680;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.tline.total .tline-amt { font-size: 14px; font-weight: 700; }

.journal-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 16px;
  border-top: 1px solid var(--line-soft);
  background: var(--surface-2);
}

.journal-id {
  font-size: 10.5px;
  color: var(--ink-3);
  word-break: break-all;
}

.journal-balanced {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  font-weight: 650;
  color: var(--ok);
  white-space: nowrap;
}

.journal-balanced svg { width: 13px; height: 13px; }

.journal-broken {
  font-size: 11px;
  font-weight: 680;
  color: var(--bad);
}

@media (max-width: 880px) {
  .tcols { grid-template-columns: 1fr; }
  .tcol + .tcol { border-left: 0; border-top: 1px solid var(--line); }
  .tline.empty-line { display: none; }
}

/* ==========================================================================
   THE TRANSACTIONS LIST
   Scanned, not read. The amount is what somebody is looking for; the id is
   a 30-character ULID nobody reads and it was the widest, boldest thing on
   every row.
   ========================================================================== */

.txn-list td { padding: 11px 16px; }

.txn-id {
  font: 500 12px/1.3 var(--mono);
  color: var(--ink-2);
  text-decoration: none;
  letter-spacing: -.01em;
}

.txn-id:hover { color: var(--accent); text-decoration: underline; }

.txn-meta {
  display: block;
  font-size: 11px;
  color: var(--ink-3);
  margin-top: 3px;
}

/* The scan target. Bigger than anything else on the row, tabular so the
   decimal points line up down the column. */
.txn-amount {
  font: 640 14.5px/1 var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  white-space: nowrap;
}

.txn-amount .ccy {
  font-family: var(--sans);
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ink-3);
  margin-left: 5px;
}

/* An empty cell says "nothing here" perfectly well. A column of em-dashes
   is noise the eye has to step over on the way to the next row. */
.txn-none { color: var(--line-strong); }


/* ==========================================================================
   VERTICAL RHYTHM
   Everything on every page sat at the same 14px gap, so a row of tiles was
   separated from a whole new section by exactly as much as two rows inside
   one table. Spacing carries no information when it is uniform -- it is the
   thing that tells the eye where one idea stops and the next begins.

   Three steps, and nothing between them:
     8px   inside a component
     20px  between blocks that belong together
     38px  between sections
   ========================================================================== */

/* Blocks that sit one after another down the page. */
.page > * + * { margin-top: 20px; }

/* A section heading starts something new, so it gets the large step above
   and a smaller one below -- it belongs to what follows it, not to what it
   just left. */
.page > h2.section,
.page > .section {
  margin-top: 38px;
  margin-bottom: 16px;
}

.page > *:first-child { margin-top: 0; }

/* `.stack` was doing two jobs at very different scales: a two-line label in
   the rail, and a column of whole panels on the transaction page. At 3px
   the second one had its panels touching. Inside a grid it is holding
   blocks, so it gets the block step. */
.grid .stack { gap: 20px; }

.grid { gap: 18px; }
.grid.tiles { gap: 16px; }

/* Panels stacked directly, without a grid between them. */
.panel + .panel { margin-top: 20px; }

/* Room inside, not only between. A panel whose content starts 16px from its
   own edge reads as full before it is. */
.panel-body { padding: 20px; }
.panel-body.tight { padding: 12px 20px; }
.panel-head { padding: 14px 20px; }
.panel-foot { padding: 12px 20px; }

table.data th { padding: 11px 20px; }
table.data td { padding: 14px 20px; }

.page-head { margin-bottom: 26px; }

/* The empty state was centred in a box barely taller than its own text. */
.empty { padding: 44px 24px; }

@media (max-width: 720px) {
  .page { padding: 20px 16px 56px; }
  .panel-body, .panel-head, .panel-foot { padding-inline: 16px; }
  table.data th, table.data td { padding-inline: 16px; }
  .page > * + * { margin-top: 16px; }
  .page > h2.section, .page > .section { margin-top: 30px; }
}

/* ---------------------------------------------------------------------------
   THE VENDOR FORM
   ------------------------------------------------------------------------ */

/* A gateway code is an identifier, not prose: it is typed once, read back
   off a callback URL, and matched against a row in the books. Setting it in
   the same face as the amounts and references everywhere else makes the
   characters that get confused when read aloud -- 0 and O, 1 and l -- tell
   themselves apart, and the slight tracking keeps a long one legible. */
input.ident {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: .02em;
}

/* The three fields in this row are peers: a code, a name, a type. One of
   them carried `grow` and came out twice the width of the other two, which
   read as though the select mattered more and left the two text inputs
   looking cramped beside it. Equal basis, and the row lines up.

   The basis is 260px rather than the old 210px because the inputs hold a
   gateway code and a legal name, and both were being truncated at a width
   chosen before either had a placeholder showing what goes in them. */
.panel-body > form .grid.thirds > .field {
  flex: 1 1 260px;
  min-width: 220px;
}
