/* bidnwin
 *
 * Colour is declared once, here, as roles. Every SVG the engine generates
 * references these custom properties rather than literal hex, so light and dark
 * are one definition and the charts are theme-agnostic.
 *
 * The categorical slots are 1–3 of the validated palette (blue / orange /
 * aqua): they clear the all-pairs CVD and normal-vision floors in both modes.
 * Aqua sits below 3:1 on the light surface, so every series drawn in it is
 * directly labelled — that is the relief rule, not an oversight.
 *
 * Polarity uses the diverging pair blue ↔ red. Status colours are reserved and
 * always ship with a word beside them; colour never carries meaning alone.
 */

/* ── Roboto Mono, served from this machine ───────────────────────────────
   Figures are read as data and want a monospace with tabular digits. The
   face came from Google's CDN until now, and that is what the flicker was:
   a DNS lookup and a TLS handshake to fonts.gstatic.com before the file
   even starts, so the first paint was always the fallback and the swap
   always visible.

   Same origin now — the connection is already open, the file is 33 KB, and
   `skin()` preloads it on the pages that are full of figures, so it is
   usually there before the first paint and there is nothing to swap.

   This does not contradict the note further down about web fonts costing a
   round trip on a village connection. That round trip was to a third party;
   this is a file on the same connection as the stylesheet asking for it.
   Declared here and not in market.css because every page loads this sheet
   and a declaration costs nothing until a rule uses the family.

   Two subsets, with the ranges Google itself publishes, so a page of Latin
   figures never fetches the accented one. Devanagari is deliberately absent:
   Noto Sans Devanagari carries the Nepali and no monospace here needs it. */
@font-face {
  font-family: "Roboto Mono";
  font-style: normal;
  /* One variable file covers the range; both weights we use come from it. */
  font-weight: 100 700;
  font-display: swap;
  src: url("/static/fonts/roboto-mono-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Roboto Mono";
  font-style: normal;
  font-weight: 100 700;
  font-display: swap;
  src: url("/static/fonts/roboto-mono-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  color-scheme: light;

  --plane:        #f9f9f7;
  --surface-1:    #fcfcfb;
  --heat:         #a8285c;
  --heat-ink:     #ffffff;
  --ink:          #0b0b0b;
  --ink-2:        #52514e;
  --muted:        #898781;
  --grid:         #e1e0d9;
  --baseline:     #c3c2b7;
  --border:       rgba(11, 11, 11, 0.10);
  --track:        #ececE6;

  /* The brand, one set for the whole application. market.css had these
     for the front of the house and the back office was still on the blue
     it was born with, so a shop's owner signed in through plum doors and
     arrived somewhere blue. Same values, declared once here. */
  --brand:        #93306f;   /* plum magenta */
  --brand-deep:   #6d2152;   /* pressed, and text on a soft tint */
  --brand-soft:   #fbf2f8;   /* a wash behind a panel or a badge */
  --brand-ink:    #ffffff;   /* what sits on the brand itself */

  /* The interactive colour is the brand, and it is also the first chart
     series. The other two keep their distance from it rather than from the
     blue that used to be here: orange still reads against plum, and the
     aqua is the third thing on a chart either way. */
  --series-1:     var(--brand);
  --series-2:     #eb6834;   /* orange */
  --series-3:     #1baf7a;   /* aqua   */

  /* Semantic colour is NOT the brand's to borrow. `pos` and `neg` mean
     added and deducted on a statement, and a reader learns them from the
     ledger rather than from the logo; they kept the blue on purpose when
     the brand took it over. */
  --pos:          #2a78d6;   /* diverging: adds    */
  --neg:          #d03b3b;   /* diverging: deducts */
  --neutral-mark: #898781;   /* diverging midpoint / totals */

  --status-good:     #0ca30c;
  --status-warning:  #fab219;
  /* The warning ramp's full strength. #fab219 is a chip colour and
     measures 1.74 against this ground as a fill; this measures 3.15. */
  --tone-warning:    #c97b16;
  --status-serious:  #ec835a;
  --status-critical: #d03b3b;
  --status-neutral:  #898781;
  --success-text:    #006300;

  --radius: 10px;
  --pad: 20px;

  /* The stand-in tile for a thing with no photograph. Only the lightness
     and saturation change between themes; the hue comes from the thing's
     own name (see the `tint` filter). */
  --thumb-s:      34%;
  --thumb-top:    93%;
  --thumb-bottom: 84%;
  --thumb-ink-s:  42%;
  --thumb-ink-l:  33%;
}

@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    color-scheme: dark;
    --plane:      #0d0d0d;
    --surface-1:  #1a1a19;
    --heat:       #f093b4;
    --heat-ink:   #2a0f1c;
    --ink:        #ffffff;
    --ink-2:      #c3c2b7;
    --muted:      #898781;
    --grid:       #2c2c2a;
    --baseline:   #383835;
    --border:     rgba(255, 255, 255, 0.10);
    --track:      #2c2c2a;

    --brand:      #eaa6d3;
    --brand-deep: #f6cce7;
    --brand-soft: #241c2f;
    --brand-ink:  #1b0d17;
    --series-1:   var(--brand);
    --series-2:   #d95926;
    --series-3:   #199e70;

    --pos:        #3987e5;
    --neg:        #d03b3b;
    /* Amber reads on a dark ground; it is the light one that needed
       taking down. */
    --tone-warning: #fab219;
    --success-text: #0ca30c;

    --thumb-s:      26%;
    --thumb-top:    27%;
    --thumb-bottom: 17%;
    --thumb-ink-s:  40%;
    --thumb-ink-l:  76%;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --plane:      #0d0d0d;
  --surface-1:  #1a1a19;
  --heat:       #f093b4;
  --heat-ink:   #2a0f1c;
  --ink:        #ffffff;
  --ink-2:      #c3c2b7;
  --muted:      #898781;
  --grid:       #2c2c2a;
  --baseline:   #383835;
  --border:     rgba(255, 255, 255, 0.10);
  --track:      #2c2c2a;

  --brand:      #eaa6d3;
  --brand-deep: #f6cce7;
  --brand-soft: #241c2f;
  --brand-ink:  #1b0d17;
  --series-1:   var(--brand);
  --series-2:   #d95926;
  --series-3:   #199e70;

  --pos:        #3987e5;
  --neg:        #d03b3b;
  --tone-warning: #fab219;
  --success-text: #0ca30c;

  --thumb-s:      26%;
  --thumb-top:    27%;
  --thumb-bottom: 17%;
  --thumb-ink-s:  40%;
  --thumb-ink-l:  76%;
}


* { box-sizing: border-box; }

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

a { color: var(--series-1); text-decoration: none; }
a:hover { text-decoration: underline; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}
.skip {
  position: absolute; left: -9999px; top: 0; padding: 10px 14px;
  background: var(--surface-1); z-index: 20;
}
.skip:focus { left: 8px; top: 8px; }

/* ───────────────────────────────────────────────────────────────── topbar */

.topbar {
  display: flex; align-items: center; gap: 18px;
  padding: 12px 24px;
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 10;
  flex-wrap: wrap;
}
.brand {
  display: flex; align-items: center; gap: 9px;
  font-weight: 650; letter-spacing: -0.01em; color: var(--ink);
}
.brand:hover { text-decoration: none; }
/* The mark beside an organisation's name. Its gradient ran from the
   interactive colour to the chart's third series — plum to aqua once the
   brand took the first — which is two unrelated colours meeting. The
   brand's own two instead. Still a mark rather than the logo: at fifteen
   pixels the graph and its shadow are mud, and the drawing in
   static/shramanka-mark.svg wants more room than this. */
.brand-mark {
  width: 15px; height: 15px; border-radius: 4px;
  background: linear-gradient(135deg, var(--brand), var(--brand-deep));
}
.search { flex: 1 1 280px; min-width: 200px; }
.search input {
  width: 100%; padding: 8px 12px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--plane); color: var(--ink); font: inherit;
}
.search input:focus { outline: 2px solid var(--series-1); outline-offset: 1px; }
.topnav { display: flex; gap: 16px; }
.topnav a { color: var(--ink-2); font-size: 14px; }
.theme-toggle {
  border: 1px solid var(--border); background: var(--plane); color: var(--ink-2);
  border-radius: 8px; width: 34px; height: 34px; cursor: pointer; font-size: 15px;
}

main { max-width: 1180px; margin: 0 auto; padding: 26px 24px 60px; }

/* ───────────────────────────────────────────────────────── page furniture */

.page-head { margin-bottom: 22px; }
.page-head h1 { font-size: 25px; margin: 0 0 4px; letter-spacing: -0.015em; }
.page-head .sub { color: var(--ink-2); margin: 0; font-size: 14px; }
.crumb { font-size: 13px; color: var(--muted); margin: 0 0 6px; }

.panel {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--pad);
  margin-bottom: 20px;
}
.panel > h2 {
  font-size: 13px; text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--muted); margin: 0 0 4px; font-weight: 650;
}
.panel > .panel-note {
  color: var(--ink-2); font-size: 13px; margin: 0 0 16px; max-width: 68ch;
}
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
@media (max-width: 900px) { .grid-2 { grid-template-columns: 1fr; } }

/* ───────────────────────────────────────────────────────────── stat tiles */

.tiles {
  display: grid; gap: 14px; margin-bottom: 20px;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
}
.tile {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 15px 17px;
}
.tile .label {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); font-weight: 650;
}
.tile .value {
  font-size: 27px; line-height: 1.15; margin-top: 6px; letter-spacing: -0.02em;
}
.tile .value .unit { font-size: 14px; color: var(--ink-2); margin-left: 3px; }
.tile .note { font-size: 12.5px; color: var(--ink-2); margin-top: 5px; }
.tile.good  .value { color: var(--success-text); }
.tile.warn  .value { color: var(--ink); }

/* ─────────────────────────────────────────────────────────────── the viz */

.viz { display: block; width: 100%; overflow: visible; }
.viz-label { font: 600 12px system-ui, sans-serif; fill: var(--ink); }
.viz-sub   { font: 11px system-ui, sans-serif; fill: var(--muted); }
.viz-value { font: 600 11.5px system-ui, sans-serif; fill: var(--ink-2); }
.viz-scroll { overflow-x: auto; }

.legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 10px; }
.legend span { display: inline-flex; align-items: center; gap: 6px;
               font-size: 12.5px; color: var(--ink-2); }
.swatch { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }

/* ───────────────────────────────────────────────────────────────── tables */

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
  text-align: left; font-size: 11.5px; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--muted); font-weight: 650;
  padding: 8px 10px; border-bottom: 1px solid var(--border); white-space: nowrap;
}
td { padding: 9px 10px; border-bottom: 1px solid var(--grid); vertical-align: top; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: color-mix(in srgb, var(--series-1) 5%, transparent); }
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.neg { color: var(--neg); }
.pos { color: var(--success-text); }
.table-scroll { overflow-x: auto; margin: 0 calc(var(--pad) * -1) calc(var(--pad) * -1); }
/* A table narrow enough to fit its panel wants none of what `.table-scroll`
   does for a wide one: no 720px floor forcing it sideways, no bleeding to the
   panel edge, and above all no negative bottom margin — which lifted a total
   row clean out of its own rounded box, where `overflow: hidden` then clipped
   it. Scrolls only if it genuinely has to. */
.fits { overflow-x: auto; margin: 0; }
.fits table { min-width: 0; }
.table-scroll table { min-width: 720px; }
.table-scroll th:first-child, .table-scroll td:first-child { padding-left: var(--pad); }
.table-scroll th:last-child,  .table-scroll td:last-child  { padding-right: var(--pad); }

tr.group td { background: color-mix(in srgb, var(--ink) 3%, transparent); font-weight: 600; }
tr.total td { border-top: 2px solid var(--baseline); font-weight: 650; }
.item-no { font-variant-numeric: tabular-nums; color: var(--ink-2); white-space: nowrap; }
.desc { max-width: 46ch; color: var(--ink-2); font-size: 13px; }

/* ─────────────────────────────────────────────────────────────── chips */

.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px; border-radius: 999px; font-size: 12px; font-weight: 600;
  border: 1px solid var(--border); color: var(--ink-2); white-space: nowrap;
}
.chip::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--status-neutral);
}
.chip.good::before     { background: var(--status-good); }
.chip.warning::before  { background: var(--status-warning); }
.chip.serious::before  { background: var(--status-serious); }
.chip.critical::before { background: var(--status-critical); }
.chip.neutral::before  { background: var(--status-neutral); }
.chip-plain::before { display: none; }

.tag {
  display: inline-block; padding: 1px 7px; border-radius: 5px;
  background: color-mix(in srgb, var(--series-1) 12%, transparent);
  color: var(--ink-2); font-size: 11.5px; font-weight: 600;
}
/* Which of the two things the network sells. The same word has to mean the
   same colour at the till, on the bill and on the customer's own page, so
   the modifiers live here rather than in one surface's stylesheet. */
.tag-emi   { background: color-mix(in srgb, var(--series-1) 14%, transparent); color: var(--series-1); }
.tag-bnpl  { background: color-mix(in srgb, var(--series-2) 14%, transparent); color: var(--series-2); }
.tag-demo  { background: var(--track); color: var(--ink-2); }

