/*
  The design system, lifted verbatim from mockups/debit-card-purchase-form.html - the layout that was
  built from the .docx and approved as the shape to aim at. Keep it a lift rather than a
  reinterpretation: when the two disagree, the mockup is right and this file is the bug.

  Two things follow from that, and neither is arbitrary:

  - There is no component library here. GSBC.ImpactKids uses MudBlazor; this app does not, because the
    mockup is a hand-rolled system whose tokens MudBlazor would have to be overridden against on every
    surface. A form that has to look like the paper document it replaces is the wrong place to fight a
    theme.

  - Everything is a custom property on :root, in all three theme states. The viewer's theme has three:
    an explicit choice stamps data-theme on the root element, and the default "system" setting stamps
    nothing, leaving prefers-color-scheme as the only signal. A colour whose only definition sits
    inside a media query is a colour that disappears in one of the three.

  The print block at the bottom is the on-screen approximation of the printed form. It is NOT the PDF:
  slice 8 renders that server-side with QuestPDF from the submission aggregate, deliberately, so the
  screen layout and the printed layout are free to diverge.
*/

:root{
  /*
    Not in the mockup, and it is not decoration: color-scheme is what the browser paints its OWN
    surfaces from - the date picker panel, the select dropdown, scrollbars, the caret. Left unset they
    stay light, so a dark form opened a date picker that was a white sheet. It has to be redeclared in
    each of the three theme blocks for the same reason every colour does.
  */
  color-scheme:light;
  --ground:#f7f6f3;
  --surface:#ffffff;
  --surface-sunk:#f1efea;
  --ink:#1b1c20;
  --ink-2:#4a4d55;
  --ink-3:#7c7f88;
  --line:#dfdcd4;
  --line-strong:#c6c2b8;
  --brand:#33415c;
  --brand-2:#4a5c80;
  --brand-wash:#eaedf4;
  --ok:#2f6e4e;
  --ok-wash:#e6f0e9;
  --warn:#8a5a17;
  --warn-wash:#f6eeda;
  --bad:#9d3427;
  --bad-wash:#f7e7e3;
  --focus:#8aa0cc;
  --radius:4px;
  --shadow:0 1px 2px rgba(27,28,32,.06), 0 8px 24px -16px rgba(27,28,32,.28);
}
/*
  The dark palette is written out twice on purpose, and plain CSS gives no way round it: an at-rule
  block and a plain selector cannot share one declaration list. The two blocks below MUST stay
  byte-identical - the first answers a viewer on the system default, the second answers an explicit
  choice, and a value that only reaches one of them produces a page that is dark until somebody
  touches the theme toggle.

  The mockup had already drifted here: --line-strong was declared twice inside the media block,
  #464a53 then #4a4e57, and only the second matched :root[data-theme="dark"]. The dead one is deleted.
*/
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    color-scheme:dark;
    --ground:#16171a;
    --surface:#1e2024;
    --surface-sunk:#25272c;
    --ink:#eceae5;
    --ink-2:#b3b5bc;
    --ink-3:#83868f;
    --line:#33363d;
    --brand:#a9bce2;
    --brand-2:#8fa4cd;
    --brand-wash:#232833;
    --ok:#7fc39c;
    --ok-wash:#1e2a24;
    --warn:#d7a75a;
    --warn-wash:#2c2519;
    --bad:#e0897c;
    --bad-wash:#2e1f1d;
    --line-strong:#4a4e57;
    --focus:#7d93c0;
    --shadow:0 1px 2px rgba(0,0,0,.4), 0 8px 24px -16px rgba(0,0,0,.8);
  }
}
:root[data-theme="dark"]{
  color-scheme:dark;
  --ground:#16171a;
  --surface:#1e2024;
  --surface-sunk:#25272c;
  --ink:#eceae5;
  --ink-2:#b3b5bc;
  --ink-3:#83868f;
  --line:#33363d;
  --line-strong:#4a4e57;
  --brand:#a9bce2;
  --brand-2:#8fa4cd;
  --brand-wash:#232833;
  --ok:#7fc39c;
  --ok-wash:#1e2a24;
  --warn:#d7a75a;
  --warn-wash:#2c2519;
  --bad:#e0897c;
  --bad-wash:#2e1f1d;
  --focus:#7d93c0;
  --shadow:0 1px 2px rgba(0,0,0,.4), 0 8px 24px -16px rgba(0,0,0,.8);
}

*{box-sizing:border-box}
/*
  Not in the mockup. iOS Safari inflates the font of any block it decides is a "column of text" when
  the phone is turned to landscape, so a form reflows to a different type size on rotation and the
  labels stop lining up with the fields. 100% keeps the sizes this file asks for.
*/
html{-webkit-text-size-adjust:100%}
body{
  margin:0;
  background:var(--ground);
  color:var(--ink);
  font-family:"Public Sans","Helvetica Neue",Arial,sans-serif;
  font-size:15px;
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
}
a{color:var(--brand)}
:focus-visible{outline:2px solid var(--focus);outline-offset:2px;border-radius:2px}