/* A label-and-figure grid. Hairline cells so a row of facts reads as one
   object rather than as several. Defined here because the till, a bill and a
   customer's own page all want the same thing. */
.facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1px; margin: 0; background: var(--grid);
  border: 1px solid var(--grid); overflow: hidden;
}
.facts > div { background: var(--surface-1); padding: 9px 12px; }
.facts dt { font-size: 10px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin: 0 0 2px; }
.facts dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }
.facts dd.free { color: var(--success-text); }

/* The promise, wherever somebody is deciding whether to come in. Small and
   set in the heading face: a tagline shouted is a tagline nobody believes. */
.brand-line {
  margin: 0 0 10px; font-size: 13px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--series-1);
}
.find-hero .brand-line { font-size: 14px; letter-spacing: .05em; }

/* The front door's own navigation: where a visitor who is not signed in
   chooses which of the two doors is theirs. Pushed left so it is read before
   the theme toggle, which is the least important thing on the bar. */
.frontnav { display: flex; align-items: center; gap: 4px; margin-right: auto; }
.frontnav a { padding: 6px 11px; border-radius: var(--radius); color: var(--ink-2);
              font-size: 14px; font-weight: 600; text-decoration: none; }
.frontnav a:hover { background: var(--surface-1); color: var(--ink); text-decoration: none; }
.frontnav a.button { margin-left: 4px; }
@media (max-width: 420px) { .frontnav a:first-child { display: none } }

/* The gallery as a merchant manages it: every picture at a glance, with a
   number so it is obvious which order the shop will slide them in. */
.shot-grid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); margin-top: 10px; }
.shot-cell { margin: 0; border: 1px solid var(--grid); border-radius: var(--radius); overflow: hidden; background: var(--plane); }
.shot-cell img { width: 100%; height: 110px; object-fit: cover; display: block; }
.shot-cell figcaption { display: flex; justify-content: space-between; align-items: center;
                        gap: 8px; padding: 5px 9px; border-top: 1px solid var(--grid); }

/* A list of things happening now — a delivery crossing town, an appointment
   this afternoon. One row each, what it is on the left, where it has got to
   on the right. */
.live-list { display: flex; flex-direction: column; }
.live-row {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 16px;
  flex-wrap: wrap; padding: 11px 0; border-top: 1px solid var(--grid);
}
.live-row:first-child { border-top: 0; }
.live-what { font-weight: 600; }
.live-row .desc { margin: 2px 0 0; }
.live-aside { text-align: right; }

/* What went where this month. One measure across a few shops, so one hue:
   the shop's name says which row is which, and a second colour would be
   decoration pretending to carry data. Every row is a link. */
.spend { display: flex; flex-direction: column; }
.spend-row {
  display: grid; grid-template-columns: minmax(7em, 12em) 1fr auto;
  align-items: center; gap: 12px; padding: 8px 0; color: inherit; text-decoration: none;
  border-top: 1px solid var(--grid);
}
.spend-row:first-child { border-top: 0; }
.spend-row:hover { text-decoration: none; }
.spend-row:hover .spend-who { color: var(--series-1); }
.spend-who { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spend-track { height: 10px; background: var(--track); }
.spend-bar {
  display: block; height: 100%; background: var(--series-1);
  /* Anchored to the baseline; only the growing end is rounded. */
  border-radius: 0 4px 4px 0;
}
.spend-amt { font-weight: 600; font-variant-numeric: tabular-nums; }
@media (max-width: 460px) {
  .spend-row { grid-template-columns: 1fr auto; }
  .spend-track { display: none; }
}

/* ─────────────────────────────────────────────── a customer's ways to pay */

.methods { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.method {
  display: flex; flex-direction: column; gap: 10px;
  padding: 18px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface-1);
}
.method-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.method-head h2 { margin: 0 0 3px; font-size: 18px; }
.method-head .desc { margin: 0; max-width: 42ch; }
.method-detail { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }
.method-note { margin: 0; font-size: 13px; color: var(--muted); }
.method .actions { margin-top: auto; }

/* ──────────────────────────────────────────────────── a customer's credit */

/* The next thing they owe. It is the one figure on the page somebody opens
   it to see, so it is the largest. */
.next-due .due-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 18px; flex-wrap: wrap; }
.due-figure { font-size: 30px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; margin: 2px 0 3px; }
.due-when { text-align: right; }
.due-date { font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; margin-bottom: 5px; }

.loan-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
.loan-head h2 { margin: 0 0 3px; }
.loan-who .desc { margin: 0; }
.loan-money { display: flex; flex-wrap: wrap; gap: 10px 34px; margin: 15px 0 12px; }
.loan-figure { font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums; }
.loan-figure.strong { color: var(--ink); font-weight: 700; }
.loan-progress { display: flex; align-items: center; gap: 11px; margin-bottom: 12px; }
.loan-progress .bar { flex: 1 1 auto; min-width: 120px; }
.loan-progress .desc { white-space: nowrap; }
.sched-fold > summary { margin-bottom: 8px; }

/* A schedule is read by finding one row in it: the next one, or a late one.
   Everything else is background. */
.due-table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.due-table th, .due-table td { padding: 7px 10px; text-align: left; border-bottom: 1px solid var(--grid); }
.due-table th { font-size: 11.5px; font-weight: 600; color: var(--muted); letter-spacing: .04em; text-transform: uppercase; }
.due-table .num { text-align: right; }
.due-table .rn { color: var(--muted); width: 3em; }
.due-table tbody tr:last-child td { border-bottom: 0; }
.due-table tr.is-paid td { color: var(--muted); }
.due-table tr.is-next td { background: color-mix(in srgb, var(--status-warning) 8%, transparent); font-weight: 600; }
.due-table tr.is-late td { background: color-mix(in srgb, var(--status-critical) 8%, transparent); font-weight: 600; }

/* ───────────────────────────────────────────────────────────── contracts */

.cards { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
.card {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 18px; display: block; color: inherit;
}
.card:hover { text-decoration: none; border-color: var(--series-1); }
.card h3 { margin: 0 0 3px; font-size: 16px; letter-spacing: -0.01em; }
.card .no { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.card .parties { font-size: 13px; color: var(--ink-2); margin: 10px 0 14px; }
.bar { height: 9px; border-radius: 5px; background: var(--track); overflow: hidden; }
.bar > i { display: block; height: 100%; border-radius: 5px; background: var(--series-1); }
.bar > i.over { background: var(--status-warning); }
.card .figures { display: flex; justify-content: space-between; gap: 12px;
                 margin-top: 9px; font-size: 12.5px; color: var(--ink-2); }

.kv { display: grid; grid-template-columns: auto 1fr; gap: 5px 18px; font-size: 13.5px; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; font-variant-numeric: tabular-nums; }

/* ───────────────────────────────────────────────────────────────── forms */

form.stack { display: grid; gap: 13px; max-width: 460px; }
.field { display: grid; gap: 4px; }
.field label { font-size: 12.5px; color: var(--ink-2); font-weight: 600; }
.field input, .field select, .field textarea {
  padding: 8px 11px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--plane); color: var(--ink); font: inherit;
}
.field .hint { font-size: 12px; color: var(--muted); }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 13px; }
.check { display: flex; align-items: center; gap: 8px; font-size: 13.5px; color: var(--ink-2); }
button.primary {
  padding: 9px 18px; border-radius: 8px; border: none; cursor: pointer;
  background: var(--series-1); color: #fff; font: inherit; font-weight: 600;
  justify-self: start;
}
button.primary:hover { filter: brightness(1.06); }

.notice {
  padding: 11px 14px; border-radius: 8px; font-size: 13.5px; margin-bottom: 16px;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--status-good) 10%, transparent);
}
.notice-bad {
  background: color-mix(in srgb, var(--status-critical) 10%, transparent);
}
.notice p { margin: 4px 0 0; }

/* ──────────────────────────────────────────────────────────────── lookup */

.facets { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.facets a { font-size: 12.5px; }
.facets a.on .chip { border-color: var(--series-1); color: var(--ink); }
.hits { display: grid; gap: 2px; }
.hit {
  display: grid; grid-template-columns: 104px 1fr auto; gap: 16px;
  padding: 12px 14px; border-radius: 8px; color: inherit; align-items: baseline;
}
.hit:hover { background: color-mix(in srgb, var(--series-1) 6%, transparent);
             text-decoration: none; }
.hit .k { font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.05em;
          color: var(--muted); font-weight: 650; }
.hit .t { font-weight: 600; }
.hit .s { font-size: 13px; color: var(--ink-2); }
.hit .d { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
@media (max-width: 700px) { .hit { grid-template-columns: 1fr; gap: 3px; } }

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

/* ────────────────────────────────────────────────────────── the Top Sheet */

.topsheet td, .topsheet th { padding: 7px 10px; }
.topsheet .row-label { color: var(--ink-2); }
.topsheet .rowid {
  color: var(--muted); font-weight: 650; width: 26px;
  font-variant-numeric: tabular-nums;
}
.topsheet tr.generated td { background: color-mix(in srgb, var(--series-1) 6%, transparent); }
.topsheet .gen-flag { font-size: 11px; color: var(--series-1); font-weight: 650; }

.signatures {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 18px; margin-top: 8px;
}
.sig { border-top: 1px solid var(--baseline); padding-top: 8px; font-size: 13px; }
.sig .role { font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.05em;
             color: var(--muted); font-weight: 650; }

.foot {
  max-width: 1180px; margin: 0 auto; padding: 22px 24px 40px;
  border-top: 1px solid var(--border); color: var(--muted); font-size: 12.5px;
}
.foot p { margin: 0; max-width: 78ch; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Cascadia Mono", "DejaVu Sans Mono", "Liberation Mono", monospace; font-size: 0.92em; }

.tabs { display: flex; gap: 4px; margin-bottom: 18px; flex-wrap: wrap; }
.tabs a {
  padding: 7px 14px; border-radius: 8px; font-size: 13.5px; color: var(--ink-2);
  border: 1px solid transparent;
}
.tabs a.on { background: var(--surface-1); border-color: var(--border); color: var(--ink);
             font-weight: 600; }

/* ────────────────────────────────────────────────────────── notifications */

.bell-count {
  position: absolute; top: -4px; right: -4px; min-width: 16px; height: 16px;
  border-radius: 8px; padding: 0 4px; font-size: 10px; line-height: 16px;
  background: var(--status-critical); color: #fff; text-align: center;
}
#bell { position: relative; }
#bell .bell-count:empty { display: none; }
.toasts {
  position: fixed; right: 18px; bottom: 18px; display: grid; gap: 8px;
  z-index: 50; max-width: 340px;
}
.toast {
  display: grid; gap: 2px; padding: 11px 14px; border-radius: 10px;
  background: var(--surface-1); border: 1px solid var(--border); color: inherit;
  box-shadow: 0 6px 24px rgba(0,0,0,.18); font-size: 13.5px;
  transition: opacity .4s, transform .4s;
}
.toast strong { font-weight: 650; }
.toast span { color: var(--ink-2); }
.toast.gone { opacity: 0; transform: translateY(6px); }

/* The module's own name sits under the platform's, on a module page. */
.brand-sub{display:block;font-size:.62em;font-weight:500;opacity:.62;letter-spacing:.02em;line-height:1}

/* ---------------------------------------------------------------------------
   A plan is a picture of a place: tables on a floor, chairs in a salon,
   rooms, aisles. The renderer is `platform::plan::svg`; every tone carries
   a word as well as a colour, because colour alone is the fastest thing on
   a screen and the least reliable.
   --------------------------------------------------------------------------- */
.plan-wrap { width: 100%; overflow-x: auto; }
.floorplan { width: 100%; height: auto; max-height: 68vh; display: block; }
.plan-grid line { stroke: var(--grid); stroke-width: 1; }
.plan-editing .plan-grid line { stroke-dasharray: 2 4; }

.spot rect, .spot ellipse {
  fill: var(--surface-1); stroke: var(--baseline); stroke-width: 1.5;
  transition: fill .12s, stroke .12s;
}
.spot text { fill: var(--ink); font-family: inherit; }
.spot text.label { font-size: 15px; font-weight: 640; }
.spot text.status, .spot text.line { font-size: 11.5px; fill: var(--ink-2); }

.spot.free rect, .spot.free ellipse { fill: color-mix(in srgb, var(--status-good) 12%, var(--surface-1)); stroke: var(--status-good); }
.spot.busy rect, .spot.busy ellipse { fill: color-mix(in srgb, var(--status-warning) 20%, var(--surface-1)); stroke: var(--status-warning); }
.spot.soon rect, .spot.soon ellipse { fill: color-mix(in srgb, var(--series-1) 12%, var(--surface-1)); stroke: var(--series-1); }
.spot.idle rect, .spot.idle ellipse { fill: var(--track); stroke: var(--baseline); }
.spot.idle text.label { font-weight: 500; fill: var(--ink-2); }
.spot.gone rect, .spot.gone ellipse { fill: none; stroke: var(--status-critical); stroke-dasharray: 5 4; }
.spot.gone text { fill: var(--status-critical); }

.spot.tap { cursor: pointer; }
.spot.tap:hover rect, .spot.tap:hover ellipse { stroke-width: 2.5; }
.spot.tap:focus { outline: none; }
.spot.tap:focus-visible rect, .spot.tap:focus-visible ellipse { stroke: var(--ink); stroke-width: 3; }
a.spot { text-decoration: none; }

/* The four boxes that say where a spot is, side by side. */
.nudge { display: flex; gap: 4px; align-items: center; }
.nudge input { max-width: 3.6rem; }
tr.row-bad td { background: color-mix(in srgb, var(--status-critical) 8%, transparent); }

/* The key under a plan: what each colour means, in words. */
.plan-key { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; font-size: 13px; color: var(--ink-2); }
.plan-key span { display: inline-flex; align-items: center; gap: 6px; }
.plan-key i { width: 13px; height: 13px; border-radius: 4px; border: 1.5px solid var(--baseline); background: var(--surface-1); }
.plan-key i.free { background: color-mix(in srgb, var(--status-good) 12%, var(--surface-1)); border-color: var(--status-good); }
.plan-key i.busy { background: color-mix(in srgb, var(--status-warning) 20%, var(--surface-1)); border-color: var(--status-warning); }
.plan-key i.soon { background: color-mix(in srgb, var(--series-1) 12%, var(--surface-1)); border-color: var(--series-1); }
.plan-key i.idle { background: var(--track); }

/* ---------------------------------------------------------------------------
   The shell: one sidebar, one top bar, one phone tab bar, drawn for every
   page by platform::nav::fill. No page writes a menu link.
   --------------------------------------------------------------------------- */
:root { --console: #3d3a8c; }
@media (prefers-color-scheme: dark) { :root:where(:not([data-theme="light"])) { --console: #a9a5f5; } }
:root[data-theme="dark"] { --console: #a9a5f5; }

.shell { display: flex; min-height: 100vh; }
.shell-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
/* `main` takes the slack, so a footer sits at the bottom of the window even
   when the page is short. Without this a search that found nothing left the
   footer riding up under two lines of text with the rest of the screen
   blank beneath it, which reads as a page that failed to load rather than
   one with nothing to show. `100vh` on the shell was never the problem —
   nothing inside it grew. */
.shell-main > main { width: 100%; flex: 1 1 auto; }

.side {
  flex: none; width: 248px; position: sticky; top: 0; height: 100vh; overflow-y: auto;
  background: var(--surface-1); border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
}
.side-head { padding: 16px 16px 12px; border-bottom: 1px solid var(--border); display: grid; gap: 3px; }
.side-org { display: flex; align-items: center; gap: 9px; font-weight: 650; letter-spacing: -0.01em; color: var(--ink); }
.side-org:hover { text-decoration: none; }
.side-sub { padding-left: 24px; font-size: 12.5px; color: var(--muted); }
.side-tag {
  margin-left: 24px; justify-self: start; padding: 1px 7px; border-radius: 4px;
  font-size: 10.5px; font-weight: 650; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--console); background: color-mix(in srgb, var(--console) 12%, transparent);
}
.brand-mark-console { background: linear-gradient(135deg, var(--console), var(--series-1)); }
.side-groups { flex: 1; padding: 8px; display: grid; gap: 6px; align-content: start; }
.side-label { font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); font-weight: 650; padding: 8px 8px 3px; }
.side-link {
  display: flex; align-items: center; gap: 10px; min-height: 30px; padding: 4px 8px;
  border-radius: 7px; color: var(--ink-2); font-size: 13.5px;
}
.side-link svg { width: 16px; height: 16px; flex: none; }
.side-link:hover { background: color-mix(in srgb, var(--ink) 5%, transparent); color: var(--ink); text-decoration: none; }
.side-link[aria-current="page"] { background: color-mix(in srgb, var(--series-1) 12%, transparent); color: var(--series-1); font-weight: 600; }
.side-admin .side-link[aria-current="page"] { background: color-mix(in srgb, var(--console) 12%, transparent); color: var(--console); }
.side-foot { border-top: 1px solid var(--border); padding: 12px 16px; font-size: 12.5px; color: var(--ink-2); display: grid; gap: 1px; }
.side-foot span + span { color: var(--muted); }

.top-gap { flex: 1 1 auto; }
.shell-brand { display: none; }
.signin { padding: 7px 14px; border-radius: 8px; background: var(--series-1); color: #fff; font-weight: 600; font-size: 14px; }
.signin:hover { text-decoration: none; filter: brightness(1.06); }
.usermenu { position: relative; }
.usermenu > summary { list-style: none; cursor: pointer; }
.usermenu > summary::-webkit-details-marker { display: none; }
.avatar {
  width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  background: var(--track); border: 1px solid var(--border); color: var(--ink-2); font-size: 12.5px; font-weight: 600;
}
.usermenu-panel {
  position: absolute; right: 0; top: 42px; z-index: 40; min-width: 230px; padding: 8px;
  display: grid; gap: 2px; background: var(--surface-1); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.14);
}
.usermenu-panel strong { padding: 6px 10px 0; }
.usermenu-who { padding: 0 10px 6px; font-size: 12.5px; color: var(--muted); }
.usermenu-panel a { padding: 8px 10px; border-radius: 7px; color: var(--ink); font-size: 14px; }
.usermenu-panel a:hover { background: color-mix(in srgb, var(--ink) 5%, transparent); text-decoration: none; }
.signout { margin: 0; }
.signout button {
  width: 100%; text-align: left; padding: 8px 10px; border: 0; border-radius: 7px; background: none;
  color: var(--ink); font: inherit; font-size: 14px; cursor: pointer;
}
.signout button:hover { background: color-mix(in srgb, var(--ink) 5%, transparent); }
.side-foot .signout { margin-top: 8px; }
.side-foot .signout button { padding: 6px 0; font-size: 13px; color: var(--ink-2); }
.side-foot .signout button:hover { background: none; color: var(--ink); text-decoration: underline; }

.today-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
.today-tile {
  min-height: 96px; display: flex; flex-direction: column; justify-content: space-between; gap: 12px;
  padding: 16px; border-radius: 12px; background: var(--surface-1); border: 1px solid var(--border);
  color: var(--ink); font-weight: 600;
}
.today-tile svg { width: 24px; height: 24px; color: var(--series-1); }
.today-tile:hover { text-decoration: none; border-color: color-mix(in srgb, var(--series-1) 40%, transparent); }

.tabbar { display: none; }
@media (max-width: 860px) {
  .side { display: none; }
  .shell-brand { display: flex; }
  .topbar .topnav { display: none; }
  .tabbar {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    background: var(--surface-1); border-top: 1px solid var(--border);
    padding: 4px 4px calc(8px + env(safe-area-inset-bottom));
  }
  .tab, .tab-more {
    flex: 1 1 0; min-width: 0;
  }
  .tab {
    min-height: 52px; display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px; font-size: 11px; color: var(--muted); list-style: none; cursor: pointer;
  }
  .tab::-webkit-details-marker { display: none; }
  .tab svg { width: 22px; height: 22px; }
  .tab span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tab:hover { text-decoration: none; }
  .tab[aria-current="page"] { color: var(--series-1); font-weight: 600; }
  .tab-more .sheet {
    position: fixed; left: 0; right: 0; bottom: 64px; max-height: 72vh; overflow-y: auto;
    background: var(--surface-1); border-top: 1px solid var(--border); border-radius: 16px 16px 0 0;
    box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.14); padding: 10px 12px 16px; display: grid; gap: 4px;
  }
  .tab-more .side-link { min-height: 44px; }
  main { padding-bottom: 96px; }
}

/* An application's conversation, and a folded form. */
.note-item { padding: 10px 0; border-top: 1px solid var(--border); }
.note-item:first-of-type { border-top: 0; }
.note-body { margin: 4px 0; white-space: pre-wrap; }
.note-applicant .note-body { color: var(--ink); }
.note-doc { margin-top: 4px; }
.note-ask { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.check { display: flex; gap: 8px; align-items: center; font-size: 14px; }
.fold { margin-top: 14px; }
.fold > summary { cursor: pointer; color: var(--series-1); font-weight: 600; }
.fold[open] > summary { margin-bottom: 10px; }
table.facts th { text-align: left; font-weight: 500; color: var(--muted); width: 36%; }

/* A link that looks like a button, a row of controls above a list. */
.button { display: inline-flex; align-items: center; justify-content: center; padding: 7px 14px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface-1); color: var(--ink); font-weight: 600; font-size: 14px; }
.button:hover { text-decoration: none; border-color: color-mix(in srgb, var(--series-1) 40%, transparent); }
.button.primary { background: var(--series-1); border-color: var(--series-1); color: #fff; }
.button.small { padding: 4px 10px; font-size: 13px; }
.toolbar { display: flex; gap: 12px; flex-wrap: wrap; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.row-quiet > td { opacity: .6; }

/* Forms in parts: what is always there, and what opens when it is wanted. */
fieldset.group { border: 0; border-top: 1px solid var(--border); margin: 0; padding: 14px 0 0; display: grid; gap: 12px; min-width: 0; }
fieldset.group:first-of-type { border-top: 0; padding-top: 0; }
fieldset.group > legend { font-weight: 650; padding: 0 0 6px; }
.group-toggle:has(> .check input:not(:checked)) > :not(legend):not(.check) { display: none; }
.pack-ticks { display: flex; flex-wrap: wrap; gap: 6px 16px; }

/* An on/off switch that is a real button in a form. */
.switch { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--border); border-radius: 999px;
  padding: 2px 10px 2px 3px; background: var(--surface-1); font: inherit; font-size: 12.5px; color: var(--muted); cursor: pointer; }
.switch span { width: 16px; height: 16px; border-radius: 50%; background: var(--track); border: 1px solid var(--border); }
.switch.on { color: var(--series-1); border-color: color-mix(in srgb, var(--series-1) 45%, transparent); }
.switch.on span { background: var(--series-1); border-color: var(--series-1); }
.switch:focus-visible { outline: 2px solid var(--series-1); outline-offset: 2px; }
.order-buttons { white-space: nowrap; }
.order-buttons form { display: inline; }

/* Sets of sections to add. */
.packs { display: grid; gap: 8px; }
.pack { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 8px 0; border-top: 1px solid var(--border); }
.pack:first-child { border-top: 0; }
.pack-added strong { color: var(--muted); }

/* A decision table, as its version shows it. */
.rule-table code { white-space: pre-wrap; word-break: break-word; font-size: 12.5px; }
.rule-table th.rule-in, .rule-table td.rule-in { background: color-mix(in srgb, var(--series-1) 5%, transparent); }
.rule-table th.rule-out { color: var(--ink); }
textarea.code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Cascadia Mono", "DejaVu Sans Mono", "Liberation Mono", monospace; font-size: 12.5px; width: 100%; }
ul.problems { margin: 0 0 12px; padding-left: 18px; color: var(--critical, #b42318); }

/* Editing a decision table in place. */
.rule-edit td { vertical-align: top; padding: 4px 6px; }
.rule-edit input.cell { width: 100%; min-width: 9rem; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Cascadia Mono", "DejaVu Sans Mono", "Liberation Mono", monospace; font-size: 12.5px; padding: 4px 6px; }
.rule-edit input.cell-short { min-width: 6rem; }
.rule-edit input.cell-label { width: 100%; min-width: 12rem; font-size: 13px; padding: 4px 6px; }
.rule-edit th .linkish { padding: 0 4px; }
.cell-note { display: block; font-size: 11.5px; line-height: 1.3; min-height: 0; }
.cell-ok { color: var(--good, #067647); }
.cell-bad { color: var(--critical, #b42318); }
.try-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px 14px; }


/* ── Demo polish: one base for native controls, and layouts that hold on a phone ──
   `:where()` keeps these at zero specificity, so `.field`, `.primary`,
   `.linkish` and every module's own rules still win. */
:root { --line: var(--border); --panel: var(--surface-1); }
:where(button, input, select, textarea) { font: inherit; color: inherit; }
:where(button:not([class])) {
  padding: 6px 12px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--surface-1); color: var(--ink); font-weight: 600; font-size: 13.5px; cursor: pointer;
}
:where(button:not([class])):hover { border-color: color-mix(in srgb, var(--series-1) 40%, transparent); }
:where(select, input:not([type=checkbox], [type=radio], [type=hidden], [type=submit])) {
  padding: 6px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--plane);
}
form.stack, .field { align-content: start; }
form.stack > button { justify-self: start; }
.grid-2 > * { min-width: 0; }
.grid-2 .table-scroll table { min-width: 0; }
.cards { grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); }
form.search, div.search { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
form.search input, div.search input { flex: 1 1 200px; width: auto; }
td.actions { white-space: nowrap; }
td.actions .actions { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
td.actions form { display: inline-block; }
.hint { font-size: 12px; color: var(--muted); font-weight: 400; }
.status { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 12px; font-weight: 600;
  color: var(--ink-2); background: color-mix(in srgb, var(--status-neutral) 14%, transparent); }
.status.placed, .status.pending, .status.draft { background: color-mix(in srgb, var(--status-warning) 22%, transparent); color: var(--ink); }
.status.accepted, .status.ordered, .status.paid, .status.posted { background: color-mix(in srgb, var(--series-1) 14%, transparent); color: var(--ink); }
.status.ready, .status.settled, .status.received, .status.completed { background: color-mix(in srgb, var(--status-good) 16%, transparent); color: var(--success-text); }
.status.cancelled { background: color-mix(in srgb, var(--status-critical) 14%, transparent); color: var(--neg); }
td, .tile .value, .kv dd { font-variant-numeric: tabular-nums; }
.table-scroll:not(:last-child) { margin-bottom: 14px; }
.table-card { text-align: left; font: inherit; }
.module-choice { padding: 8px 0; border-top: 1px solid var(--border); }
.module-choice:first-of-type { border-top: 0; }
.module-parts { margin: 6px 0 0 1.6rem; }
pre.json { max-height: 360px; overflow: auto; font-size: 12px; background: var(--plane); padding: 10px; border-radius: 8px; }
@media (max-width: 520px) { .row2 { grid-template-columns: 1fr; } }

/* Product photos */
.item-thumb { width: 34px; height: 34px; object-fit: cover; border-radius: 6px; vertical-align: middle; margin-right: 8px; }
.item-photo { width: 100%; max-width: 360px; aspect-ratio: 1; object-fit: cover; border-radius: 10px; display: block; margin-bottom: 12px; }
.shop-photo { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 8px; margin-bottom: 8px; background: var(--plane); }
.shop-photo.thumb-none { font-size: 30px; }

/* A button that reads as a link: remove, cancel, send again. */
button.linkish { background: none; border: 0; padding: 0; color: var(--series-1); font-weight: 500; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
button.linkish:hover { color: var(--neg); }

/* An offer in the online shop and on the counter. */
.offer-tag { display:inline-block; align-self:start; padding:2px 9px; border-radius:999px; font-size:12px; font-weight:600;
  background: color-mix(in srgb, var(--status-good) 16%, transparent); color: var(--success-text); }

/* An element the page hides stays hidden, whatever a class says. */
[hidden] { display: none !important; }

/* ───────────────────────────────────────────────── forms: sized to what they hold
   A form is a reading column, never the whole panel. A box is as wide as what
   goes in it: a date is a date wide, a number a number wide, a name a line.
   Side-by-side fields collapse to one column on a phone. */
form.stack { max-width: 520px; }
.field input, .field select, .field textarea { width: 100%; max-width: 34em; min-width: 0; box-sizing: border-box; }
.field input[type="date"], .field input[type="time"] { max-width: 11.5em; }
.field input[inputmode="decimal"], .field input[inputmode="numeric"], .field input[type="number"] { max-width: 9em; text-align: right; font-variant-numeric: tabular-nums; }
.field input[inputmode="tel"] { max-width: 13em; }
.field input[maxlength="9"], .field input[maxlength="10"] { max-width: 10em; }
.field select { width: auto; max-width: 100%; }
.field textarea { min-height: 4.5em; }
.field label { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.field label .hint { font-weight: 400; }
/* A tooltip: the label says what the box is for; the title says what is expected. */
.field label[title] { cursor: help; }
.field label[title]::after { content: "?"; display: inline-grid; place-items: center; width: 14px; height: 14px; border-radius: 50%; font-size: 10px; font-weight: 700; color: var(--muted); border: 1px solid var(--border); margin-left: 2px; }
.row2, .row3 { display: grid; gap: 13px; align-items: start; }
.row2 { grid-template-columns: 1fr 1fr; }
.row3 { grid-template-columns: 1fr 1fr 1fr; }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; align-items: start; }
.grid-3 > * { min-width: 0; }
.grid-2 form.stack, .grid-3 form.stack { max-width: none; }
.sub-row > td { padding: 6px 10px 12px; background: color-mix(in srgb, var(--ink) 2%, transparent); }
.sub-row details > summary { margin: 2px 0 6px; }
.sub-row form.stack { max-width: none; }
form.inline, .inline { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.inline input, .inline select { padding: 6px 9px; border: 1px solid var(--border); border-radius: 8px; background: var(--plane); color: var(--ink); font: inherit; width: auto; max-width: 14em; }
.inline input[type="date"] { max-width: 11.5em; }
.inline label { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--ink-2); }
.stats { display: flex; flex-wrap: wrap; gap: 16px 28px; }
.stat .stat-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.stat .stat-value { font-size: 20px; font-weight: 600; font-variant-numeric: tabular-nums; }
@media (max-width: 900px) { .grid-3 { grid-template-columns: 1fr; } .row3 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .row3 { grid-template-columns: 1fr; } .field input, .field select { max-width: 100%; } }

/* ───────────────────────────────────────── a grid: a spreadsheet's habits
   One item to a row, columns across, the whole thing scrolling sideways
   rather than wrapping. A cell is its input; the borders are the table's. */
table.grid { border-collapse: separate; border-spacing: 0; min-width: 62em; font-variant-numeric: tabular-nums; }
table.grid th {
  position: sticky; top: 0; z-index: 1; background: var(--plane);
  font-size: 12px; font-weight: 600; color: var(--ink-2); text-align: left;
  padding: 7px 8px; border-bottom: 1px solid var(--border); white-space: nowrap;
}
table.grid th.num { text-align: right; }
table.grid th .hint { font-weight: 400; color: var(--muted); font-size: 11px; }
table.grid td { padding: 0; border-bottom: 1px solid var(--border); }
table.grid td.rn {
  text-align: right; padding: 0 8px; color: var(--muted); font-size: 12px;
  background: color-mix(in srgb, var(--ink) 3%, transparent); user-select: none;
}
table.grid td input {
  width: 100%; max-width: none; border: 0; border-radius: 0; background: transparent;
  padding: 8px 9px; font: inherit; font-size: 14px; color: var(--ink);
}
table.grid td input.num { text-align: right; }
table.grid td input:focus {
  outline: 2px solid var(--series-1); outline-offset: -2px;
  background: var(--plane); position: relative; z-index: 2;
}
table.grid td input::placeholder { color: color-mix(in srgb, var(--muted) 55%, transparent); }
table.grid tr:hover td { background: color-mix(in srgb, var(--ink) 2%, transparent); }
table.grid td button.linkish { padding: 0 8px; text-decoration: none; font-size: 16px; color: var(--muted); }
table.grid td button.linkish:hover { color: var(--neg); }

/* A heading row: a number and a title, nothing measured against it. The
   sheet is read by its headings, so the page shows one as the sheet does. */
table.grid tr.hd td { background: color-mix(in srgb, var(--series-1) 7%, transparent); }
table.grid tr.hd td.rn { color: var(--series-1); font-weight: 700; }
table.grid tr.hd td input[name="description"] { font-weight: 650; letter-spacing: .01em; }
table.grid tr.hd td input[name="unit"],
table.grid tr.hd td input[name="quantity"],
table.grid tr.hd td input[name="rate"] { opacity: .35; }

/* What the item could be, offered under the cell being typed in. */
.sheet { position: relative; }
.suggest {
  position: absolute; z-index: 30; min-width: 24em; max-width: 34em; max-height: 15em; overflow-y: auto;
  background: var(--plane); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .14); padding: 4px;
}
.suggest button.linkish {
  display: block; width: 100%; text-align: left; padding: 6px 8px; border-radius: 6px;
  text-decoration: none; font-size: 13px; color: var(--ink);
}
.suggest button.linkish:hover, .suggest button.linkish:focus, .suggest button.linkish.on {
  background: color-mix(in srgb, var(--series-1) 12%, transparent); color: var(--ink);
}
.suggest .desc { display: block; color: var(--muted); font-size: 12px; }

/* Recording a measurement without leaving the page: while it is in flight
   the block it came from is held still and says so. */
.mbook { position: relative; }
.mbook.busy, .mbook[data-panel].busy { opacity: .55; pointer-events: none; }
.mbook > .notice { margin: 0 0 10px; }
.working { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: 13px; }
.spinner {
  width: 13px; height: 13px; border-radius: 50%; display: inline-block;
  border: 2px solid color-mix(in srgb, var(--series-1) 30%, transparent);
  border-top-color: var(--series-1); animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.4s; } }

/* Anything posting in the background: held still while it is in flight, so
   a second tap does nothing and the page does not move under a thumb.
   One rule for every module — see /static/live.js. */
[data-panel].busy { opacity: .6; pointer-events: none; }
.live-notice { margin: 0 0 10px; }
.pos-ticket > .notice { margin: 0 0 10px; }

/* Where you are, beside the organisation's name. Three pages into the
   books a person needs the way back without reaching for the browser. */
.crumbs {
  display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden;
  font-size: 13px; color: var(--muted); white-space: nowrap;
}
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--ink); text-decoration: underline; }
.crumbs [aria-current="page"] { color: var(--ink); font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
.crumb-sep { color: color-mix(in srgb, var(--muted) 50%, transparent); }
/* On a phone the trail is one step: the way back, and nothing else. */
@media (max-width: 720px) {
  .crumbs { font-size: 12px; }
  .crumbs > a:not(:nth-last-child(3)), .crumbs > .crumb-sep:not(:nth-last-child(2)) { display: none; }
  .crumbs > a:nth-last-child(3)::before { content: "\2039\00a0"; }
}

/* ───────────────────────────────── an organisation's own colour
   One property, set on the shell from what the organisation chose, and
   every shade mixed from it. Nothing to keep in step: change the one and
   the rest follow. Where nobody has chosen, `--accent` is unset and the
   fallback is the stylesheet's own green, so an organisation that has
   never opened the Identity page looks exactly as it did.

   The steps are the same idea as a tonal ramp — 100 lightest through 900
   darkest — mixed against the page's own ground and ink so they sit right
   in the light theme and the dark one without being written twice. */
.brand, .side, .topbar { --accent-base: var(--accent, var(--series-1)); }
:root {
  --accent-100: color-mix(in oklab, var(--accent, var(--series-1)) 12%, var(--plane));
  --accent-200: color-mix(in oklab, var(--accent, var(--series-1)) 24%, var(--plane));
  --accent-400: color-mix(in oklab, var(--accent, var(--series-1)) 55%, var(--plane));
  --accent-600: var(--accent, var(--series-1));
  --accent-800: color-mix(in oklab, var(--accent, var(--series-1)) 70%, var(--ink));
}

/* The mark itself, where the plain square used to be. */
.brand-logo {
  height: 24px; width: auto; max-width: 120px; border-radius: 3px;
  object-fit: contain; display: block;
}

/* A typeface, at last: the system's own, in an order that gets a decent
   one on a Nepali laptop, a Windows desktop and an Android phone alike,
   and that has Devanagari where the page needs it. Nothing is downloaded:
   a web font is a second round trip before anybody can read anything, and
   that is expensive on a village connection. */
:root {
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans", "Noto Sans Devanagari", "Mangal", Arial, sans-serif;
  --font-heading: var(--font);
}
body { font-family: var(--font); }
h1, h2, h3, h4 { font-family: var(--font-heading); letter-spacing: -0.01em; }

/* ───────────────────────────────── the directory, as a stranger meets it
   A page that opens with the question rather than with a list: how many
   places there are, what you are looking for, and the kinds of place to
   start from when you cannot think of a word. */
.find-hero {
  padding: 40px 0 28px; text-align: center; max-width: 720px; margin: 0 auto;
}
.find-count { margin: 0 0 6px; color: var(--muted); font-size: 13px; letter-spacing: .02em; }
.find-hero h1 { margin: 0; font-size: clamp(26px, 5vw, 40px); line-height: 1.1; text-wrap: balance; }
.find-also { margin: 4px 0 0; color: var(--muted); font-size: clamp(15px, 2.6vw, 20px); }
.find-search { display: flex; gap: 8px; margin: 20px auto 0; max-width: 560px; }
.find-search input {
  flex: 1; min-width: 0; max-width: none; padding: 12px 14px; font-size: 16px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--surface-1); color: var(--ink);
}
.find-search input:focus { outline: 2px solid var(--accent, var(--series-1)); outline-offset: -1px; }
.find-search button { padding: 12px 20px; font-size: 15px; border-radius: 10px; }
.find-try { margin: 14px 0 0; display: flex; gap: 6px; justify-content: center; align-items: center; flex-wrap: wrap; }

/* The kinds of place: a name, its other name, and how many there are. */
.kinds { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.kind {
  display: flex; flex-direction: column; gap: 2px; padding: 14px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--surface-1);
  text-decoration: none; color: var(--ink); transition: border-color .12s, background .12s;
}
.kind:hover { border-color: var(--accent, var(--series-1)); background: var(--accent-100, var(--surface-1)); text-decoration: none; }
.kind.hit { border-color: var(--accent, var(--series-1)); }
.kind-name { font-weight: 600; font-size: 15px; }
.kind-also { color: var(--muted); font-size: 13px; }
.kind-count { margin-top: 6px; color: var(--accent, var(--series-1)); font-weight: 700; font-size: 18px; font-variant-numeric: tabular-nums; }

/* "List your business" sits in the public bar, before signing in. */
.list-yours {
  padding: 7px 12px; border: 1px solid var(--border); border-radius: 8px;
  font-size: 13px; text-decoration: none; color: var(--ink); white-space: nowrap;
}
.list-yours:hover { border-color: var(--accent, var(--series-1)); text-decoration: none; }
@media (max-width: 640px) { .list-yours { display: none; } }

/* ───────────────────────────────── one thing, on a page of its own
   A shop page is a wall of small cards, which is right for choosing and
   wrong for deciding. This is the deciding page: the picture, then what it
   costs and what the price is made of. */
.item-page { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: 24px; margin: 8px 0 20px; }
@media (max-width: 720px) { .item-page { grid-template-columns: 1fr; } }
.item-figure {
  border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
  background: var(--surface-1); aspect-ratio: 4 / 3; display: grid; place-items: center;
}
.item-figure img { width: 100%; height: 100%; object-fit: cover; }
.item-noimg { font-size: 64px; }
.item-facts h1 { margin: 0; font-size: clamp(22px, 4vw, 32px); line-height: 1.15; }
.item-also { margin: 2px 0 0; color: var(--muted); font-size: 18px; }
.item-price { margin: 16px 0 0; font-size: 34px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1; }
.item-cur { font-size: 18px; font-weight: 600; color: var(--muted); margin-right: 4px; }
.item-per { font-size: 15px; font-weight: 400; color: var(--muted); }
.item-was { margin: 4px 0 0; color: var(--muted); font-size: 14px; text-decoration: line-through; }
.item-buy { display: flex; gap: 8px; align-items: center; margin: 16px 0 0; }
.item-buy input { max-width: 5em; text-align: center; padding: 10px; font-size: 16px; }
.item-buy button { padding: 10px 22px; font-size: 15px; }
.item-desc { margin: 16px 0 0; max-width: 60ch; }

/* ───────────────────────────────── the front page, as panels
   Every panel declared its size and the grid honours it: a figure takes a
   quarter, a list takes a half, a table takes the row. Nothing here knows
   what any panel contains. */
.dash { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; align-items: start; }
.dash > .dash-small { grid-column: span 1; }
.dash > .dash-wide  { grid-column: span 2; }
.dash > .dash-full  { grid-column: span 4; }
@media (max-width: 1000px) {
  .dash { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dash > .dash-wide, .dash > .dash-full { grid-column: span 2; }
}
@media (max-width: 560px) {
  .dash { grid-template-columns: 1fr; }
  .dash > .dash-small, .dash > .dash-wide, .dash > .dash-full { grid-column: span 1; }
}
.dash-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; margin: 0 0 14px; }
.dash-head h1 { margin: 0; font-size: clamp(20px, 3vw, 26px); }
.dash-also { margin: 0; color: var(--muted); }
.dash-figure { margin: 0; font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; }
.dash .panel h2 { font-size: 13px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 0 0 8px; }

/* A place somebody deals with: its mark, its name, and what it holds of
   theirs. */
.place-head { display: flex; align-items: center; gap: 10px; }
.place-logo { height: 28px; width: auto; max-width: 72px; object-fit: contain; border-radius: 4px; }
.place-card h3 { margin: 0; }
.place-kv { margin-top: 8px; }

/* A thing somebody searched for, shown as the thing: the picture first,
   then what it is, then what it costs and who has it. A row in a table
   tells you none of that. */
.finds { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 12px; }
.find-item {
  display: flex; flex-direction: column; gap: 3px; padding: 10px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--surface-1);
  text-decoration: none; color: var(--ink);
}
.find-item:hover { border-color: var(--accent, var(--series-1)); text-decoration: none; }
.find-photo {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 7px;
  background: color-mix(in srgb, var(--ink) 4%, transparent); margin-bottom: 4px;
}
.find-photo.thumb-none { font-size: 30px; }
.find-name { font-weight: 600; line-height: 1.25; }
.find-desc {
  color: var(--muted); font-size: 13px; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.find-price { font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 2px; }
/* The whole card opens the item, so the picture and name are not links of
   their own inside it. */
.shop-open { display: contents; color: inherit; text-decoration: none; }
.shop-item:hover .shop-open strong { text-decoration: underline; }

/* ── A thing with no photograph ──────────────────────────────────────────
   Every shelf in this application shows the same stand-in: the till's item
   grid, the shop, the item page and the directory search. It used to be a
   grey letter on a flat ground, which on a dark screen reads as a picture
   that failed to load rather than a thing nobody has photographed yet.

   The tile takes its hue from the thing's own name, so a wall of them
   reads as a set. `--tint` is set on the element; everything else comes
   from the theme. Sizing stays with whichever grid owns the tile — this
   rule is appearance only, and adds nothing about width or shape. */
.thumb-none {
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1;
  text-transform: none;
  background: linear-gradient(155deg,
    hsl(var(--tint, 218) var(--thumb-s) var(--thumb-top)),
    hsl(var(--tint, 218) var(--thumb-s) var(--thumb-bottom)));
  color: hsl(var(--tint, 218) var(--thumb-ink-s) var(--thumb-ink-l));
}

/* ── The Loop mark ───────────────────────────────────────────────────────
   A payment method people are meant to recognise, so it is shown the way
   its owner shows it: a ring and the wordmark, in Loop's own indigo. Drawn
   rather than fetched — a till at a stall may have no network, and a logo
   that arrives late or not at all is worse than one that is always there.

   The brand colour is stated in oklch as Loop states it, with an sRGB hex
   first for anything that cannot read oklch. */
:root {
  --loop-accent: #4f46e5;
  --loop-accent: oklch(0.50 0.22 265);
  --loop-ink: #14162b;
  --loop-ink: oklch(0.16 0.03 260);
}
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    --loop-accent: #8b87f8;
    --loop-accent: oklch(0.70 0.17 265);
    --loop-ink: #ffffff;
  }
}
:root[data-theme="dark"] {
  --loop-accent: #8b87f8;
  --loop-accent: oklch(0.70 0.17 265);
  --loop-ink: #ffffff;
}

.loopmark { display: inline-flex; align-items: center; gap: 8px; vertical-align: middle; }
.loopmark-ring {
  width: 17px; height: 17px; border-radius: 50%; flex: none;
  border: 3.5px solid var(--loop-accent);
}
.loopmark-wm {
  font-weight: 800; letter-spacing: 1.5px; font-size: 15px; line-height: 1;
  color: var(--loop-ink);
}
/* On the payment panel's own heading the mark stands in for the word, so it
   carries the heading's size rather than its own. */
h2 .loopmark-wm { font-size: inherit; letter-spacing: 1.2px; }
h2 .loopmark-ring { width: 0.72em; height: 0.72em; border-width: 0.16em; }
.terminal-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 6px;
}

/* ── Filling in a missing thing, where you are ───────────────────────────
   A purchase order wants a vendor that has not been added yet. Sending
   somebody to the vendors page and back loses the order they were halfway
   through typing, so the vendor form comes to them instead. Behaviour is
   in live.js (`data-opens`, `data-from`, `data-close`); this is the look. */
dialog.sheet {
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
  color: var(--ink);
  width: min(460px, calc(100vw - 32px));
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.28);
}
dialog.sheet::backdrop { background: rgba(0, 0, 0, 0.45); }
.sheet-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.sheet-head h2 { margin: 0; font-size: 16px; }
.sheet-head form { margin: 0; }
.sheet-body { padding: 16px; }
.sheet-body .actions { display: flex; gap: 10px; align-items: center; margin-top: 4px; }
/* The button that opens one sits beside the box it fills in. */
.picker { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.picker > select { flex: 1 1 auto; min-width: 0; }

/* The platform mark inside somebody else's storefront: a way back to the
   directory that does not compete with the shop's own name. */
.home-mark {
  display: inline-flex; align-items: center; flex: none;
  padding: 4px; border-radius: 8px; opacity: 0.75;
  text-decoration: none;
}
.home-mark:hover { opacity: 1; background: color-mix(in srgb, var(--ink) 7%, transparent); }

/* Whoever took the picture, where its licence asks to be told. Quiet, and
   directly under the photograph it belongs to. */
.photo-credit { margin: 6px 0 0; font-size: 12px; color: var(--muted); line-height: 1.35; }

/* Which way of having a thing, on the card where it is chosen. */
.shop-way { width: 100%; margin-bottom: 6px; }

/* What comes with what: on the shelf as a promise, in the basket as a fact. */
.combo-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.combo-list li { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.earned {
  margin-top: 14px; padding: 12px 14px; border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--status-good) 35%, var(--border));
  background: color-mix(in srgb, var(--status-good) 8%, transparent);
}
.earned h3 { margin: 0 0 8px; font-size: 14px; }
.earned ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.earned li { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }

/* A key shown once. Wide, selectable, and impossible to mistake for prose. */
.key-once {
  margin: 8px 0 0; padding: 10px 12px; border-radius: 8px;
  background: var(--plane); border: 1px solid var(--border);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Cascadia Mono", "DejaVu Sans Mono", "Liberation Mono", monospace; font-size: 13px;
  overflow-x: auto; user-select: all;
}
tr.off { opacity: 0.55; }

/* What a place takes at the counter: Loop, QR, Card. Said on the card so a
   customer choosing between two shops knows before they walk. */
.takes { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin: 6px 0 0; }
.pay-tag {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em;
  border: 1px solid var(--border); color: var(--ink-2);
  background: color-mix(in srgb, var(--ink) 4%, transparent);
}

/* ───────────────────────────────────────────────── the measure sheet

   `parts/measure_sheet.html`, which every module that measures something
   includes. In app.css rather than a skin of its own because the sheet is
   an instrument used inside the console, where app.css is the system — a
   module that includes the part needs no stylesheet of its own, which is
   the point of the part. */

.measure-head { margin-bottom: 14px; }
.measure-head h2 { margin: 0; }
.measure-head .sub { margin: 4px 0 0; max-width: 72ch; }

/* Rows that wrap rather than a fixed grid: the context fields are declared
   by the host and there may be one of them or four, so the row has to take
   whatever it is given. Each field keeps the width its content needs — see
   the house rule on forms sized to content. */
.measure-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 16px;
  align-items: flex-end;
  margin-bottom: 12px;
}
.measure-row > .field { margin: 0; }
.measure-form > .field { margin-bottom: 12px; }
.measure-form .field label { display: block; margin-bottom: 4px; font-weight: 600; font-size: 13.5px; }
.measure-form .field .hint { font-weight: 400; color: var(--muted); font-size: 11.5px; }

/* The subject is the widest thing on the sheet and the one most often
   changed, so it gets the full width and a readable face. */
#ms-subject { width: 100%; max-width: 68ch; }

/* The dimensions, swapped in when the template changes. A fixed small width
   each, because a dimension is a number of a few digits and a field as wide
   as a description invites somebody to type a sentence into it. */
.measure-dims {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 14px;
  align-items: flex-end;
  margin-bottom: 12px;
  padding: 12px 14px;
  border: 1px dashed var(--baseline);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--series-1) 4%, transparent);
}
.measure-dims:empty { display: none; }
.measure-dims .field { margin: 0; }
.measure-dims input { max-width: 9ch; font-variant-numeric: tabular-nums; }
.measure-dims label { font-family: var(--font-mono, ui-monospace), Consolas, "Cascadia Mono", monospace; }

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