/* ---------- shell ---------- */
.topbar{
  position:sticky;top:0;z-index:40;
  display:flex;align-items:center;gap:18px;
  padding:14px 28px;
  background:var(--surface);
  border-bottom:1px solid var(--line);
}
.mark{
  width:34px;height:34px;flex:none;border-radius:2px;
  background:var(--brand);color:var(--surface);
  display:grid;place-items:center;
  font-family:"Newsreader",Georgia,serif;font-size:17px;font-weight:600;letter-spacing:.02em;
}
:root[data-theme="dark"] .mark, :root:not([data-theme="light"]) .mark{color:var(--ground)}
@media (prefers-color-scheme: light){:root:not([data-theme="dark"]) .mark{color:#fff}}
.topbar h1{
  margin:0;font-family:"Newsreader",Georgia,serif;
  font-size:19px;font-weight:600;letter-spacing:-.01em;line-height:1.15;
}
/*
  Not in the mockup, which has no router. App.razor renders <FocusOnNavigate Selector="h1">, so every
  navigation - including the first paint - moves focus to this heading and the :focus-visible rule
  above draws a ring around the page title. It reads as a stray selection box on a title nobody
  clicked, and it is the first thing on screen on a phone.

  Nothing is lost by hiding it: FocusOnNavigate gives the element tabindex="-1", so it is reachable
  only programmatically and never by Tab. The focus move still happens and is still announced.
*/
.topbar h1:focus,.topbar h1:focus-visible{outline:none}
.topbar .titles{min-width:0}
.topbar .sub{font-size:12px;color:var(--ink-3);letter-spacing:.04em;text-transform:uppercase}
.topbar .spacer{flex:1}
/*
  The mockup's topbar buttons are text ("Theme", "Print / PDF"), which works when there are three of
  them at desk width. There is one here and it has to survive a 390px topbar next to a wrapping page
  title, so it is a square glyph button. The label it does not show is on title and aria-label.
*/
.iconbtn{
  flex:none;display:grid;place-items:center;
  width:34px;height:34px;padding:0;
  border:1px solid var(--line);border-radius:var(--radius);
  background:var(--surface);color:var(--ink-2);cursor:pointer;
}
.iconbtn:hover{border-color:var(--brand-2);background:var(--brand-wash);color:var(--brand)}
.iconbtn svg{display:block}
.draft{
  font-size:12px;color:var(--ink-3);
  font-family:"IBM Plex Mono",monospace;
}

.layout{
  display:grid;
  grid-template-columns:238px minmax(0,1fr);
  gap:0;
  align-items:start;
  max-width:1320px;margin:0 auto;
}
/*
  Not in the mockup, which only ever has the one page and always draws the rail.

  .layout is two columns, and the FIRST is the 238px rail. A page that renders <main> without a
  nav.rail before it therefore puts main in the rail's slot and every card is squeezed to 238px, with
  the rest of the window left empty. There is no error and the styling still looks correct, which is
  what makes it hard to spot - it reads as "the design is just narrow".

  Any page with no rail - the landing page, not-found - uses .layout.norail.
*/
.layout.norail{grid-template-columns:minmax(0,1fr)}

/* ---------- breadcrumbs ---------- */
/*
  Sits between the topbar and .layout, full width, on every page except the landing page.

  NOT sticky, deliberately. The topbar already is, and on a form the rail is too at mobile widths;
  a third pinned strip would leave a phone reading a form through a letterbox. The way home is
  needed once, at the moment somebody decides to leave, and scrolling up is what people already do
  to leave a page.

  Max-width and centring match .layout so the trail lines up with the rail beneath it rather than
  floating at the window edge on a wide screen.
*/
.crumbs{
  max-width:1320px;margin:0 auto;
  padding:12px 28px 0;
}
.crumbs ol{
  display:flex;flex-wrap:wrap;align-items:center;gap:7px;
  list-style:none;margin:0;padding:0;
  font-size:12.5px;color:var(--ink-3);
}
/*
  The separator is a pseudo-element, so it is decoration a screen reader never announces - the trail
  is read as its links plus the aria-current item, not as "Finance Forms slash Debit Card Purchase".
*/
.crumbs li + li::before{content:"/";margin-right:7px;color:var(--line-strong)}
.crumbs a{
  color:var(--ink-2);text-decoration:none;
  border-bottom:1px solid transparent;
}
.crumbs a:hover{color:var(--brand-2);border-bottom-color:var(--brand-2)}
.crumbs a:focus-visible{outline:2px solid var(--focus);outline-offset:2px;border-radius:2px}
.crumbs [aria-current="page"]{color:var(--ink-3)}

/* ---------- rail ---------- */
.rail{
  position:sticky;top:63px;
  padding:26px 18px 40px 28px;
  align-self:start;
  max-height:calc(100vh - 63px);
  overflow-y:auto;
}
.rail h2{
  font-size:11px;text-transform:uppercase;letter-spacing:.11em;
  color:var(--ink-3);margin:0 0 12px;font-weight:600;
}
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:1px}
.rail a{
  display:grid;grid-template-columns:20px 1fr;gap:9px;align-items:baseline;
  padding:7px 8px;border-radius:var(--radius);
  color:var(--ink-2);text-decoration:none;font-size:13.5px;line-height:1.35;
}
.rail a:hover{background:var(--surface-sunk);color:var(--ink)}
.rail a.active{background:var(--brand-wash);color:var(--ink);font-weight:600}
.rail .n{font-family:"IBM Plex Mono",monospace;font-size:11.5px;color:var(--ink-3)}
.rail a.active .n{color:var(--brand)}
.rail .dot{
  display:inline-block;width:6px;height:6px;border-radius:50%;
  background:var(--line-strong);margin-left:6px;vertical-align:middle;
}
.rail .dot.done{background:var(--ok)}
.rail .dot.err{background:var(--bad)}

/* ---------- form column ---------- */
main{padding:26px 28px 160px;min-width:0}
.lede{
  max-width:64ch;margin:0 0 26px;
  font-family:"Newsreader",Georgia,serif;font-size:17px;line-height:1.55;color:var(--ink-2);
}
.lede strong{color:var(--ink);font-weight:600}

section.card{
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--radius);
  margin-bottom:18px;
  box-shadow:var(--shadow);
  scroll-margin-top:78px;
}
.card > header{
  display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;
  padding:15px 20px;border-bottom:1px solid var(--line);
  background:linear-gradient(to bottom, var(--surface), var(--surface));
}
.card > header .num{
  font-family:"IBM Plex Mono",monospace;font-size:12px;font-weight:500;
  color:var(--brand);letter-spacing:.06em;
}
.card > header h3{
  margin:0;font-size:15.5px;font-weight:600;letter-spacing:-.005em;
}
.card > header .hint{font-size:13px;color:var(--ink-3);margin-left:auto}
.card .body{padding:20px}