/* Over the agreed quantity: worth noticing, because it is how a variation
   gets discovered at the certificate rather than on the day. */
.measure-over { color: var(--status-warning); font-weight: 600; }

@media (max-width: 680px) {
  .measure-row > .field,
  .measure-form > .field { width: 100%; }
  #ms-subject { max-width: none; }
}

/* ─────────────────────────────────────────── measure mode's grid

   `parts/measure_grid.html`. One item, its rows, and the next row ready —
   a surveyor types twenty of these, so the row being typed sits in the
   same table as the rows above it and its columns line up with them. */

.measure-mode .mm-head {
  display: flex; flex-wrap: wrap; gap: 14px 24px;
  align-items: flex-start; justify-content: space-between; margin-bottom: 14px;
}
.measure-mode .mm-head h2 { margin: 0; }
.measure-mode .mm-head .sub { margin: 4px 0 0; }
.mm-tpl { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.mm-tpl label { font-weight: 600; font-size: 13px; }

.mgrid { overflow-x: auto; }
.mgrid-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.mgrid-table th {
  text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--baseline);
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
  white-space: nowrap;
}
.mgrid-table th.num, .mgrid-table td.num { text-align: right; }
.mgrid-table td { padding: 5px 8px; border-bottom: 1px solid var(--grid); vertical-align: top; }
.mgrid-table .mono, .mgrid-table .mg-qty { font-variant-numeric: tabular-nums; }
.mgrid-table .mg-n { color: var(--muted); font-variant-numeric: tabular-nums; width: 3.5rem; }
.mgrid-table .mg-qty { font-weight: 650; }

/* A reversed row is struck through and kept: nothing is ever removed from a
   measurement book, and the correction is a row of its own. */
.mgrid-table tr.mg-spent td { opacity: .5; text-decoration: line-through; }
.mgrid-table tr.mg-ded .mg-qty { color: var(--status-critical); }

/* The row being typed. A tint rather than a border, so the columns still
   read straight down from the rows above. */
.mgrid-table tr.mg-entry td {
  background: color-mix(in srgb, var(--series-1) 7%, transparent);
  border-bottom: 2px solid var(--series-1);
}
.mgrid-table tr.mg-entry input {
  width: 100%; min-width: 4.5rem; padding: 5px 6px;
  border: 1px solid var(--border); border-radius: 6px; background: var(--surface-1);
  font: inherit; font-variant-numeric: tabular-nums;
}
.mgrid-table tr.mg-entry input.num { text-align: right; }
.mgrid-table tr.mg-entry input:focus { outline: 2px solid var(--series-1); outline-offset: 0; }
.mgrid-table .mg-dim { min-width: 5rem; }
.mgrid-table .mg-x { width: 3.5rem; }
.mgrid-table .mg-q { min-width: 7rem; }

.mgrid-bar {
  display: flex; flex-wrap: wrap; gap: 12px 16px; align-items: center;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--grid);
}
.mgrid-bar .hint { color: var(--muted); font-weight: 400; font-size: 11.5px; }