.grid{display:grid;gap:16px 18px;grid-template-columns:repeat(12,1fr)}
.f{display:flex;flex-direction:column;gap:6px;min-width:0}
.c3{grid-column:span 3}.c4{grid-column:span 4}.c5{grid-column:span 5}
.c6{grid-column:span 6}.c7{grid-column:span 7}.c8{grid-column:span 8}.c12{grid-column:span 12}
@media (max-width:900px){
  .c3,.c4,.c5,.c6,.c7,.c8{grid-column:span 12}
}

label,.lbl{
  font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--ink-2);
}
.req{color:var(--bad);font-weight:600}
.help{font-size:12px;color:var(--ink-3);line-height:1.45}

input[type=text],input[type=date],input[type=time],input[type=number],input[type=email],select,textarea{
  width:100%;padding:8px 10px;
  font:inherit;font-size:14px;color:var(--ink);
  background:var(--surface);
  border:1px solid var(--line-strong);border-radius:var(--radius);
}
input[type=number],.mono{font-family:"IBM Plex Mono",monospace;font-variant-numeric:tabular-nums}
textarea{resize:vertical;min-height:78px;line-height:1.5}
input:focus,select:focus,textarea:focus{outline:2px solid var(--focus);outline-offset:0;border-color:var(--brand-2)}
input::placeholder,textarea::placeholder{color:var(--ink-3)}
.money{position:relative}
.money input{padding-left:22px}
.money::before{
  content:"$";position:absolute;left:9px;top:50%;transform:translateY(-50%);
  color:var(--ink-3);font-family:"IBM Plex Mono",monospace;font-size:13px;pointer-events:none;
}

.chips{display:flex;flex-wrap:wrap;gap:7px}
.chip{
  display:inline-flex;align-items:center;gap:6px;
  padding:6px 11px;border:1px solid var(--line-strong);border-radius:99px;
  font-size:13px;color:var(--ink-2);cursor:pointer;background:var(--surface);
  user-select:none;
}
.chip:hover{border-color:var(--brand-2);color:var(--ink)}
.chip input{position:absolute;opacity:0;width:0;height:0}
.chip:has(input:checked){background:var(--brand-wash);border-color:var(--brand-2);color:var(--ink);font-weight:600}
.chip:has(input:focus-visible){outline:2px solid var(--focus);outline-offset:2px}