/* ── the measurement summary ──────────────────────────────────────────
   The read half of measure mode. Banded by the bill's own sections,
   because an abstract of cost is checked section by section. */
.msum-head { display:flex; gap:1rem; align-items:flex-start; justify-content:space-between; flex-wrap:wrap; }
.msum-table { font-variant-numeric: tabular-nums; }
/* A section heading, set apart by weight and a rule rather than a fill:
   the bands are structure, not twelve more boxes. */
.msum-table tr.msum-band th {
  background: var(--surface-2, #f6f6f8);
  border-top: 2px solid var(--line, #d8d8de);
  text-align: left; font-weight: 600; padding-top: .55rem; padding-bottom: .45rem;
}
.msum-band-note { text-align:right; font-weight:400; color: var(--muted, #6a6a74); }
/* An item nobody has measured yet is still worth seeing — it is the work
   remaining — but it should not compete with the rows that have moved. */
.msum-untouched td { color: var(--muted, #6a6a74); }
.msum-untouched td a { color: inherit; }
/* This bill's own column, which is the one being worked in. */
.msum-now { font-weight: 600; }
.msum-over td { background: var(--warn-wash, #fff7ed); }
.msum-bar-h, .msum-bar-c { width: 10rem; }
.msum-bar {
  display:inline-block; width:6.5rem; height:.5rem; vertical-align:middle;
  background: var(--surface-3, #e6e6ec); border-radius:999px; overflow:hidden;
}
.msum-fill { display:block; height:100%; background: var(--series-1, #4f6ef7); }
/* Full and past it: the bar cannot say "more than full", so the colour does
   and the percentage beside it says how far. */
.msum-fill-over { background: var(--bad, #c2410c); }
.msum-pct { margin-left:.4rem; font-size:.85em; color: var(--muted, #6a6a74); }
@media (max-width: 760px) { .msum-bar { width:3.5rem; } .msum-bar-h, .msum-bar-c { width:6rem; } }

/* ── measure mode: the bill on the left, the item's rows on the right ──
   The layout the work actually has. A billing engineer goes down the bill
   for an hour; losing the list to open one item means finding your place
   again twenty times, so nothing is hidden and the item in hand is lit. */
.mm { padding: 0; overflow: hidden; }
.mm-top {
  display:flex; gap:1rem; align-items:flex-end; justify-content:space-between;
  flex-wrap:wrap; padding: .9rem 1.1rem; border-bottom:1px solid var(--line, #d8d8de);
}
.mm-top h1 { margin:0; font-size:1.35rem; }
.mm-top .sub { margin:.15rem 0 0; }
.mm-top-acts { display:flex; gap:.5rem; align-items:center; flex-wrap:wrap; }
.mm-open { margin:0; }

.mm-split { display:grid; grid-template-columns: 17rem minmax(0,1fr); align-items:start; }

/* The rail: every item, its progress, and which one is in hand. */
.mm-rail {
  border-right:1px solid var(--line, #d8d8de);
  padding:.75rem 0 1rem; max-height:44rem; overflow-y:auto;
  background: var(--surface-2, #f6f6f8);
}
.mm-rail-head {
  display:flex; justify-content:space-between; align-items:baseline;
  gap:.5rem; padding:0 .9rem .5rem;
}
.mm-eyebrow {
  font-size:.68rem; letter-spacing:.1em; text-transform:uppercase;
  color: var(--muted, #6a6a74);
}
.mm-keys { display:flex; gap:.25rem; }
.mm-keys kbd {
  font: inherit; font-size:.62rem; padding:0 .3rem;
  border:1px solid var(--line, #d8d8de); border-radius:3px; color: var(--muted, #6a6a74);
}
.mm-sect { margin-top:.5rem; }
.mm-sect-head {
  padding:.15rem .6rem .15rem .9rem; font-weight:600; font-size:.8rem;
  letter-spacing:.03em; color: var(--muted, #6a6a74);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.mm-item {
  display:grid; grid-template-columns: 2.6rem minmax(0,1fr); gap:.2rem .5rem;
  padding:.4rem .9rem; text-decoration:none; color:inherit; border-left:3px solid transparent;
}
.mm-item:hover { background: var(--surface-3, #e6e6ec); }
/* The item in hand. A tint and an edge rather than a box: the rail is a
   list being moved through, not a column of cards. */
.mm-item-on {
  background: var(--accent-wash, #eef1fe);
  border-left-color: var(--series-1, #4f6ef7);
}
.mm-item-on:hover { background: var(--accent-wash, #eef1fe); }
.mm-item-no { font-weight:600; font-size:.82rem; color: var(--series-1, #4f6ef7); }
.mm-item-d { font-size:.82rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.mm-item-prog { grid-column:2; display:flex; align-items:center; gap:.45rem; }
.mm-item-bar {
  flex:1; height:3px; background: var(--surface-3, #e6e6ec); border-radius:999px; overflow:hidden;
}
.mm-item-fill { display:block; height:100%; background: var(--series-1, #4f6ef7); }
.mm-item-fill-over { background: var(--bad, #c2410c); }
.mm-item-pct {
  font-size:.68rem; width:2.4rem; text-align:right;
  font-variant-numeric: tabular-nums; color: var(--muted, #6a6a74);
}

.mm-main { min-width:0; }
.mm-head {
  display:flex; gap:1.25rem; align-items:flex-end; justify-content:space-between;
  flex-wrap:wrap; padding:1rem 1.25rem .8rem; border-bottom:1px solid var(--line, #d8d8de);
}
.mm-head-id { display:flex; align-items:baseline; gap:.8rem; min-width:0; }
/* The item number, big: it is what the item is called on site, in the
   abstract and in the rail, and it is how a reader knows the right one is
   open without reading the description. */
.mm-head-no { font-size:1.9rem; font-weight:600; line-height:1; color: var(--series-1, #4f6ef7); }
.mm-head h2 {
  margin:0; font-size:1.15rem; line-height:1.2; min-width:0;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.mm-tpl { display:flex; flex-direction:column; gap:.2rem; margin:0; }
.mm-tpl label { font-size:.75rem; color: var(--muted, #6a6a74); }

/* The four figures read between rows, and the one bar that is all four. */
.mm-tot {
  display:grid; grid-template-columns: repeat(4, max-content) minmax(0,1fr);
  gap:1.4rem; align-items:end; padding:.9rem 1.25rem 1rem;
  border-bottom:1px solid var(--line, #d8d8de);
}
.mm-fig { display:flex; flex-direction:column; gap:.1rem; }
.mm-fig-k { font-size:.73rem; color: var(--muted, #6a6a74); }
.mm-fig-v { font-size:1.5rem; font-weight:600; line-height:1.05; font-variant-numeric: tabular-nums; }
.mm-fig-s { font-size:.72rem; color: var(--muted, #6a6a74); }
.mm-bar-wrap { display:flex; flex-direction:column; gap:.35rem; min-width:9rem; }
.mm-bar {
  position:relative; display:flex; height:.85rem;
  background: var(--surface-3, #e6e6ec); border-radius:2px; overflow:visible;
}
.mm-seg { display:block; height:100%; }
.mm-seg-earlier { background: var(--series-1, #4f6ef7); }
/* This bill, lighter: the same colour family, because it is the same
   quantity at a different stage — not a different kind of thing. */
.mm-seg-this { background: color-mix(in srgb, var(--series-1, #4f6ef7) 45%, transparent); }
/* The contract quantity. A mark at the end rather than the bar's edge,
   because the bar can be full while the measuring has gone past it. */
.mm-mark {
  position:absolute; right:0; top:-4px; bottom:-4px;
  border-left:2px solid var(--fg, #1b1b20);
}
.mm-legend {
  display:flex; justify-content:space-between; gap:.6rem;
  font-size:.7rem; color: var(--muted, #6a6a74); flex-wrap:wrap;
}
.mm-keys-legend { display:flex; gap:.7rem; align-items:center; }
.mm-swatch { display:inline-block; width:.55rem; height:.55rem; margin-right:.25rem; vertical-align:middle; }
.mm-sw-earlier { background: var(--series-1, #4f6ef7); }
.mm-sw-this { background: color-mix(in srgb, var(--series-1, #4f6ef7) 45%, transparent); }
.mm-empty { padding:2.5rem 1.25rem; max-width:44rem; }
.mm-empty h2 { margin:0 0 .4rem; }

/* One column on a narrow screen: the rail becomes a short scrolling strip
   above the work rather than squeezing it. */
@media (max-width: 880px) {
  .mm-split { grid-template-columns: minmax(0,1fr); }
  .mm-rail {
    border-right:none; border-bottom:1px solid var(--line, #d8d8de);
    max-height:14rem;
  }
  .mm-tot { grid-template-columns: repeat(2, minmax(0,1fr)); gap:.9rem; }
  .mm-bar-wrap { grid-column: 1 / -1; }
  .mm-head-no { font-size:1.4rem; }
}

/* ── the verse behind the front ─────────────────────────────
   उद्यमेन हि सिध्यन्ति कार्याणि न मनोरथैः — work is accomplished
   by effort, not by wishing.

   Fixed and oversized rather than placed in the flow. As one block at the
   top of `main` it showed on a short page and hid behind the first panel on
   every other one, which is why it looked absent everywhere but the sign-in
   doors. A fixed field is behind the page wherever the page has got to, and
   does not move as it scrolls — a watermark on the paper.

   Every row carries the same line; the odd ones are drawn back half a
   phrase and the whole field is turned a few degrees, so the columns do not
   stack and it reads as woven rather than as a table. */
.shlok {
  position: fixed;
  inset: -14vh -24vw;
  z-index: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  font-family: 'Noto Sans Devanagari', 'Nirmala UI', 'Mangal', serif;
  font-size: clamp(.95rem, 2.2vw, 1.45rem);
  font-weight: 500;
  line-height: 1.1;
  color: color-mix(in srgb, var(--ink) 6%, transparent);
  transform: rotate(-4deg);
  user-select: none;
  pointer-events: none;
  overflow: hidden;
  white-space: nowrap;
}
.shlok span { display: block; }
.shlok span:nth-child(odd) { margin-left: -8em; }
/* Everything the page itself draws sits above it. */
.shlok ~ * { position: relative; z-index: 1; }
/* A reader who asked for more contrast does not want a field of text behind
   the page. */
@media (prefers-contrast: more) { .shlok { display: none; } }

/* ── a sign-in page has a floor ──────────────────────────────────────────
   Without one the panel and the line under it huddle at the top with the
   screen empty beneath, which reads as a page that stopped loading. The
   page is given a minimum height and the last line sits on the floor of
   it. `main` grows, but a block child of a block cannot stretch to fill
   it, so the height is stated here rather than inherited. */
.login-page { display: flex; flex-direction: column; min-height: 66vh; }
.login-foot { margin-top: auto; padding-top: 1.4rem; }

/* The button and the way out of a forgotten password, on one row inside
   the box. */
.signin-go {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: .3rem;
}
.signin-go .primary { min-width: 11rem; }
.signin-forgot { font-size: .86rem; color: var(--ink-2); }
.signin-forgot:hover { color: var(--brand, var(--series-1)); }
@media (max-width: 560px) {
  .signin-go { flex-direction: column; align-items: stretch; gap: .7rem; }
  .signin-go .primary { width: 100%; }
  .signin-forgot { text-align: center; }
}

/* ── the merchant's door gets the same ruled ground ──────────────────────
   The ruling lives in market.css on `body`, so only the market-skinned
   pages had it: the customer's door, the directory, /join. The merchant's
   door wears the app's own skin and sat on plain paper with the shloka
   floating on nothing.

   Scoped to where the market skin is absent, so the two never draw it
   twice — stacked, the same gradients at 4% would come out near 8% and
   stop being a hint. */
:root:not([data-skin="market"]) .login-page {
  --rule:       color-mix(in srgb, var(--ink) 4%, transparent);
  --rule-major: color-mix(in srgb, var(--ink) 7%, transparent);
  background-image:
    linear-gradient(to right, var(--rule) 1px, transparent 1px),
    linear-gradient(to bottom, var(--rule) 1px, transparent 1px),
    linear-gradient(to right, var(--rule-major) 1px, transparent 1px),
    linear-gradient(to bottom, var(--rule-major) 1px, transparent 1px);
  background-size: 5mm 5mm, 5mm 5mm, 25mm 25mm, 25mm 25mm;
}
@media (prefers-contrast: more) {
  :root:not([data-skin="market"]) .login-page { background-image: none; }
}
/* And the panel on that door takes the customer door's card: the same
   narrow column drawn to the middle, rounded hard, letting the ruling
   through. Two sign-in pages in two different shapes reads as an accident
   rather than as two doors. No portrait on this one — a merchant signs in
   to an organisation, not to a face. */
:root:not([data-skin="market"]) .login-page > .panel {
  width: 100%;
  max-width: 25rem;
  margin-inline: auto;
  padding: clamp(1.6rem, 5vw, 2.4rem) clamp(1.3rem, 4vw, 1.9rem);
  border-radius: 2.2rem;
  background: color-mix(in srgb, var(--surface-1) 62%, transparent);
}
:root:not([data-skin="market"]) .login-page .page-head,
:root:not([data-skin="market"]) .login-page .login-foot,
:root:not([data-skin="market"]) .login-page .notice {
  max-width: 25rem;
  margin-inline: auto;
}
@media (max-width: 560px) {
  :root:not([data-skin="market"]) .login-page > .panel {
    border-radius: 1.5rem;
    max-width: none;
  }
}

/* The verse as a line that is read, not a field behind the form. Set in
   the Devanagari face and a little larger than an ordinary sub, because a
   sentence nobody can make out is not worth saying. */
.shlok-said {
  font-family: 'Noto Sans Devanagari', 'Nirmala UI', 'Mangal', serif;
  font-size: 1.12em;
  line-height: 1.55;
}


/* ── an owner's figures ──────────────────────────────────────────────────
   A panel answers one question, so its figures sit in one block and wrap
   to the room they have rather than to a fixed column count: four across
   on a desk, two on a tablet, one on a phone, with no breakpoint doing the
   deciding. */
.dash-figs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
  gap: 1.1rem 1.5rem;
  margin: 0 0 1.1rem;
}
.dash-fig { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.dash-fig-k { font-size: .78rem; color: var(--ink-2); }
.dash-fig-v {
  font-size: 1.5rem; font-weight: 650; line-height: 1.1;
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em;
}
/* The two the question is really about are larger. An owner asking whether
   they are making money should not have to find the answer among equals. */
.dash-fig-lead .dash-fig-v { font-size: 2rem; }
.dash-fig-s { font-size: .74rem; color: var(--muted); }

/* A change against yesterday. Colour carries the direction and so does the
   arrow, because colour alone is not a signal everybody can read. */
.dash-delta { font-size: .76rem; font-variant-numeric: tabular-nums; margin-top: .1rem; }
.dash-delta.up { color: var(--success-text, #15803d); }
.dash-delta.down { color: var(--neg, #b91c1c); }
.dash-delta.flat { color: var(--muted); }

/* Where the day came from, as one bar. */
.dash-split { display: flex; flex-direction: column; gap: .5rem; }
.dash-split-bar {
  display: flex; height: .9rem; border-radius: 3px; overflow: hidden;
  background: var(--track, #eee);
}
.dash-part { display: block; height: 100%; }
.dash-counter { background: var(--series-1); }
/* The same hue a step lighter: online is the same money through another
   door, not a different kind of thing. */
.dash-online { background: color-mix(in srgb, var(--series-1) 42%, transparent); }
.dash-split-keys {
  display: flex; gap: 1.4rem; flex-wrap: wrap;
  font-size: .8rem; color: var(--ink-2);
}
.dash-split-keys b { color: var(--ink); font-variant-numeric: tabular-nums; }
.dash-split-keys em { font-style: normal; color: var(--muted); font-size: .74rem; }
.dash-swatch {
  display: inline-block; width: .6rem; height: .6rem; border-radius: 2px;
  margin-right: .4rem; vertical-align: baseline;
}

/* A quiet day, and the questions this panel cannot answer. Both are notes
   rather than figures: neither is a number, and neither should look like
   one. */
.dash-quiet { margin: .2rem 0 0; font-size: .85rem; color: var(--ink-2); }
.dash-cannot {
  margin: 1.1rem 0 0; padding-top: .8rem; border-top: 1px solid var(--border);
  font-size: .76rem; color: var(--muted); line-height: 1.6;
}
.dash-cannot b { color: var(--ink-2); font-weight: 600; }

/* ── a top ten, and a month's line ──────────────────────────────────────
   Two lists side by side at a desk and stacked on a phone, because the
   point of the pair is the comparison and a comparison nobody can see on
   one screen is two lists. */
.dash-two { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem 2rem; }
@media (max-width: 54rem) { .dash-two { grid-template-columns: 1fr; } }
.dash-col-h {
  font: 600 0.78rem/1.3 var(--font, inherit);
  letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--ink-2); margin: 0 0 0.6rem;
  border-bottom: 1px solid var(--line); padding-bottom: 0.4rem;
}
.dash-col-h em { font-style: normal; text-transform: none; letter-spacing: 0;
  font-weight: 400; color: var(--ink-2); margin-left: 0.4rem; }

/* A rank is a grid so the numbers line up down the column however long a
   name is. The bar sits in its own track and the name is allowed to run
   out of room rather than pushing the figures about. */
.rank { list-style: none; margin: 0; padding: 0; }
.rank-row {
  display: grid; align-items: center;
  grid-template-columns: 1.4rem minmax(0, 1fr) 3.5rem auto minmax(0, auto);
  gap: 0 0.5rem; padding: 0.3rem 0;
  border-bottom: 1px solid var(--line-soft, var(--line));
}
.rank-row:last-child { border-bottom: 0; }
.rank-n { font: 600 0.72rem var(--font-mono, ui-monospace), Consolas, "Cascadia Mono", monospace; color: var(--ink-2); text-align: right; }
.rank-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.86rem; }
.rank-bar { height: 0.4rem; border-radius: 0.2rem; background: var(--line); overflow: hidden; }
.rank-bar i { display: block; height: 100%; background: var(--series-1); }
.rank-bar i.rank-bar-b { background: var(--series-2); }
.rank-v { font: 600 0.86rem/1 var(--font-mono, ui-monospace), Consolas, "Cascadia Mono", monospace; font-variant-numeric: tabular-nums;
  text-align: right; white-space: nowrap; }
.rank-v em { font: 400 0.72rem var(--font, inherit); color: var(--ink-2); font-style: normal; }
/* The other list's measure, so a row carries both: what moved and what
   it came to. Quiet, because the column it is in is the one being
   ordered by and this is the aside. */
.rank-x { font: 0.74rem/1 var(--font-mono, ui-monospace), Consolas, "Cascadia Mono", monospace;
  font-variant-numeric: tabular-nums; color: var(--ink-2);
  text-align: right; white-space: nowrap; padding-left: 0.5rem; }

/* The month's chart. The viewBox keeps its own ratio, so the line's
   thickness stays even and a dot stays round at every width. */
.chart { margin: 0 0 1rem; }
.chart-plot { display: block; width: 100%; aspect-ratio: 100 / 40; height: auto; overflow: visible; }
.chart-wash { fill: var(--series-1); opacity: 0.12; stroke: none; }
.chart-line { fill: none; stroke: var(--series-1); stroke-width: 0.7;
  stroke-linejoin: round; stroke-linecap: round; }
.chart-dot { fill: var(--series-1); stroke: var(--surface-1, #fff); stroke-width: 0.3; }
.chart-avg { stroke: var(--ink-2); stroke-width: 0.3; stroke-dasharray: 1.6 1.6; }
.chart-feet {
  display: flex; justify-content: space-between; gap: 0.5rem;
  font: 0.72rem var(--font, inherit); color: var(--ink-2); margin-top: 0.35rem;
}
.chart-peak { font-variant-numeric: tabular-nums; }
.dash-figs-tight { margin-top: 0; }

/* ── the till, and the stockroom ────────────────────────────────────────
   Two role panels. Both are lists of things to act on rather than figures
   to admire, so the row is wide, the name leads, and the one thing that
   needs doing sits on the right where the eye lands last. */
.till-who { font: 0.78rem var(--font, inherit); color: var(--ink-2); margin: 0 0 0.8rem; }
.till-ways { list-style: none; margin: 0.4rem 0 0.8rem; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: 0.4rem 1.5rem; }
.till-ways li { display: grid; grid-template-columns: 4.5rem auto 1fr; align-items: baseline;
  gap: 0 0.6rem; padding: 0.3rem 0; border-bottom: 1px solid var(--line); }
.till-ways b { font-weight: 600; font-size: 0.86rem; }
.till-amt { font: 600 0.9rem/1 var(--font-mono, ui-monospace), Consolas, "Cascadia Mono", monospace;
  font-variant-numeric: tabular-nums; }
.till-ways em { font-style: normal; font-size: 0.74rem; color: var(--ink-2); }

.stock-part { margin: 0 0 1.2rem; }
.stock-part:last-of-type { margin-bottom: 0.6rem; }
.stock-list { list-style: none; margin: 0; padding: 0; }
.stock-list li {
  display: grid; align-items: baseline;
  grid-template-columns: minmax(8rem, 1fr) minmax(0, 1.4fr) auto;
  grid-template-areas: "name mid amt" "flag flag flag";
  gap: 0.1rem 0.8rem; padding: 0.4rem 0;
  border-bottom: 1px solid var(--line);
}
.stock-list li:last-child { border-bottom: 0; }
.stock-list b { grid-area: name; font-weight: 600; font-size: 0.88rem; }
.stock-mid { grid-area: mid; font-size: 0.78rem; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stock-amt { grid-area: amt; text-align: right; white-space: nowrap;
  font: 600 0.86rem/1 var(--font-mono, ui-monospace), Consolas, "Cascadia Mono", monospace;
  font-variant-numeric: tabular-nums; }
.stock-list em { grid-area: flag; font-style: normal; font-size: 0.74rem; color: var(--ink-2); }
/* The thing that is actually wrong. Not by colour alone, and not by
   coloured text: --neg measures 3.62 against the dark ground, under AA at
   these sizes. Every one of these rows already says what is wrong in
   words, so the colour becomes a rule down the side of the row — a cue
   that carries no text and so has no contrast to fail. */
.stock-bad { font-weight: 600; }
.stock-flag { color: var(--ink-2); }
.stock-list li:has(.stock-bad), .stock-list li:has(.stock-flag) {
  border-left: 2px solid var(--neg); padding-left: .55rem;
}
@media (max-width: 40rem) {
  .stock-list li { grid-template-columns: 1fr auto; grid-template-areas: "name amt" "mid mid" "flag flag"; }
  .till-ways li { grid-template-columns: 4.5rem 1fr; }
}

/* ── a customer's points, by place ──────────────────────────────────────
   One row per shop, and the row's job is the last part of it: whether
   these points can be spent. The name leads because that is what somebody
   scans for; the verdict sits on its own line so it has room to be a
   sentence rather than a code. */
.pts-list { list-style: none; margin: .4rem 0 0; padding: 0; }
.pts-list li {
  display: grid; align-items: baseline;
  grid-template-columns: minmax(8rem, 1fr) 7rem auto;
  grid-template-areas: "name n worth" "say say say";
  gap: .15rem .8rem; padding: .45rem 0;
  border-bottom: 1px solid var(--line);
}
.pts-list li:last-child { border-bottom: 0; }
.pts-list b { grid-area: name; font-weight: 600; font-size: .9rem; }
.pts-n {
  grid-area: n; text-align: right; white-space: nowrap;
  font: 600 .88rem/1 var(--font-mono, ui-monospace), Consolas, "Cascadia Mono", monospace;
  font-variant-numeric: tabular-nums;
}
.pts-n em { font: 400 .72rem var(--font, inherit); font-style: normal; color: var(--ink-2); }
.pts-worth {
  grid-area: worth; text-align: right; white-space: nowrap;
  font: 600 .88rem/1 var(--font-mono, ui-monospace), Consolas, "Cascadia Mono", monospace;
  font-variant-numeric: tabular-nums; color: var(--brand);
}
.pts-list em { grid-area: say; font-style: normal; font-size: .76rem; color: var(--ink-2); }
/* Points that are real and cannot be spent yet. Marked down the side
   rather than in colour on the words: the sentence already says it, and
   coloured small text is what fails a contrast check. */
.pts-list li:has(.pts-flag) { border-left: 2px solid var(--brand-soft); padding-left: .55rem; }
.pts-flag { color: var(--ink-2); }
@media (max-width: 34rem) {
  .pts-list li { grid-template-columns: 1fr auto; grid-template-areas: "name worth" "n n" "say say"; }
  .pts-n { text-align: left; }
}

/* ── a customer's own page ──────────────────────────────────────────────
   An avatar, who they are, and what the month has cost — one band across
   the top, stacking to one column on a phone. The figures sit right
   because that is the side an eye lands on last, and they are the thing
   worth landing on. */
.mehead {
  display: grid; align-items: center; gap: .9rem 1.2rem;
  grid-template-columns: auto minmax(0, 1fr) auto;
  padding: 0 0 1rem; margin-bottom: 1rem;
  border-bottom: 1px solid var(--line);
}
.avatar-oval {
  display: grid; place-items: center;
  width: 3.4rem; height: 4rem;              /* an oval, not a circle */
  border-radius: 50% / 42%;
  background: var(--brand); color: var(--brand-ink);
  font: 600 1.15rem/1 var(--font-heading, inherit);
  letter-spacing: .02em; user-select: none;
}
.mehead-who h1 { margin: 0; }
.mehead-who .sub { margin: .2rem 0 0; font-size: .82rem; color: var(--ink-2); }
.mehead-figs { display: flex; flex-wrap: wrap; gap: .4rem 1.6rem; justify-content: flex-end; }
.mehead-figs .dash-fig { min-width: 8.5rem; }
@media (max-width: 52rem) {
  .mehead { grid-template-columns: auto minmax(0, 1fr); }
  .mehead-figs { grid-column: 1 / -1; justify-content: flex-start; }
}

/* The month, two ways: the pattern beside the pacing. */
.month-two { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 1.4rem 2rem; align-items: start; }
@media (max-width: 54rem) { .month-two { grid-template-columns: 1fr; } }

/* A real calendar grid: seven columns, Monday first, one square a day. */
.heat-dows, .heat-grid {
  display: grid; grid-template-columns: repeat(7, 1.35rem); gap: .2rem;
}
.heat-dows { margin-bottom: .25rem; }
.heat-dows span { font: .62rem/1 var(--font, inherit); color: var(--muted); text-align: center; }
.heat-pad { visibility: hidden; }
.heat-cell {
  aspect-ratio: 1; border-radius: .2rem;
  background: var(--line);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 7%, transparent);
}
/* There is no number in a square, and that is a decision rather than an
   omission. It was there for orientation and could not be made legible:
   the ink has to switch somewhere along a four-step ramp, and whichever
   step it switched at, the two shades either side failed AA for small
   text — 3.52 and 3.78 measured in light, 3.86 in dark. A figure nobody
   can read is not orientation.

   What orientates instead: the weekday columns, today's outline, the
   month's dates under the chart beside it, and `title` on every square
   carrying the date and what it cost. Which is how a heat grid is read
   anyway, and is also 31 fewer marks on the page. */
/* Four shades of the brand over the month's own dearest day. Five steps
   including the empty square is as much as an eye reads off a grid. */
/* Four steps of whatever the month's tone is. No text sits on these, so
   the only thing they owe is being told apart from each other and from
   the ground. */
.heat-1 { background: color-mix(in srgb, var(--tone, var(--heat)) 20%, var(--plane)); }
.heat-2 { background: color-mix(in srgb, var(--tone, var(--heat)) 44%, var(--plane)); }
.heat-3 { background: color-mix(in srgb, var(--tone, var(--heat)) 70%, var(--plane)); }
.heat-4 { background: var(--tone, var(--heat)); }
/* Money that came back: a bill paid earlier and cancelled. Hatched rather
   than coloured, so it is not mistaken for a heavy spending day. */
.heat-back {
  background: repeating-linear-gradient(45deg,
    var(--line) 0 3px, var(--plane) 3px 6px);
}
.heat-ahead { background: transparent; box-shadow: inset 0 0 0 1px var(--line); }
.heat-today { outline: 2px solid var(--ink-2); outline-offset: 1px; }
.heat-key { display: flex; align-items: center; gap: .22rem; margin-top: .45rem;
  font: .66rem var(--font, inherit); color: var(--ink-2); }
.heat-key .heat-cell { width: .8rem; }
.heat-key span:first-child { margin-right: .2rem; }
.heat-key span:last-child { margin-left: .2rem; }

/* The running total against the limit. */
.run-plot { display: block; width: 100%; aspect-ratio: 100 / 34; height: auto; overflow: visible; }
/* One tone across both drawings of the month.

   The grid and the line are one chart in two forms, and they were two
   colours: the grid plum because that is the spending colour, the line
   green or red because that is the verdict. A month drawn green beside a
   month drawn plum is a page arguing with itself.

   So the tone sits on the pair. The **shade** still says how dear a day
   was; the **hue** says how the month is going, and both the squares and
   the line take it. `plain` is a month with no limit to be judged
   against — then there is no verdict and the pair wears the spending
   colour, which is the honest answer rather than a borrowed green. */
.month-two      { --tone: var(--heat); }
.tone-good      { --tone: var(--status-good); }
.tone-neutral   { --tone: var(--status-neutral); }
.tone-warning   { --tone: var(--tone-warning, var(--status-warning)); }
.tone-critical  { --tone: var(--status-critical); }
.run-wash { fill: var(--tone); opacity: .14; stroke: none; }
.run-line { fill: none; stroke: var(--tone); stroke-width: .7; stroke-linejoin: round; stroke-linecap: round; }
.run-dot { fill: var(--tone); stroke: var(--surface-1); stroke-width: .3; }
.heat-basis { margin-left: auto; color: var(--ink-2); }
/* The limit, drawn as the month's own pace. Dashed, because it is a
   intention rather than a measurement. */
.run-budget { fill: none; stroke: var(--ink-2); stroke-width: .35; stroke-dasharray: 1.8 1.4; }
.run-feet { display: flex; justify-content: space-between; gap: .5rem; margin-top: .35rem;
  font: .7rem var(--font, inherit); color: var(--ink-2); }
.run-key { display: inline-flex; align-items: center; gap: .3rem; }
.run-swatch { width: .9rem; height: 0; border-top: 2px dashed var(--ink-2); }

/* Setting the limit: one line, label and field and button together. */
.limit-set {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  margin-top: 1.2rem; padding-top: .9rem; border-top: 1px solid var(--line);
}
.limit-set label { font-size: .84rem; font-weight: 600; }
.limit-cur { font-size: .82rem; color: var(--ink-2); }
.limit-set input { width: 8rem; font-variant-numeric: tabular-nums; }
.limit-set .hint { flex: 1 1 18rem; font-size: .72rem; color: var(--ink-2); }

/* ── the row of sections ────────────────────────────────────────────────
   Buttons, not links: they fetch a fragment and swap it in, and nothing
   navigates. Wrapping on a phone to two or three rows. */
.me-icons {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr));
  gap: .5rem; margin: 1.4rem 0 1rem;
}
.me-icon {
  display: grid; justify-items: center; gap: .4rem;
  padding: .85rem .4rem; border: 1px solid var(--line); border-radius: .5rem;
  background: var(--surface-1); color: var(--ink); cursor: pointer;
  font: 500 .76rem/1.2 var(--font, inherit); text-align: center;
}
.me-icon svg { width: 1.45rem; height: 1.45rem; fill: none;
  stroke: var(--brand); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.me-icon:hover { border-color: var(--brand); background: var(--brand-soft); }
.me-icon:focus-visible { outline: 2px solid var(--brand-deep); outline-offset: 2px; }
/* htmx puts this on the element it is fetching for, so the row says which
   one is loading without a spinner library. */
.me-icon.htmx-request { border-color: var(--brand); opacity: .6; }

.me-detail { scroll-margin-top: 4.5rem; }
.me-detail > .empty { padding: 1.6rem 0; }

/* The derived favourites list. */
.fav-list { list-style: none; margin: .6rem 0 0; padding: 0; }
.fav-list li {
  display: grid; align-items: center; gap: .15rem .8rem;
  grid-template-columns: minmax(8rem, 1fr) 6rem minmax(4rem, 8rem) auto;
  grid-template-areas: "name what bar amt" "note note note note";
  padding: .45rem 0; border-bottom: 1px solid var(--line);
}
.fav-list li:last-child { border-bottom: 0; }
.fav-list b { grid-area: name; font-weight: 600; font-size: .9rem; }
.fav-what { grid-area: what; font-size: .74rem; color: var(--ink-2); text-transform: capitalize; }
.fav-bar { grid-area: bar; height: .4rem; border-radius: .2rem; background: var(--line); overflow: hidden; }
.fav-bar i { display: block; height: 100%; background: var(--heat); }
.fav-amt { grid-area: amt; text-align: right; white-space: nowrap;
  font: 600 .88rem/1 var(--font-mono, ui-monospace), Consolas, "Cascadia Mono", monospace; font-variant-numeric: tabular-nums; }
.fav-list em { grid-area: note; font-style: normal; font-size: .74rem; color: var(--ink-2); }
@media (max-width: 40rem) {
  .fav-list li { grid-template-columns: 1fr auto; grid-template-areas: "name amt" "what bar" "note note"; }
}

.addr-list { list-style: none; margin: .5rem 0 1rem; padding: 0; }
.addr-list li { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem;
  padding: .45rem 0; border-bottom: 1px solid var(--line); }
.addr-list li:last-child { border-bottom: 0; }

/* ── figures are read as data ────────────────────────────────────────────
   Everything here already carried `tabular-nums` so columns of digits line
   up; what it did not carry was a face that makes a figure look like a
   figure. One rule rather than fourteen, because the next class that holds
   a number should join this list instead of growing its own font stack.

   Deliberately not whole tables: `.due-table` and `.facts dd` hold words
   as well, and a mono paragraph is harder to read, not easier. Only the
   cells and spans that are nothing but a number. */
.num,
td.num,
.item-no,
.card .no,
.tile .value,
.dash-fig-v,
.due-figure,
.due-date,
.loan-figure,
.loan-ref,
.spend-amt,
.method-detail,
.due-table td.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* ── where the money went, and what somebody entered themselves ─────────
   A kind of place, then the shops in it. The kind is the division somebody
   thinks in; the shop is the one they recognise, so both are shown and the
   shop sits underneath. */
.going { list-style: none; margin: .7rem 0 0; padding: 0; }
.going > li {
  display: grid; align-items: center; gap: .15rem .8rem;
  grid-template-columns: minmax(7rem, 1fr) minmax(4rem, 10rem) auto;
  grid-template-areas: "name bar amt" "note note note" "in in in";
  padding: .6rem 0; border-bottom: 1px solid var(--line);
}
.going > li:last-child { border-bottom: 0; }
.going > li > b { grid-area: name; font-weight: 600; font-size: .92rem; }
.going-bar { grid-area: bar; height: .45rem; border-radius: .22rem; background: var(--line); overflow: hidden; }
.going-bar i { display: block; height: 100%; background: var(--heat); }
.going-amt {
  grid-area: amt; text-align: right; white-space: nowrap;
  font: 600 .92rem/1 var(--font-mono); font-variant-numeric: tabular-nums;
}
.going > li > em { grid-area: note; font-style: normal; font-size: .74rem; color: var(--ink-2); }

/* The shops within a kind. Indented, smaller, and without a bar of their
   own: two sets of bars at two scales on one list is a list nobody can
   read across. */
.going-places { grid-area: in; list-style: none; margin: .3rem 0 0 .9rem; padding: 0 0 0 .7rem;
  border-left: 2px solid var(--line); }
.going-places li {
  display: grid; align-items: baseline; gap: 0 .6rem;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "who amt" "note note";
  padding: .22rem 0;
}
.going-places a { grid-area: who; font-size: .84rem; }
.going-places > li > span { grid-area: amt; text-align: right; white-space: nowrap;
  font: .82rem/1 var(--font-mono); font-variant-numeric: tabular-nums; color: var(--ink-2); }
.going-places em { grid-area: note; font-style: normal; font-size: .7rem; color: var(--muted); }

.ent-list { list-style: none; margin: .6rem 0 0; padding: 0; }
.ent-list li {
  display: grid; align-items: baseline; gap: .15rem .8rem;
  grid-template-columns: minmax(6rem, 1fr) 7rem auto auto;
  padding: .4rem 0; border-bottom: 1px solid var(--line);
}
.ent-list li:last-child { border-bottom: 0; }
.ent-list b { font-weight: 600; font-size: .88rem; }
.ent-when { font-size: .76rem; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.ent-amt { text-align: right; white-space: nowrap;
  font: 600 .88rem/1 var(--font-mono); font-variant-numeric: tabular-nums; }
.field-short { max-width: 12rem; }

/* What may come back, on a bill or a delivery. One row per line: what it is
   on the left, then either a box to type a quantity into or, in its place,
   the reason it cannot come back — a refusal in the slot where the control
   would have been reads as an answer rather than as something missing. */
.back-list { list-style: none; margin: .6rem 0 0; padding: 0; }
.back-list li {
  display: grid; align-items: baseline; gap: .15rem .7rem;
  grid-template-columns: minmax(8rem, 1fr) auto auto;
  padding: .4rem 0; border-bottom: 1px solid var(--line);
}
.back-list li:last-child { border-bottom: 0; }
.back-what { font-size: .88rem; font-weight: 600; }
.back-what small { display: block; font-weight: 400; font-size: .76rem; color: var(--ink-2); }
.back-list input { width: 5rem; text-align: right; font-family: var(--font-mono); }
.back-max { font-size: .76rem; color: var(--ink-2); white-space: nowrap; }
.back-no { font-size: .76rem; color: var(--ink-2); grid-column: 2 / -1; font-style: italic; }
.back-list .num { text-align: right; white-space: nowrap;
  font: 600 .88rem/1 var(--font-mono); font-variant-numeric: tabular-nums; }
@media (max-width: 40rem) {
  .going > li { grid-template-columns: 1fr auto; grid-template-areas: "name amt" "bar bar" "note note" "in in"; }
  .ent-list li { grid-template-columns: 1fr auto; }
  .back-list li { grid-template-columns: 1fr auto; }
}