.yn{display:inline-flex;border:1px solid var(--line-strong);border-radius:99px;overflow:hidden;flex:none}
.yn label{
  padding:4px 14px;font-size:12.5px;font-weight:600;cursor:pointer;color:var(--ink-3);
  background:var(--surface);letter-spacing:.02em;
}
.yn label + label{border-left:1px solid var(--line-strong)}
.yn input{position:absolute;opacity:0;width:0;height:0}
.yn label:has(input:checked){background:var(--brand);color:#fff}
:root[data-theme="dark"] .yn label:has(input:checked),
:root:not([data-theme="light"]) .yn label:has(input:checked){color:var(--ground)}
@media (prefers-color-scheme: light){:root:not([data-theme="dark"]) .yn label:has(input:checked){color:#fff}}
.yn label:has(input[value="yes"]:checked){background:var(--warn);border-color:var(--warn)}

.checkline{
  display:grid;grid-template-columns:18px 1fr;gap:11px;align-items:start;
  padding:11px 12px;border:1px solid var(--line);border-radius:var(--radius);
  background:var(--surface-sunk);font-size:13.5px;line-height:1.5;color:var(--ink-2);
  cursor:pointer;
}
.checkline:has(input:checked){background:var(--ok-wash);border-color:color-mix(in srgb, var(--ok) 35%, transparent);color:var(--ink)}
.checkline input{margin-top:3px;accent-color:var(--brand);width:16px;height:16px}
.checkline b{font-weight:700;color:var(--ink)}
.stack{display:flex;flex-direction:column;gap:9px}

/* question rows in section 4 */
.q{
  display:flex;gap:14px;align-items:flex-start;justify-content:space-between;
  padding:13px 0;border-top:1px solid var(--line);
}
.q:first-child{border-top:0;padding-top:0}
.q p{margin:0;font-size:13.5px;line-height:1.5;color:var(--ink-2);max-width:66ch}
.q .qlabel{font-weight:600;color:var(--ink);display:block;font-size:14px;margin-bottom:2px}
.reveal{
  margin:2px 0 4px;padding:14px;border-left:2px solid var(--warn);
  background:var(--warn-wash);border-radius:0 var(--radius) var(--radius) 0;
}
.reveal .lbl{color:var(--warn)}
[hidden]{display:none !important}

/* ---------- tables ---------- */
.tablewrap{overflow-x:auto;border:1px solid var(--line);border-radius:var(--radius)}
table{border-collapse:collapse;width:100%;min-width:840px;font-size:13.5px}
th{
  text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.07em;
  color:var(--ink-3);font-weight:600;padding:9px 10px;background:var(--surface-sunk);
  border-bottom:1px solid var(--line);white-space:nowrap;
}
td{padding:7px 8px;border-bottom:1px solid var(--line);vertical-align:middle}
tbody tr:last-child td{border-bottom:0}
td input,td select{border-color:transparent;background:transparent;padding:6px 7px}
td input:hover,td select:hover{border-color:var(--line-strong)}
td input:focus,td select:focus{background:var(--surface)}
th.num,td.num{text-align:right}
td .money::before{left:6px}
td .money input{padding-left:18px;text-align:right}
.rowbtn{
  border:0;background:transparent;color:var(--ink-3);cursor:pointer;
  font-size:16px;line-height:1;padding:5px 7px;border-radius:var(--radius);
}
.rowbtn:hover{background:var(--bad-wash);color:var(--bad)}
.btn-add{
  margin-top:10px;display:inline-flex;align-items:center;gap:7px;
  border:1px dashed var(--line-strong);background:transparent;color:var(--brand);
  padding:7px 13px;border-radius:var(--radius);font:inherit;font-size:13px;font-weight:600;cursor:pointer;
}
.btn-add:hover{border-color:var(--brand-2);background:var(--brand-wash)}

.totals{
  margin-top:16px;margin-left:auto;max-width:420px;
  display:grid;grid-template-columns:1fr auto;gap:8px 20px;align-items:center;
  font-size:13.5px;
}
.totals .k{color:var(--ink-2)}
.totals .v{font-family:"IBM Plex Mono",monospace;font-variant-numeric:tabular-nums;text-align:right}
.totals .rule{grid-column:1/-1;height:1px;background:var(--line);margin:2px 0}
.totals .net{font-weight:700;color:var(--ink);font-size:15px}
.totals .net .v{font-size:16px}
.totals input{max-width:130px}

/* ---------- attachments ---------- */
.drop{
  /*
    display:block is not in the mockup and is load-bearing here. The mockup's drop zone is a <div>;
    this app makes it a <label> so the whole panel targets the hidden file input and is keyboard
    reachable with no JS interop. A <label> is INLINE by default, so the dashed box collapsed to the
    width of nothing and the text spilled out beside it - "Choose receipts, or dr" inside the border and
    "op them here" outside it. Observed in Chrome on 2026-08-31.
  */
  display:block;
  border:1.5px dashed var(--line-strong);border-radius:var(--radius);
  padding:26px 20px;text-align:center;background:var(--surface-sunk);
  cursor:pointer;transition:border-color .12s, background .12s;
}
.drop:hover,.drop.over{border-color:var(--brand-2);background:var(--brand-wash)}
.drop .big{font-size:14.5px;font-weight:600;color:var(--ink);display:block;margin-bottom:3px}
.drop .small{font-size:12.5px;color:var(--ink-3)}
.files{list-style:none;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:8px}
.file{
  display:grid;grid-template-columns:44px 1fr auto auto;gap:12px;align-items:center;
  padding:9px 11px;border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);
}
.thumb{
  width:44px;height:44px;border-radius:3px;background:var(--surface-sunk);
  display:grid;place-items:center;overflow:hidden;border:1px solid var(--line);
  font-family:"IBM Plex Mono",monospace;font-size:10px;font-weight:600;color:var(--ink-3);letter-spacing:.04em;
}
.thumb img{width:100%;height:100%;object-fit:cover;display:block}
.file .meta{min-width:0}
/*
  display:block on both, and it is load-bearing rather than tidiness. They are <span>s, so the name
  and the size run together on one line as inline boxes - and an inline box cannot be ellipsised, so
  a long receipt filename ran straight through the size and out under the remove button instead of
  truncating. Blocks inside the min-width:0 grid item truncate as intended.
*/
.file .name,.file .sz{display:block}
.file .name{font-size:13.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.file .sz{font-size:12px;color:var(--ink-3);font-family:"IBM Plex Mono",monospace}
.file select{width:auto;font-size:12.5px;padding:5px 8px}

.badge{
  display:inline-flex;align-items:center;gap:5px;
  font-size:11.5px;font-weight:600;letter-spacing:.03em;
  padding:3px 9px;border-radius:99px;white-space:nowrap;
}
.badge.ok{background:var(--ok-wash);color:var(--ok)}
.badge.warn{background:var(--warn-wash);color:var(--warn)}
.badge.bad{background:var(--bad-wash);color:var(--bad)}
.badge.neutral{background:var(--surface-sunk);color:var(--ink-3)}

.notice{
  display:grid;grid-template-columns:auto 1fr;gap:11px;align-items:start;
  padding:12px 14px;border-radius:var(--radius);font-size:13px;line-height:1.5;
  background:var(--brand-wash);color:var(--ink-2);border-left:2px solid var(--brand-2);
}
.notice.warn{background:var(--warn-wash);border-left-color:var(--warn);color:var(--ink-2)}
.notice.bad{background:var(--bad-wash);border-left-color:var(--bad)}
.notice b{color:var(--ink)}
.notice .ico{font-family:"IBM Plex Mono",monospace;font-weight:700;color:var(--brand)}
.notice.warn .ico{color:var(--warn)} .notice.bad .ico{color:var(--bad)}

.locked{opacity:.72}
.locked .body{pointer-events:none}
.sig{
  display:grid;grid-template-columns:1fr 190px;gap:18px;align-items:end;
  padding:16px;border:1px solid var(--line-strong);border-radius:var(--radius);
  background:var(--surface-sunk);
}
@media (max-width:700px){.sig{grid-template-columns:1fr}}
.sig .typed{
  font-family:"Newsreader",Georgia,serif;font-style:italic;font-size:22px;
  border:0;border-bottom:1px solid var(--line-strong);border-radius:0;background:transparent;padding:4px 2px;
}

/* ---------- action bar ---------- */
.actionbar{
  position:fixed;left:0;right:0;bottom:0;z-index:50;
  background:var(--surface);border-top:1px solid var(--line);
  padding:11px 28px;
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  box-shadow:0 -8px 24px -20px rgba(0,0,0,.5);
}
.actionbar .sum{display:flex;gap:22px;align-items:baseline;flex-wrap:wrap}
.actionbar .sum div{display:flex;flex-direction:column;gap:1px}
.actionbar .sum .k{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-3);font-weight:600}
.actionbar .sum .v{font-family:"IBM Plex Mono",monospace;font-size:16px;font-weight:600;font-variant-numeric:tabular-nums}
.actionbar .spacer{flex:1}
button.b{
  font:inherit;font-size:13.5px;font-weight:600;cursor:pointer;
  padding:9px 17px;border-radius:var(--radius);border:1px solid var(--line-strong);
  background:var(--surface);color:var(--ink);
}
button.b:hover{border-color:var(--brand-2);color:var(--brand)}
button.b.primary{background:var(--brand);border-color:var(--brand);color:#fff}
:root[data-theme="dark"] button.b.primary,
:root:not([data-theme="light"]) button.b.primary{color:var(--ground)}
@media (prefers-color-scheme: light){:root:not([data-theme="dark"]) button.b.primary{color:#fff}}
button.b.primary:hover{background:var(--brand-2);border-color:var(--brand-2)}

.banner{
  margin:0 0 18px;padding:14px 16px;border-radius:var(--radius);
  background:var(--bad-wash);border-left:2px solid var(--bad);
}
.banner h4{margin:0 0 6px;font-size:13.5px;color:var(--bad)}
.banner ul{margin:0;padding-left:18px;font-size:13px;color:var(--ink-2);line-height:1.6}
.banner a{color:var(--ink-2)}

.footnote{
  max-width:78ch;margin:26px 0 0;font-size:12.5px;line-height:1.6;color:var(--ink-3);
  border-top:1px solid var(--line);padding-top:14px;
}

/* ---------- phones and small tablets ---------- */
/*
  Not in the mockup, which was drawn at desk width and carries one breakpoint - the .grid collapse at
  900px above. Everything below is this app's, and none of it is decoration: these forms get filled in
  standing up, in a car park, one-handed, with the receipt in the other hand.

  Four things in the desk design are broken rather than merely cramped on a phone, and each is fixed
  here rather than worked around:

  - The rail is 238px, which is 61% of an iPhone. It becomes a horizontal stepper pinned to the top of
    the scroll, and the topbar gives up being sticky so the two do not both eat the viewport.
  - The line table is ~990px of column min-widths. Sideways scrolling inside a form is a trap on touch
    - a swipe meant for the page moves the table instead - so every row becomes a labelled card. That
    is what the data-label attribute on each <td> is for; do not remove it.
  - Table fields are borderless until hovered. A touch screen never hovers, so on a phone the whole
    table would be invisible fields. They get their border back here.
  - Every input is 14px, and iOS Safari zooms the page when a field under 16px takes focus - leaving
    the user zoomed in with no gesture that undoes it. 16px on mobile is a functional requirement,
    not a preference.

  One breakpoint for all of it, the same 900px the .grid collapse uses. A layout that changes shape
  twice at two widths has a broken state in between that nobody ever looks at.
*/
@media (max-width:900px){
  /* the stepper is what sticks; two sticky bars would leave a phone with no viewport */
  .topbar{position:static;padding:11px 16px;gap:12px}
  .mark{width:30px;height:30px;font-size:15px}
  .topbar h1{font-size:17px}
  /* the theme button costs the title block 38px, which is what the church name no longer has. It
     truncates rather than wrapping the whole topbar to a second line. */
  .topbar .sub{
    font-size:9.5px;letter-spacing:.03em;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }
  .iconbtn{width:38px;height:38px}

  /*
    The crumb is the only way back on a phone, so the link has to be a real tap target rather than a
    12px word. The negative margin cancels the padding's effect on the strip's height, so it grows
    the touch area without pushing the form down the page.

    It is above the sticky rail in the source and is not sticky itself, so it scrolls away as soon as
    somebody starts filling the form in - which is the point. Two pinned strips is one too many at
    this width.
  */
  .crumbs{padding:8px 14px 0}
  .crumbs ol{font-size:13px;gap:8px}
  .crumbs li + li::before{margin-right:8px}
  /* 12px of padding against a 20px line box is what gets this to the 44px a thumb needs - measured
     at 390px, where 9px left it at 39px. The negative margin keeps the strip its original height. */
  .crumbs a{display:inline-block;padding:12px 2px;margin:-12px 0}

  .layout{grid-template-columns:minmax(0,1fr)}

  /* ----- rail becomes a scrolling stepper ----- */
  .rail{
    position:sticky;top:0;z-index:35;
    padding:0;max-height:none;
    overflow-x:auto;overflow-y:hidden;
    background:var(--surface);border-bottom:1px solid var(--line);
    -webkit-overflow-scrolling:touch;scrollbar-width:none;
  }
  .rail::-webkit-scrollbar{display:none}
  .rail h2{display:none}
  .rail ol{flex-direction:row;gap:6px;padding:9px 14px;width:max-content}
  .rail a{
    grid-template-columns:auto auto;gap:7px;align-items:center;
    padding:7px 13px;border:1px solid var(--line);border-radius:99px;
    white-space:nowrap;font-size:13px;
  }
  .rail a > span:last-child{display:inline-flex;align-items:center;gap:6px}
  .rail .dot{margin-left:0}

  /* ----- form column ----- */
  /* no clearance to reserve - the action bar is not fixed at this width, see below */
  main{padding:18px 14px 0}
  .lede{font-size:16px;margin-bottom:20px;max-width:none}
  section.card{margin-bottom:14px;scroll-margin-top:58px}
  .card > header{padding:12px 14px;gap:9px}
  /* flex:1 1 0 lets a long caption wrap its own text beside the number. Without it the h3 is an
     unshrinkable flex item and wraps whole, leaving "04" orphaned on a line of its own. */
  .card > header h3{font-size:15px;flex:1 1 0;min-width:0}
  /* the hint drops to its own line rather than squeezing the heading into two words a line */
  .card > header .hint{margin-left:0;flex:0 0 100%;font-size:12px}
  .card .body{padding:14px}

  /* ----- fields: 16px is the anti-zoom rule, the padding is the 44px touch target ----- */
  input[type=text],input[type=date],input[type=time],input[type=number],input[type=email],
  select,textarea{font-size:16px;padding:10px 12px}
  .money input{padding-left:25px}
  .money::before{left:11px;font-size:14px}
  .chips{gap:8px}
  .chip{padding:9px 14px;font-size:14px}
  .yn label{padding:9px 20px;font-size:14px}
  .checkline{padding:13px;font-size:14px;grid-template-columns:20px 1fr}
  .checkline input{width:20px;height:20px;margin-top:1px}

  /* a question and its No/Yes will not both fit on a line; stacking beats shrinking either */
  .q{flex-direction:column;align-items:stretch;gap:11px;padding:15px 0}
  .q p{max-width:none}
  .q .yn{align-self:flex-start}
  .reveal{padding:12px}

  /* ----- tables become one card per row ----- */
  .tablewrap{overflow:visible;border:0;border-radius:0}
  .tablewrap table{display:block;width:100%;min-width:0}
  .tablewrap thead{display:none}
  .tablewrap tbody{display:block;counter-reset:row}
  .tablewrap tr{
    display:block;position:relative;counter-increment:row;
    padding:0 12px 8px;margin-bottom:10px;
    border:1px solid var(--line);border-radius:var(--radius);
    background:var(--surface-sunk);
  }
  /*
    A caption strip across the top of each row card. It earns its place twice: stacked rows otherwise
    run together with nothing saying where one ends, and it gives the remove button somewhere to sit
    that is not on top of the first field. Padding the first cell instead left that field visibly
    shorter than the six below it, which reads as a bug rather than as a gap.
  */
  .tablewrap tr::before{
    content:"Row " counter(row);
    display:block;padding:9px 32px 8px 0;
    border-bottom:1px solid var(--line);
    font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);
  }
  .tablewrap.lines tr::before{content:"Line " counter(row)}
  .tablewrap td{
    /* 42% keeps "Supplier & item / service" and "Receipt / tax invoice" on one line at 390px, and
       still leaves the field wider than a date picker needs */
    display:grid;grid-template-columns:minmax(0,42%) minmax(0,1fr);
    gap:12px;align-items:center;text-align:left;
    padding:8px 0;border-bottom:1px solid var(--line);
  }
  .tablewrap td::before{
    content:attr(data-label);
    font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--ink-2);
  }
  /* the remove cell is always last, so the last field is the one before it */
  .tablewrap td:nth-last-child(2){border-bottom:0}
  .tablewrap td.num{text-align:left}
  .tablewrap td input,.tablewrap td select{
    border-color:var(--line-strong);background:var(--surface);padding:9px 11px;
  }
  .tablewrap td .money input{padding-left:25px;text-align:left}
  .tablewrap td .money::before{left:11px}
  /* remove is a control, not a field: it belongs on the row card's corner, not in the label column */
  .tablewrap td.rowdel{display:block;position:absolute;top:3px;right:6px;padding:0;border:0}
  .tablewrap td.rowdel::before{content:none}
  .tablewrap .rowbtn{font-size:20px;padding:7px 10px}

  .btn-add{padding:10px 16px;font-size:14px}

  /* the totals stop being a right-hand column and become a panel */
  .totals{
    max-width:none;margin-left:0;margin-top:14px;
    padding:12px 14px;border:1px solid var(--line);border-radius:var(--radius);
    background:var(--surface-sunk);
  }
  .totals .money{width:150px;margin-left:auto}
  .totals input{max-width:none;width:100%}

  /* ----- attachments ----- */
  .drop{padding:22px 14px}
  .drop .big{font-size:15px}
  .file{
    grid-template-columns:40px minmax(0,1fr) 34px;
    grid-template-areas:"thumb meta del" "badge badge badge";
    row-gap:9px;padding:10px;
  }
  .file .thumb{grid-area:thumb}
  .file .meta{grid-area:meta}
  .file .badge,.file .kind{grid-area:badge;justify-self:start}
  .file .rowbtn{grid-area:del;font-size:20px}

  /* ----- action bar ----- */
  /*
    It stops being pinned. A fixed bottom bar and the on-screen keyboard fight each other: iOS shrinks
    the visual viewport but not the layout viewport, so the bar either sits behind the keyboard or
    floats halfway up the screen over the field being typed into, and it is doing that for most of the
    time anyone spends on this page. Here it is the last block on the page, which is where somebody
    who has finished a form is looking anyway.

    env(safe-area-inset-bottom) still applies - unpinned or not, it is the last thing above the iPhone
    home indicator. It needs viewport-fit=cover in index.html to be anything other than 0.
  */
  .actionbar{
    position:static;
    padding:14px 14px calc(18px + env(safe-area-inset-bottom));
    gap:9px;box-shadow:none;
  }
  /*
    Four figures do not fit across a phone, and the two that matter are first. The rest scroll - and
    the fade on the right edge is the only thing that says so, because the scrollbar is hidden and a
    value simply cut off at the edge reads as a clipping bug rather than as more to come.
  */
  .actionbar .sum{
    flex:0 0 100%;gap:16px;flex-wrap:nowrap;
    overflow-x:auto;scrollbar-width:none;
    -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 26px),transparent);
    mask-image:linear-gradient(to right,#000 calc(100% - 26px),transparent);
  }
  .actionbar .sum::-webkit-scrollbar{display:none}
  .actionbar .sum div{flex:none}
  .actionbar .sum .k{font-size:9.5px}
  .actionbar .sum .v{font-size:14px}
  .actionbar .spacer{display:none}
  .actionbar .badge{flex:0 0 100%}
  /* the buttons share the row below the figures, with submit weighted so the thumb finds it */
  .actionbar button.b{flex:1 1 0;padding:12px 10px;font-size:14.5px}
  .actionbar button.b.primary{flex:1.7 1 0}

  /*
    Buttons inside a card - Discard on the drafts page, and its confirm pair - get the same treatment
    the action bar's already have. The base .b is 36px tall, which is under the 44px a thumb needs,
    and these are the only buttons on this app that live in the page body rather than the action bar.
  */
  main button.b{padding:12px 16px;font-size:14.5px}

  /* the landing page's two links become the tap targets they have to be on a phone */
  .cardlink{
    display:block;margin-top:2px;padding:13px 16px;
    border:1px solid var(--brand-2);border-radius:var(--radius);background:var(--brand-wash);
    text-align:center;text-decoration:none;font-size:15px;font-weight:600;
  }

  .footnote{max-width:none}
}

/* ---------- print / PDF ---------- */
.printonly{display:none}
@media print{
  @page{size:A4;margin:14mm 13mm}
  body{background:#fff;color:#000;font-size:10pt}
  .topbar,.rail,.actionbar,.btn-add,.rowbtn,.drop,.banner,.no-print{display:none !important}
  .layout{display:block;max-width:none}
  main{padding:0}
  .printonly{display:block}
  .card{break-inside:avoid;box-shadow:none;border:1px solid #999;margin-bottom:8pt}
  .card > header{background:#eee;padding:6pt 8pt}
  .card .body{padding:8pt}
  .grid{gap:8pt 10pt}
  input,select,textarea{border:0;border-bottom:1px solid #999;border-radius:0;background:#fff;padding:1pt 2pt;font-size:10pt;color:#000}
  textarea{border:1px solid #999;min-height:auto}
  .chip{border-color:#999}
  .file{break-inside:avoid;border-color:#999}
  .thumb{display:none}
  .file{grid-template-columns:1fr auto}
  .pagebreak{break-before:page}
  .totals{max-width:none}
}
.printhead{
  border-bottom:2px solid #000;padding-bottom:6pt;margin-bottom:10pt;
}
.printhead h2{font-family:"Newsreader",Georgia,serif;margin:0;font-size:16pt}
.printhead p{margin:2pt 0 0;font-size:9pt}

/* ---------- Blazor shell ---------- */
/*
  Not from the mockup - the mockup is a static page and has no boot phase. These style the two things
  a WASM app shows outside its own render tree: the download/boot spinner in #app, and the unhandled
  error strip Blazor unhides itself. Both use the tokens above so a cold load and a crash still look
  like this app rather than like the framework default.
*/
.loading-progress{
  position:relative;display:block;width:8rem;height:8rem;margin:20vh auto 1rem;
}
.loading-progress circle{
  fill:none;stroke:var(--surface-sunk);stroke-width:.6rem;
  transform-origin:50% 50%;transform:rotate(-90deg);
}
.loading-progress circle:last-child{
  stroke:var(--brand);
  stroke-dasharray:calc(3.141 * var(--blazor-load-percentage, 0%) * 2) 500%;
  transition:stroke-dasharray .05s ease-in-out;
}
.loading-progress-text{
  position:relative;text-align:center;font-size:13px;color:var(--ink-3);
  font-family:"IBM Plex Mono",monospace;
}
.loading-progress-text:after{
  content:var(--blazor-load-percentage-text, "Loading");
}

#blazor-error-ui{
  display:none;position:fixed;bottom:0;left:0;right:0;z-index:1000;
  padding:14px 20px;font-size:13.5px;
  background:var(--bad-wash);color:var(--ink);
  border-top:2px solid var(--bad);
}
#blazor-error-ui .dismiss{cursor:pointer;position:absolute;right:16px;top:12px;color:var(--ink-3)}
#blazor-error-ui .reload{color:var(--bad);font-weight:600}

/*
  Visually hidden but still announced. Used for a table header whose column holds only an icon button:
  omitting the header entirely leaves screen-reader users with an unlabelled column, and a visible one
  would print in the PDF.
*/
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
}
