/* Receipts. Colours only as tokens below. Dark values follow the device's setting, unless the header toggle
   (js/theme.js) has set data-theme="light" or "dark" on <html>. Phone-first. */
:root {
  color-scheme: light dark;
  --bg: #f6f6f3;
  --surface: #ffffff;
  --text: #1c1c1a;
  --muted: #5b5b56;
  --border: #d3d3cc;
  --accent: #1d5bb8;
  --accent-text: #ffffff;
  --danger: #b3261e;
  --danger-text: #ffffff;
  --focus: #1d5bb8;
  --ok-bg: #e5f1e7;
  --ok-text: #1c4a26;
  --err-bg: #fbe8e6;
  --err-text: #8a1c17;
  --attention: #8a5100;
  --attention-bg: #fff4e0;
  --frame: #ffffff;
  --header-bg: var(--surface);
  --radius: 8px;
  --gap: 1rem;
  --density: 1;   /* list spacing, set with − and + on Bank, Receipts and Classify (js/density.js) */
  /* To-do projects: a dot each, so a project is recognised at a glance without colour having to carry any meaning. */
  --project-slate: #4a5a6a;
  --project-teal:  #1c6b62;
  --project-plum:  #7a3d6b;
  --project-amber: #8a5100;
  --project-moss:  #4a6b30;
  --project-sky:   #1d5bb8;
  --project-clay:  #94502c;
  --project-rose:  #a32d4e;
  --project-job:   #5b5b56;
}
/* Calm (chosen in Settings, per device: js/early.js sets data-palette): light mode in soft blues, sage greens and warm
   neutrals, for eyes that tire of bright white. Every text pair is WCAG AA or better. Before the dark rules, so dark
   mode keeps its own tokens. */
:root[data-palette="calm"] {
  --bg: #f2eee6;
  --surface: #fbf8f2;
  --text: #1e2a2f;
  --muted: #54595a;
  --border: #d2c8b6;
  --accent: #1e6078;
  --focus: #1e6078;
  --ok-bg: #e1eee3;
  --ok-text: #22502f;
  --attention: #7a4a06;
  --attention-bg: #f7e9d2;
  --header-bg: #e3ecec;
}
/* Dark tokens appear twice — for "automatic" on a dark device, and for an explicit dark choice. Keep them the same. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #131312;
    --surface: #1e1e1c;
    --text: #ebebe8;
    --muted: #a9a9a3;
    --border: #3c3c38;
    --accent: #8ab6ff;
    --accent-text: #0b1a33;
    --danger: #ff8f86;
    --danger-text: #2b0704;
    --focus: #8ab6ff;
    --ok-bg: #1b3221;
    --ok-text: #bde5c5;
    --err-bg: #3b1714;
    --err-text: #ffc6bf;
    --attention: #f2c46d;
    --attention-bg: #3a2a0e;
    --header-bg: var(--surface);
    --project-slate: #9ab0c4;
    --project-teal:  #6fcfc2;
    --project-plum:  #d99ac8;
    --project-amber: #f2c46d;
    --project-moss:  #a8cf86;
    --project-sky:   #8ab6ff;
    --project-clay:  #e5a681;
    --project-rose:  #f2909f;
    --project-job:   #a9a9a3;
  }
}
:root[data-theme="dark"] {
  --bg: #131312;
  --surface: #1e1e1c;
  --text: #ebebe8;
  --muted: #a9a9a3;
  --border: #3c3c38;
  --accent: #8ab6ff;
  --accent-text: #0b1a33;
  --danger: #ff8f86;
  --danger-text: #2b0704;
  --focus: #8ab6ff;
  --ok-bg: #1b3221;
  --ok-text: #bde5c5;
  --err-bg: #3b1714;
  --err-text: #ffc6bf;
  --attention: #f2c46d;
  --attention-bg: #3a2a0e;
  --header-bg: var(--surface);
  --project-slate: #9ab0c4;
  --project-teal:  #6fcfc2;
  --project-plum:  #d99ac8;
  --project-amber: #f2c46d;
  --project-moss:  #a8cf86;
  --project-sky:   #8ab6ff;
  --project-clay:  #e5a681;
  --project-rose:  #f2909f;
  --project-job:   #a9a9a3;
  color-scheme: dark;
}
:root[data-theme="light"] { color-scheme: light; }
:root:not(.js) .palette-choice { display: none; }

*, *::before, *::after { box-sizing: border-box; }
/* Atkinson Hyperlegible Next: characters that can't be mistaken for each other (1 l I, 0 O, 5 S, 8 B), which matters
   most for amounts and numbers. Receipts' own copy (fonts/, SIL OFL 1.1), served from this site only. */
@font-face { font-family: "Atkinson Hyperlegible Next"; font-weight: 400; font-style: normal; font-display: swap;
  src: url("../fonts/atkinson-hyperlegible-next/AtkinsonHyperlegibleNext-Regular.woff2") format("woff2"); }
@font-face { font-family: "Atkinson Hyperlegible Next"; font-weight: 600; font-style: normal; font-display: swap;
  src: url("../fonts/atkinson-hyperlegible-next/AtkinsonHyperlegibleNext-SemiBold.woff2") format("woff2"); }
@font-face { font-family: "Atkinson Hyperlegible Next"; font-weight: 700; font-style: normal; font-display: swap;
  src: url("../fonts/atkinson-hyperlegible-next/AtkinsonHyperlegibleNext-Bold.woff2") format("woff2"); }
body { margin: 0; background: var(--bg); color: var(--text);
  font: 16px/1.5 "Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI", sans-serif; }
a { color: var(--accent); }
h1 { font-size: 1.5rem; margin: 0 0 var(--gap); }
h2 { font-size: 1.15rem; margin: 1.5rem 0 .5rem; }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
/* Sent here to fill something in (a to-do's button carries the box's own address): the box says which one it means,
   three times, then stops. Reduced motion gets a steady ring instead. */
input:target, select:target, textarea:target { scroll-margin-top: 1.5rem; animation: point-at 900ms ease-in-out 3; }
@keyframes point-at {
  from, to { box-shadow: 0 0 0 0 transparent; }
  50%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 45%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  input:target, select:target, textarea:target { animation: none; outline: 2px solid var(--accent); outline-offset: 2px; }
}
[hidden] { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.muted, .hint { color: var(--muted); }
.hint { font-size: .875rem; margin: .25rem 0 0; }

.skip { position: absolute; left: -999px; }
.skip:focus { left: 1rem; top: .5rem; background: var(--surface); padding: .5rem; z-index: 1; }

.top { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem 1rem;
  padding: .75rem 1rem; background: var(--header-bg); border-bottom: 1px solid var(--border); }
.brand { font-weight: 700; color: var(--text); text-decoration: none; font-size: 1.1rem; }
.brand-row { display: inline-flex; align-items: center; gap: .25rem; min-width: 0; }
/* The account being worked in, when it isn't your own. */
.account-chip { display: inline-block; max-width: 12rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: .15rem .6rem; font-size: .875rem; font-weight: 600; color: var(--accent-text); background: var(--accent);
  border-radius: 999px; text-decoration: none; }
/* Theme toggle: deliberately quiet — just a muted icon, with a full-size touch target. */
.theme-toggle { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin: -10px 0;
  padding: 0; border: 0; background: none; color: var(--muted); border-radius: var(--radius); }
.theme-toggle .icon { width: 20px; height: 20px; }
.theme-toggle > span { display: none; }
:root:not([data-theme]) .theme-toggle .i-auto, :root[data-theme="light"] .theme-toggle .i-light, :root[data-theme="dark"] .theme-toggle .i-dark { display: inline-flex; }
/* This page's help (inc/help.php): the same quiet icon as the theme toggle, opening a dialog. */
.help-open { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin: -10px 0;
  color: var(--muted); border-radius: var(--radius); }
.help-open .icon { width: 20px; height: 20px; }
/* Wide screens: the menu fills the space beside the brand and sits on the right. When space runs short the area links
   wrap (right-aligned); the bar and the account items (Admin, Settings, Guide, Sign out: icons) stay together beside
   them, so a bar never ends up alone at the end of a line — and wrap too, two icons wide, so both halves match
   (js/menu.js sets .is-wrapped). */
.top nav { flex: 1 1 0; min-width: 0; display: flex; align-items: center; justify-content: flex-end; }
.top nav form { margin: 0; }
.nav-group { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: .25rem 1rem; min-width: 0; }
.nav-account, .nav-sep { flex: none; }
.nav-account { flex-wrap: nowrap; }
.nav-sep::before { content: "|"; color: var(--muted); padding: 0 .75rem; }
.nav-account { gap: 0 .25rem; }
.top .nav-icon { display: inline-flex; align-items: center; justify-content: center; gap: .5rem; min-width: 44px; min-height: 44px;
  margin: -10px 0; padding: 0; color: var(--muted); border-radius: var(--radius); text-decoration: none; }
.top .nav-icon:hover { color: var(--text); }
.nav-icon .icon { width: 20px; height: 20px; }
.nav-icon .nav-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* An icon can't be underlined: the page you're on gets the accent and a bar under it instead. */
.top .main-nav .nav-icon[aria-current="page"] { color: var(--accent); box-shadow: inset 0 -3px 0 var(--accent); }
.main-nav.is-wrapped .nav-account { flex-wrap: wrap; justify-content: flex-end; width: calc(2 * 44px + .25rem); }
/* Wrapped: icon rows 40px apart, and the link lines spaced to match, so the two halves line up row for row. */
.main-nav.is-wrapped .nav-icon { margin: 0; min-width: 40px; min-height: 40px; }
.main-nav.is-wrapped .nav-group:not(.nav-account) { row-gap: calc(40px - 1lh); }
.menu-toggle { min-height: 40px; padding: .25rem .75rem; }
.theme-toggle:not(.js *), .menu-toggle:not(.js *) { display: none; }   /* both need JavaScript */
@media (min-width: 48rem) { .menu-toggle { display: none; } }
@media (max-width: 47.99rem) {
  /* Phones: brand and theme on the left, Menu on the right; the menu opens as a full-width list below. */
  .js .top nav { display: none; }
  .js .top nav.is-open { display: flex; flex: 1 1 100%; flex-direction: column; align-items: stretch; gap: 0; width: 100%;
    border-top: 1px solid var(--border); padding-top: .25rem; }
  .js .top nav.is-open a, .js .top nav.is-open button.link { display: flex; align-items: center; min-height: 44px; padding: 0 .25rem; }
  .js .top nav.is-open .nav-group { flex-direction: column; align-items: stretch; gap: 0; }
  .js .top nav.is-open .nav-sep { border-top: 1px solid var(--border); margin: .25rem 0; }
  .js .top nav.is-open .nav-sep::before { content: none; }
  /* The phone menu has room for the words: icon and word, left-aligned like the rest of the list. */
  .js .top nav.is-open .nav-icon { justify-content: flex-start; margin: 0; color: var(--accent); }
  .js .top nav.is-open .nav-label { position: static; width: auto; height: auto; overflow: visible; clip-path: none; text-decoration: underline; }
}
[aria-current="page"] { text-decoration: underline; text-underline-offset: 4px; }
/* The page you're on stands out in the menu: bold and in the accent, with the underline kept so it isn't colour alone. */
.main-nav [aria-current="page"] { font-weight: 700; color: var(--accent); text-decoration-thickness: 2px; }

main { max-width: 76rem; margin: 0 auto; padding: 1rem; }
.narrow { max-width: 24rem; margin: 2rem auto; }

/* ── controls ─────────────────────────────────────────────────────────────── */
button, .button { font: inherit; display: inline-flex; align-items: center; gap: .4rem; min-height: 44px; padding: .5rem 1rem;
  border-radius: var(--radius); border: 1px solid var(--accent); background: var(--surface); color: var(--accent);
  cursor: pointer; text-decoration: none; }
button.primary, .button.primary { background: var(--accent); color: var(--accent-text); }
button.secondary, .button.secondary { border-color: var(--border); color: var(--text); }
button.link { border: 0; background: none; color: var(--accent); padding: .5rem 0; min-height: 0; text-decoration: underline; }
button:disabled, button[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
/* A form on its way (sign in, js/auth.js): its fields show they're locked. */
form[aria-busy="true"] input:disabled { opacity: .6; cursor: progress; }
/* A button waiting on something (house style, js/waits.js): the reason sits right under it. */
.wait-note { margin: -.25rem 0 0; }
.wait-note:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
/* How often you lodge, when there's nothing to lodge (tax settings): clearly off, with its reason under it. */
select[data-wording]:disabled { opacity: .5; cursor: not-allowed; }
button[aria-pressed="true"] { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.button:focus-within { outline: 3px solid var(--focus); outline-offset: 2px; }
.icon { width: 1.25rem; height: 1.25rem; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex: none; }
/* Icon buttons and icon links look the same: a 44px square target. */
.icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; padding: 0;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--text); }
.icon-btn.danger { color: var(--danger); }
.icon-btn.armed { background: var(--danger); color: var(--danger-text); border-color: var(--danger); }

input, select, textarea { font: inherit; color: var(--text); background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: .5rem .6rem; min-height: 44px; width: 100%; }
/* A date is as wide as a date: dd/mm/yyyy and its calendar, wherever it appears. Stretching one across a form makes
   it harder to read, not easier. It still shrinks to fit a narrow column. */
input[type="date"] { width: auto; max-width: 100%; }
[aria-invalid="true"] { border-color: var(--danger); }
label, legend { display: block; font-weight: 600; margin-bottom: .25rem; }
fieldset { border: 0; padding: 0; margin: 0; }

.stack > * + * { margin-top: var(--gap); }
/* A fieldset that only groups (and, disabled, makes a form read-only): no box of its own, children spaced like .stack. */
fieldset.plain { border: 0; padding: 0; margin-inline: 0; min-width: 0; }
fieldset.plain > * + * { margin-top: var(--gap); }
.field { min-width: 0; }
.field-err { color: var(--err-text); font-size: .875rem; margin: .25rem 0 0; }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); }
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; margin-top: var(--gap); }

.flash, .error { padding: .6rem .8rem; border-radius: var(--radius); }
.flash { background: var(--ok-bg); color: var(--ok-text); }
.error { background: var(--err-bg); color: var(--err-text); }
.status { font-weight: 600; margin: 0 0 var(--gap); }
/* Reading a scanned receipt (receipt.php #progress, js/capture.js): a plain status line, or while busy a panel that
   stays in view with a spinner, elapsed time and a note that typing is safe. */
.progress { display: flex; gap: .75rem; align-items: flex-start; }
.progress .spinner { display: none; }
.progress.is-busy { position: sticky; top: 0; z-index: 2; margin: 0 0 var(--gap); padding: .75rem 1rem;
  background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--accent); border-radius: var(--radius); }
.progress.is-busy .status { margin: 0; }
.progress.is-busy .spinner { display: block; flex: 0 0 auto; width: 1.25rem; height: 1.25rem; margin-top: .15rem;
  border: 3px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .progress.is-busy .spinner { animation: none; border-color: var(--accent); }
}
.status:empty { margin: 0; }

/* ── list ─────────────────────────────────────────────────────────────────── */
.page-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: .5rem; margin-bottom: var(--gap); }
.page-head h1 { margin: 0; }
/* Filters: one wrapping row, "Clear filter" at the end, so wide screens fit everything on one line. */
.filters { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 1rem; }
.filters fieldset { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--gap); }
.filters legend { font-size: 1rem; margin-bottom: .5rem; }
.filters .field { flex: 1 1 9rem; }
.filters .field:has(#q) { flex-grow: 1.5; }
.filters-clear { flex: 0 0 auto; }
.filters-toggle { display: none; }
@media (max-width: 47.99rem) {
  /* Phones: the heading is visually replaced by the Filter button (the brand already says Receipts);
     the panel collapses unless filters are active; labels sit beside their controls. */
  .page-head h1 { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .js .filters-toggle { display: inline-flex; }
  .js .filters:not(.is-open) { display: none; }
  .js .filters legend { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .filters { padding: .75rem; }
  .filters fieldset { display: grid; gap: .5rem; }
  .filters .field { display: grid; grid-template-columns: 5.5rem minmax(0, 1fr); align-items: center; gap: .5rem; }
  .filters .field label { margin: 0; font-size: .9rem; line-height: 1.2; }
  .filters-clear { justify-self: start; }
}
/* Filter changes reload the page; where supported, cross-fade instead of blanking. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
.list-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: .5rem 1rem; margin: 1rem 0 .5rem; }
.list-head form { margin: 0; }
.count { color: var(--muted); margin: 0; }
/* What's left to do reads first, with the money beside it where the width allows and under it where it doesn't. */
.counts { display: flex; flex-wrap: wrap; align-items: baseline; gap: .15rem .75rem; min-width: 0; }
.count-money { font-variant-numeric: tabular-nums; }
/* Receipts table. Wide screens: columns. Phones (below): each row is a card. */
.table-wrap { overflow-x: auto; }
.receipts { width: 100%; border-collapse: collapse; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.receipts th { text-align: left; font-size: .875rem; color: var(--muted); font-weight: 600; }
.receipts th, .receipts td { padding: calc(.5rem * var(--density)) calc(.75rem * var(--density)); border-bottom: 1px solid var(--border); vertical-align: top; }
.receipts tbody tr:last-child td { border-bottom: 0; }
.receipts .c-date, .receipts .c-category { white-space: nowrap; }
.receipts .c-vendor { font-weight: 600; }
.receipts .c-desc { overflow-wrap: anywhere; min-width: 10rem; }
.receipts .c-total, .receipts .c-tax { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.receipts td.c-actions { white-space: nowrap; }
/* Edit and delete with a real gap, so the destructive one isn't hit by mistake. */
.c-actions .icon-btn + form { margin-left: .75rem; }
.c-actions form { display: inline; margin: 0; }
.c-category .muted { font-size: .875rem; }
.tag { display: inline-block; padding: 0 .5rem; border: 1px solid var(--border); border-radius: 999px; font-size: .875rem; }

@media (max-width: 47.99rem) {
  .receipts, .receipts tbody { display: block; background: none; border: 0; }
  .receipts thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .receipts tbody { display: grid; gap: calc(.5rem * var(--density)); }
  /* Vendor gets the whole first line; total and tax on the second; date and category on the third. */
  .receipts tr { display: grid; grid-template-columns: auto 1fr 44px; gap: .15rem .75rem;
    grid-template-areas: "vendor vendor actions" "total tax actions" "date category actions" "desc desc actions";
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: calc(.75rem * var(--density)); }
  .receipts td { display: block; padding: 0; border: 0; min-width: 0; }
  .receipts .c-vendor { grid-area: vendor; }
  .receipts .c-total { grid-area: total; font-weight: 600; }
  .receipts .c-date { grid-area: date; color: var(--muted); font-size: .875rem; }
  .receipts .c-total { text-align: left; }
  .receipts .c-tax { grid-area: tax; color: var(--muted); font-size: .875rem; text-align: left; align-self: center; }
  .receipts .c-date { align-self: center; }
  .receipts .c-category { grid-area: category; }
  .receipts .c-desc { grid-area: desc; min-width: 0; }
  .receipts td.c-actions { grid-area: actions; display: flex; flex-direction: column; gap: .75rem; }
  .c-actions .icon-btn + form { margin-left: 0; }
}

/* ── add / edit ───────────────────────────────────────────────────────────── */
.capture { margin-bottom: var(--gap); }
.viewfinder { position: relative; max-width: 32rem; aspect-ratio: 3 / 4; background: var(--muted); border-radius: var(--radius); overflow: hidden; }
.viewfinder video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Shown from the start where there may be a camera, so the page doesn't shift when it comes on. */
.viewfinder-note { display: none; position: absolute; inset: 0; margin: 0; place-content: center; text-align: center;
  color: var(--surface); }
.viewfinder.is-starting .viewfinder-note { display: grid; }
.frame { position: absolute; inset: 6% 10%; border: 3px dashed var(--frame); border-radius: var(--radius); pointer-events: none; }
.receipt-layout { display: grid; gap: var(--gap); }
.receipt-img { margin: 0; min-width: 0; }
/* Zoom/pan box (js/viewer.js). Small enough on a phone to keep the form fields in view; zoom for detail. */
.viewer { position: relative; height: 40vh; overflow: hidden; touch-action: pan-y; cursor: grab;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); }
.viewer > img, .viewer > canvas { display: block; width: 100%; height: 100%; object-fit: contain; }
.viewer-note { margin: 0; padding: 1rem; color: var(--muted); }
/* Checking a new receipt's image: the picture, its tools, the crop slider and the buttons fill exactly one screen, the
   picture taking whatever height is left — so none of them is ever off screen, and nothing moves when text changes. */
.check-bar { display: none; }
.is-checking .check-fit { display: flex; flex-direction: column; gap: .35rem; height: 100dvh; padding-block: .5rem;
  box-sizing: border-box; }
.is-checking .check-fit .viewer { flex: 1 1 0; min-height: 8rem; height: auto; }
.is-checking .viewer-tools { margin-top: 0; }
.is-checking .check-bar { display: block; }
.check-bar .confirm-status { margin: 0; min-height: 2.6em; }   /* two lines kept, so a message never resizes the picture */
.check-bar .actions { margin: 0; }
/* The crop frame (js/viewer.js): everything outside it is dimmed. Only its handles take touches, so the picture still
   pans and zooms underneath. */
.crop-frame { position: absolute; inset: 0; pointer-events: none; border: 2px solid var(--accent);
  box-shadow: 0 0 0 100vmax color-mix(in srgb, var(--bg) 70%, transparent); }
.crop-handle { position: absolute; width: 44px; height: 44px; pointer-events: auto; touch-action: none; cursor: grab;
  display: grid; place-items: center; }
.crop-handle::before { content: ""; background: var(--accent); border: 2px solid var(--surface); border-radius: 999px; }
.crop-left, .crop-right { top: calc(50% - 22px); }
.crop-top, .crop-bottom { left: calc(50% - 22px); }
/* Inside the frame, their grips on its edge: the viewer clips anything outside, and a frame starts at the box's edge. */
.crop-left { left: 0; place-items: center start; }    .crop-right { right: 0; place-items: center end; }
.crop-top { top: 0; place-items: start center; }      .crop-bottom { bottom: 0; place-items: end center; }
.crop-left::before, .crop-right::before { width: 10px; height: 36px; }
.crop-top::before, .crop-bottom::before { width: 36px; height: 10px; }
.crop-left, .crop-right { cursor: ew-resize; }
.crop-top, .crop-bottom { cursor: ns-resize; }
.crop-handle[hidden] { display: none; }   /* touch: a side bar near the screen's side is hidden (js/viewer.js) */
/* One slider, a thumb for each side. Inset on the right and after its label on the left, so no thumb is near the
   screen's side. */
.crop-sides { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: .75rem; margin: 0 4rem 0 1rem; }
.crop-sides[hidden] { display: none; }
.crop-sides-label { font-size: .875rem; color: var(--muted); }
.crop-track { position: relative; height: 44px; }
.crop-track::before, .crop-kept { content: ""; position: absolute; top: 50%; height: 4px; margin-top: -2px; border-radius: 2px; }
.crop-track::before { left: 0; right: 0; background: var(--border); }
.crop-kept { background: var(--accent); }
.crop-thumb { position: absolute; top: 0; width: 44px; height: 44px; margin-left: -22px; display: grid; place-items: center;
  touch-action: none; cursor: grab; }
.crop-thumb::before { content: ""; width: 20px; height: 20px; border-radius: 50%; background: var(--accent);
  border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--border); }
.crop-thumb:focus-visible { outline: 3px solid var(--focus); outline-offset: -8px; border-radius: 50%; }
/* Space kept for the note from the start, so the page never moves when a crop begins. */
.crop-note.is-quiet { visibility: hidden; }
.leave-prompt { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; margin: 0 0 var(--gap);
  padding: .6rem .8rem; background: var(--attention-bg); color: var(--text); border-left: 4px solid var(--attention); border-radius: var(--radius); }
.leave-prompt[hidden] { display: none; }
.crop-handle:focus-visible { outline: 3px solid var(--focus); outline-offset: -6px; border-radius: var(--radius); }
.viewer-tools { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-top: .5rem; }
.viewer-open { margin-left: auto; }
@media (min-width: 48rem) {
  .receipt-layout:has(.receipt-img) { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); align-items: start; }
  .receipt-img { position: sticky; top: 1rem; }
  .viewer { height: 75vh; }
}
@media (max-width: 22rem) { .row { grid-template-columns: 1fr; } }

.confirm { margin-top: var(--gap); padding: 1rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.confirm-note { margin: 0 0 var(--gap); }
.confirm-status { margin: 0 0 var(--gap); font-weight: 600; }
.confirm-status:empty { margin: 0; }
.confirm .actions { margin-top: var(--gap); }
.ocr-details { margin-top: 2rem; }
.ocr-details summary { cursor: pointer; font-weight: 600; }
.ocr-details dl { display: grid; grid-template-columns: auto 1fr; gap: .25rem 1rem; margin: .75rem 0; }
.ocr-details dt { color: var(--muted); }
.ocr-details dd { margin: 0; overflow-wrap: anywhere; }
.ocr-details pre { max-height: 50vh; overflow: auto; font-size: .8rem; padding: .75rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.credits { margin: 0; width: 100%; font-size: .8rem; color: var(--muted); }
.credits.low { color: var(--danger); font-weight: 600; }
.receipt-delete { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--border); }

/* ── admin ────────────────────────────────────────────────────────────────── */
button.danger { border-color: var(--danger); color: var(--danger); }
button.danger.armed { background: var(--danger); color: var(--danger-text); }
.errors { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.errors li { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: .75rem 1rem; min-width: 0; }
.err-head { display: flex; flex-wrap: wrap; gap: .25rem 1rem; margin: 0; font-size: .875rem; }
.badge { font-weight: 600; }
.err-msg { margin: .25rem 0 0; font-weight: 600; overflow-wrap: anywhere; }
.errors pre { margin: .5rem 0 0; font-size: .8rem; overflow-x: auto; white-space: pre; }
.errors summary { cursor: pointer; margin-top: .25rem; }
.shadow-report { margin-top: 2.5rem; }
/* How many whole receipts each reading got right: native <progress> bars in the accent colour, counts beside them. */
.gauge { display: grid; grid-template-columns: max-content 1fr max-content; align-items: center; gap: .35rem .75rem; max-width: 36rem; }
.gauge progress { width: 100%; height: .9rem; accent-color: var(--accent); }
.gauge-count { font-variant-numeric: tabular-nums; color: var(--muted); }
.gauge-state { margin: .5rem 0 var(--gap); font-weight: 600; }
.bar-sm { width: 3.5rem; height: .5rem; vertical-align: middle; accent-color: var(--accent); }
.badge-cause { font-weight: 400; padding: 0 .4rem; border: 1px solid var(--border); border-radius: 999px; font-size: .8125rem; }
.cause-in-text { color: var(--ok-text); background: var(--ok-bg); }
.cause-absent { color: var(--muted); }
.miss-causes { margin: 0 0 .5rem; color: var(--muted); }
.shadow-table { width: 100%; border-collapse: collapse; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.shadow-table th, .shadow-table td { padding: .5rem .75rem; border-bottom: 1px solid var(--border); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.shadow-table th:first-child { text-align: left; }
.shadow-table thead th { font-size: .875rem; color: var(--muted); vertical-align: bottom; }
.shadow-table td .muted { font-size: .8125rem; }
.shadow-speed { padding-left: 1.25rem; }
.shadow-values { display: grid; grid-template-columns: auto 1fr; gap: .15rem 1rem; margin: .5rem 0 0; }
.shadow-values dt { color: var(--muted); }
.shadow-values dd { margin: 0; overflow-wrap: anywhere; }
h3 { font-size: 1rem; margin: 1.5rem 0 .5rem; }

/* ── categories ───────────────────────────────────────────────────────────── */
.cat-list { list-style: none; padding: 0; margin: 0; display: grid; gap: .5rem; max-width: 32rem; }
.cat-list li { display: flex; justify-content: space-between; align-items: center; gap: .75rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: .4rem .5rem .4rem 1rem; }
.cat-list form { margin: 0; }
.cat-list .is-hidden > span:first-child { color: var(--muted); }
.cat-list li > span:first-child { min-width: 0; overflow-wrap: anywhere; }
.cat-actions { display: flex; align-items: center; gap: .35rem; flex: 0 0 auto; }
/* A setting, not a link: on is filled with a tick, off is outlined with an empty circle, so the two differ in shape
   and colour as well as in words. */
.chip { display: inline-flex; align-items: center; gap: .35rem; height: 44px; padding: 0 .75rem; font-size: .875rem;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface); color: var(--muted); }
.chip svg { width: 18px; height: 18px; }
.chip[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
/* A quiet chip for a setting that means "less of this", like a personal line: filled, but in the page's own grey. */
.chip-quiet[aria-pressed="true"] { background: color-mix(in srgb, var(--text) 12%, var(--surface));
  border-color: color-mix(in srgb, var(--text) 25%, var(--surface)); color: var(--text); }
.chip .chip-on, .chip .chip-off { display: inline-flex; align-items: center; }
.chip .chip-on, .chip[aria-pressed="true"] .chip-off { display: none; }
.chip[aria-pressed="true"] .chip-on { display: inline-flex; }
@media (max-width: 30rem) {
  /* A long category name wraps instead; the chip keeps its word, because a bare tick says nothing on its own. */
  .chip { padding: 0 .6rem; }
}
.add-cat { display: flex; flex-wrap: wrap; align-items: end; gap: .5rem 1rem; margin-top: var(--gap); max-width: 32rem; }
.add-cat .field { flex: 1 1 12rem; }

/* ── vehicle log ──────────────────────────────────────────────────────────── */
.vehicle-list li { padding: .6rem 1rem; }
.vehicle-list a { color: var(--text); text-decoration: none; min-width: 0; overflow-wrap: anywhere; }
.vehicle-list a:hover { text-decoration: underline; }
.add-vehicle, .journey-form, .settings-card { max-width: 32rem; }
.add-vehicle { margin-top: 2rem; }
.add-vehicle h2 { font-size: 1.1rem; margin: 0; }
.vehicle-head h1 { overflow-wrap: anywhere; }
@media (max-width: 47.99rem) {
  /* The receipts list hides its heading on phones; a vehicle's heading is its name, so keep it. */
  .vehicle-head h1 { position: static; width: auto; height: auto; overflow: visible; clip-path: none; font-size: 1.25rem; }
}

.totals { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 var(--gap); }
.totals div { flex: 1 1 7rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: .5rem .75rem; }
.totals dt { font-size: .875rem; color: var(--muted); }
.totals dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }

.journeys { width: 100%; border-collapse: collapse; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.journeys th { text-align: left; font-size: .875rem; color: var(--muted); font-weight: 600; }
.journeys th, .journeys td { padding: .5rem .75rem; border-bottom: 1px solid var(--border); vertical-align: top; }
.journeys tbody tr:last-child td { border-bottom: 0; }
.journeys .c-date, .journeys .c-purpose { white-space: nowrap; }
.journeys .c-desc { overflow-wrap: anywhere; min-width: 10rem; }
.journeys .c-driver { overflow-wrap: anywhere; }
.journeys .c-km, .journeys .c-trip { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.journeys .c-trip { font-weight: 600; }
.journeys td.c-actions { white-space: nowrap; }
.card-label { display: none; }
@media (max-width: 47.99rem) {
  .journeys, .journeys tbody { display: block; background: none; border: 0; }
  .journeys thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .journeys tbody { display: grid; gap: .5rem; }
  /* Date and trip on the first line; description; odometer readings; driver and purpose. */
  .journeys tr { display: grid; grid-template-columns: auto 1fr 44px; gap: .15rem .75rem;
    grid-template-areas: "date trip actions" "desc desc actions" "start end actions" "driver purpose actions";
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: .75rem; }
  .journeys td { display: block; padding: 0; border: 0; min-width: 0; text-align: left; }
  .journeys .c-date { grid-area: date; font-weight: 600; }
  .journeys .c-trip { grid-area: trip; text-align: left; }
  .journeys .c-desc { grid-area: desc; min-width: 0; }
  .journeys .c-start { grid-area: start; color: var(--muted); font-size: .875rem; text-align: left; }
  .journeys .c-end { grid-area: end; color: var(--muted); font-size: .875rem; text-align: left; }
  .journeys .c-driver { grid-area: driver; color: var(--muted); font-size: .875rem; }
  .journeys .c-purpose { grid-area: purpose; }
  .journeys td.c-actions { grid-area: actions; display: flex; flex-direction: column; gap: .75rem; }
  .card-label { display: inline; }
}

.km-row { display: flex; gap: .5rem; align-items: stretch; }
.km-row input { flex: 1 1 auto; min-width: 0; font-variant-numeric: tabular-nums; }
.odo-photo { flex: 0 0 auto; display: inline-flex; align-items: center; gap: .35rem; white-space: nowrap; }
.odo-status { margin: .25rem 0 0; font-size: .875rem; }
.odo-status:empty { display: none; }
.km-field .hint { margin: 0 0 .25rem; font-size: .875rem; }
/* Start and End side by side only when the photo buttons fit beside the numbers. */
.journey-form .row { grid-template-columns: 1fr; }
@media (min-width: 40rem) { .journey-form .row { grid-template-columns: 1fr 1fr; } }
.purpose { border: 1px solid var(--border); border-radius: var(--radius); padding: .5rem 1rem .75rem; margin: 0; }
.purpose legend { padding: 0 .25rem; }
/* Filed, but not finished — it wants doing something about, so it reads as a warning rather than a category tag. */
.tag-followup, .tag-bill { color: var(--attention); background: var(--attention-bg); border-color: var(--attention); }
.followup-note { display: block; color: var(--muted); font-size: .875rem; overflow-wrap: anywhere; }
.shadow-account { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin: 0; }
.shadow-account select { min-height: 44px; max-width: 16rem; }
.choice { display: inline-flex; align-items: center; gap: .5rem; min-height: 44px; margin-right: 1.5rem; }
.choice input { width: auto; min-height: 0; margin: 0; }
/* Receipt or bill (receipt.php): the pay-by date only for a bill. */
.bill-kind:not(:has(input[value="bill"]:checked)) .bill-due { display: none; }
.bill-kind .bill-due { margin-top: .25rem; }
.bill-kind legend { padding: 0; }
/* A bill's tag and its Paid chip (receipts list, Home). */
.bill-state { display: inline-flex; flex-wrap: wrap; align-items: center; gap: .25rem .5rem; }
.bill-paid-form { display: inline-flex; margin: 0; }
.bill-note { margin: 0; }
.bill-note.is-error { color: var(--err-text); }
.driver-note { margin: 0; }

.vehicle-settings { margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid var(--border); }
.vehicle-settings h2 { font-size: 1.2rem; }
/* A section folded behind a chevron: its heading is the summary. */
.fold > summary { display: flex; align-items: center; justify-content: space-between; gap: .5rem; min-height: 44px; cursor: pointer; }
.fold > summary h2 { margin: 0; }
.fold[open] > summary { margin-bottom: .75rem; }
.vehicle-settings .hint { max-width: 40rem; }
/* Everything that can log journeys, one row each: a person, a code, the PIN. Wider than the settings lists, because
   a code's row carries its picture and four controls. */
.share-list { max-width: 44rem; }
.share-list .is-hidden { opacity: .65; }
/* A code's row carries a picture, two buttons, a toggle and a bin, which is more than a phone's width: let the row
   wrap so the label keeps a sane column instead of being squeezed to one letter per line. */
.share-list li { flex-wrap: wrap; row-gap: .5rem; }
.share-list li > span:first-child { flex: 1 1 12rem; }
.share-list .cat-actions { flex-wrap: wrap; row-gap: .5rem; }
.share-qr { display: flex; align-items: center; gap: .75rem; min-width: 0; }
.share-qr > span { min-width: 0; overflow-wrap: anywhere; }
.share-toggle { margin: 0; }
.qr-code { background: var(--frame); padding: .35rem; border: 1px solid var(--border); border-radius: var(--radius); }   /* --frame is white in both themes: QR codes need dark on light */
.qr-code svg { display: block; width: 100%; height: auto; }
.qr-thumb { flex: 0 0 4rem; width: 4rem; }
/* The sticker's own label: only on paper, where nothing else says which car it belongs to. */
.qr-label { display: none; }
/* Three ways to add, each folded away: nothing is open until it's wanted, so the resting height is the list. */
.share-add { display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; margin-top: var(--gap); }
.share-add > details { min-width: 14rem; }
.share-add summary { min-height: 44px; display: flex; align-items: center; color: var(--accent); cursor: pointer; }
.share-add .add-cat { margin-top: .5rem; }
.drive-mine { margin-top: 2rem; }
.drive-mine .cat-list li > span { min-width: 0; overflow-wrap: anywhere; }

/* The credit at the foot of every page: quiet, out of the way of the thing above it, clear of the phone's home bar. */
.site-foot { margin: 3rem 0 0; padding: 1rem 1rem calc(1rem + env(safe-area-inset-bottom)); border-top: 1px solid var(--border); color: var(--muted); font-size: .8125rem; text-align: right; }
.site-foot a { color: inherit; }
@media print {
  body * { visibility: hidden; }
  /* A car can have several codes (sql/063), so only the row whose Print was pressed goes on the page, and of that
     row only the picture and the label: the buttons and the dates are for the screen. The thumbnail is a vector, so
     it is the same file at 10cm. */
  .printing, .printing .share-qr, .printing .qr-code, .printing .qr-code *, .printing .qr-label, .printing .qr-label * { visibility: visible; }
  .printing { position: absolute; top: 0; left: 0; display: block; max-width: 10cm; border: 0; padding: 0; }
  .printing .share-qr { display: block; }
  .printing .qr-code { flex: none; width: auto; max-width: none; border: 0; padding: 0; }
  .printing .qr-label { display: block; margin-top: .5rem; text-align: center; }
  .printing .cat-actions, .printing .share-toggle { display: none; }
}
.logbook-export { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem 1rem; margin: 0 0 var(--gap); }
.logbook-export .field { flex: 0 1 12rem; }
.totals-note { margin: 0 0 .25rem; font-size: .875rem; }
.vehicle-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1.25rem; }
.notice { margin: 0 0 .5rem; padding: .6rem .8rem; background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--accent); border-radius: var(--radius); }
.notice a { white-space: nowrap; }
/* Export everything (export-all.php): each part's tick box with what it adds under it, and the build's progress bar. */
.export-part .hint { margin: -.5rem 0 0 1.75rem; }
.export-status p { margin: 0 0 .5rem; }
.export-status progress { width: 100%; height: .9rem; accent-color: var(--accent); }
.export-status .actions { margin-top: .75rem; }

/* Settings */
.settings-section { margin: 0 0 calc(var(--gap) * 2); max-width: 40rem; }
.member { margin: 0 0 var(--gap); padding: 1rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.member h3 { margin: 0 0 .5rem; font-size: 1rem; }
.member-email { margin: 0 0 .5rem; font-weight: 600; overflow-wrap: anywhere; }
.access-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: .5rem 1rem; }
.access-grid .field { margin: 0; }
.member .actions { margin-top: var(--gap); }
.account-switch { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin: 0 0 var(--gap); }
.account-switch-label { color: var(--muted); font-size: .875rem; }
/* Many accounts (an accountant's clients): folded; open, a search box over a list that scrolls in place. */
.account-finder { margin: 0 0 var(--gap); }
.account-finder > summary { cursor: pointer; display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; }
.account-finder-change { color: var(--accent); text-decoration: underline; font-size: .875rem; }
.account-finder-form { margin-top: .5rem; max-width: 28rem; }
.account-finder-list { list-style: none; margin: .25rem 0 0; padding: 0; max-height: 20rem; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.account-finder-list li + li { border-top: 1px solid var(--border); }
.account-finder-item { display: block; width: 100%; text-align: left; padding: .6rem .75rem; min-height: 44px;
  background: none; border: 0; border-radius: 0; color: var(--text); font: inherit; }
.account-finder-item:hover { background: var(--bg); }
.account-finder-item[aria-current="true"] { font-weight: 600; }

/* ── bank ─────────────────────────────────────────────────────────────────── */
/* One financial year at a time. Buttons in a group, not ARIA tabs: each one reloads the list it names. */
.fy-tabs { display: flex; flex-wrap: wrap; gap: .35rem; margin: 0 0 var(--gap); }
.fy-tab { min-height: 44px; padding: .5rem .9rem; color: var(--text); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius); }
.fy-tab[aria-current="true"], .fy-tab[aria-current="page"] { color: var(--accent-text); background: var(--accent); border-color: var(--accent); font-weight: 600; }
/* The classify list: one line per transaction, its category beside it, and a fee tucked under its purchase. */
.txn-list { --row-y: calc(.65rem * var(--density)); list-style: none; margin: 0; padding: 0; display: grid; gap: calc(.5rem * var(--density)); }
[data-density="compact"] { --density: .6; }
[data-density="relaxed"] { --density: 1.6; }
.density { display: none; align-items: center; gap: .35rem; }
.js .density { display: inline-flex; }
.density .hint { margin: 0 0 0 .25rem; }
.txn { position: relative; padding: var(--row-y) calc(.85rem * var(--density)); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
/* "Still to classify" is a short mark beside the first line, not a bar down the whole row: a tall row — one with a
   rewritten description, or with a chooser open inside it — would otherwise carry a long stripe of colour. */
.txn-todo::before { content: ""; position: absolute; left: 0; top: calc(var(--row-y) + .05rem); width: 3px; height: 1.4em;
  background: var(--accent); border-radius: 0 3px 3px 0; }
.txn-fee { margin-left: 1.5rem; border-style: dashed; }
/* Every other purchase carries a faint tint, so a long list doesn't blur into one block. A fee wears its purchase's
   tint (bank.php sets the class), which is what keeps the pair looking like a pair. */
.txn-band { background: color-mix(in srgb, var(--text) 4%, var(--surface)); }
/* Personal spending that landed in a business account: still here, still readable, plainly not part of the total. */
.txn-personal { border-style: dotted; }
.txn-personal .txn-main { opacity: .6; }
/* Sits in the row beside the two selects: the same 44px target they have, with less padding around the word. */
.txn-personal-toggle { padding: 0 .6rem; }
.txn-main { display: flex; align-items: baseline; gap: .75rem; }
.txn-what { flex: 1 1 0; min-width: 0; }
.txn-details { display: block; overflow-wrap: anywhere; }
/* Date, merchant and the bank's own words share one quiet line under the description. */
.txn-meta { display: block; margin-top: .2rem; font-size: .875rem; line-height: 1.4; color: var(--muted); overflow-wrap: anywhere; }
.txn-meta > * + *::before { content: " · "; font-weight: 400; color: var(--muted); }
.txn-merchant { font-weight: 600; color: var(--text); }
.txn-tag { display: inline-block; margin-right: .35rem; padding: 0 .35rem; font-size: .75rem;
  color: var(--muted); border: 1px solid var(--border); border-radius: var(--radius); }
.txn-amount { flex: 0 0 auto; margin-left: auto; font-variant-numeric: tabular-nums; }
.txn-in { color: var(--ok-text); }
/* Everything under a transaction — links, the merchant button, the bank's own category — reads at one size. */
.txn-classify { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; margin-top: calc(.6rem * var(--density)); font-size: .875rem; }
.txn-classify form { margin: 0; }
.txn-category, .txn-job { font-size: 1rem; min-height: 44px; max-width: 16rem; }
/* Without JavaScript an unfilled category select has one choice; its row's link (bank.php) opens the full list. */
html:not(.js) .txn-category[data-fill] { display: none; }
/* Its width mustn't change as its list is filled in (js/bank.js), or it moves under the pointer. */
.txn-category { width: 16rem; }
.txn-bank-category, .txn-note { color: var(--muted); font-size: .875rem; }
.txn-note.is-error { color: var(--err-text); }
.txn-describe { width: 100%; max-width: 32rem; min-height: 44px; }
form.txn-describe { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; }
form.txn-describe input { flex: 1 1 14rem; min-height: 44px; }
/* The description is the control: ordinary text until you point at it, then it offers itself for editing. */
a.txn-details { color: inherit; text-decoration: none; }
a.txn-details:hover, a.txn-details:focus-visible { text-decoration: underline dotted; text-underline-offset: 3px; }
/* Choosing a receipt for one transaction: the likely ones first, then everything else. The panel is a card like the
   dashboard's, rather than a slab of plain page with an accent bar down it. */
.link-panel { margin-bottom: var(--gap); padding: .75rem 1rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.link-panel h2 { margin: 0 0 .25rem; font-size: 1.1rem; }
.link-panel h3 { margin: var(--gap) 0 .25rem; font-size: 1rem; }
.link-panel .hint { margin: 0 0 .35rem; }
.link-list li { gap: .75rem; }
.link-list .is-taken { color: var(--muted); }
.list-actions { margin: 0; }
/* The chooser opens inside the row it belongs to, so the list never jumps. A small notch on its top edge points back
   at the line it came from — a speech bubble's tail rather than a slab of colour down the side. */
.txn-chooser { margin: .75rem 0 .25rem; padding-left: .75rem; }
.txn-chooser .link-panel { position: relative; margin: 0; }
.txn-chooser .link-panel::before { content: ""; position: absolute; top: -6px; left: 1.25rem; width: 10px; height: 10px;
  background: var(--surface); border-top: 1px solid var(--border); border-left: 1px solid var(--border); transform: rotate(45deg); }
.txn.is-open { background: var(--bg); }
@media (prefers-reduced-motion: no-preference) {
  .txn-chooser { animation: chooser-in .12s ease-out; }
  @keyframes chooser-in { from { opacity: 0; transform: translateY(-4px); } }
}
.txn-chooser .field { margin: .35rem 0; max-width: 20rem; }
.job-closed summary { cursor: pointer; margin-top: .5rem; font-weight: 600; }
.job-picker-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; margin: var(--gap) 0 0; }
.job-picker-actions form { margin: 0; }
/* Import: one row per field, so nothing is mapped silently. */
.map-table { width: 100%; border-collapse: collapse; margin: var(--gap) 0; }
.map-table th, .map-table td { padding: .35rem .5rem; text-align: left; vertical-align: top; border-bottom: 1px solid var(--border); }
.map-table select { min-height: 44px; max-width: 18rem; }
.map-table .hint { margin: .15rem 0 0; }
.map-preview { color: var(--muted); font-size: .875rem; overflow-wrap: anywhere; }
.map-table .required { font-weight: 400; color: var(--muted); }
/* Which account each part of the file goes to: the file's own name for it, then the name it's filed under here. */
.map-accounts { margin: var(--gap) 0; }
.map-account { padding: .5rem 0; border-bottom: 1px solid var(--border); }
.map-account:last-child { border-bottom: 0; }
.map-account .field { margin: .35rem 0 0; max-width: 22rem; }
.map-account .hint { margin: .25rem 0 0; }
.map-account-value { margin: 0; }
.import-choose, .import-map { max-width: 54rem; }
@media (max-width: 35.99rem) {
  .map-table, .map-table tbody, .map-table tr, .map-table th, .map-table td { display: block; border: 0; }
  .map-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .map-table tr { padding: .5rem 0; border-bottom: 1px solid var(--border); }
  .txn-fee { margin-left: .75rem; }
  /* The category box takes what's left of the line, so an attached tag fits beside it rather than under it. */
  .txn-classify > .txn-form:first-of-type { flex: 1 1 9rem; }
  .txn-classify .txn-category { width: 100%; max-width: none; }
}

/* ── jobs ─────────────────────────────────────────────────────────────────── */
@media (max-width: 47.99rem) {
  .jobs-head h1 { position: static; width: auto; height: auto; overflow: visible; clip-path: none; font-size: 1.25rem; }
}
.back { margin: 0 0 .5rem; font-size: .9375rem; }
.job-list li, .job-receipts li { flex-wrap: wrap; gap: .15rem 1rem; padding: .6rem 1rem; }
.job-list a, .job-receipts a { color: var(--text); min-width: 0; overflow-wrap: anywhere; }
.job-meta { color: var(--muted); font-size: .875rem; }
.overdue { color: var(--attention); }
.job-receipts span { font-variant-numeric: tabular-nums; white-space: nowrap; }
.jobs-done { margin-top: 1.5rem; }
.jobs-done summary { cursor: pointer; font-weight: 600; margin-bottom: .5rem; min-height: 44px; display: flex; align-items: center; }
.job-form { max-width: 40rem; }
.job-section { margin-top: 2rem; max-width: 60rem; }
.job-spent { font-weight: 600; margin: 0 0 .5rem; }
.thumbs { list-style: none; margin: 0 0 var(--gap); padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: .75rem; }
.thumbs li { position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.thumbs img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: var(--frame); }
.thumbs form { position: absolute; top: .35rem; right: .35rem; margin: 0; }
.thumb-link { display: block; color: var(--text); text-decoration: none; }
.thumb-link span { display: block; padding: .4rem .6rem; font-size: .875rem; font-weight: 600; overflow-wrap: anywhere; }
.thumb-link:hover span { text-decoration: underline; }

/* ── drawing ──────────────────────────────────────────────────────────────── */
.drawing-head { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem 1rem; }
.drawing-head .field { flex: 1 1 14rem; max-width: 30rem; }
/* The status line always holds one line, so "Saving…" coming and going never pushes the drawing area. */
.drawing .draw-status, .drawing .draw-status:empty { margin: .5rem 0; min-height: 1.5em; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.draw-tools { position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
  padding: .5rem; margin-bottom: .5rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.tool-group { display: flex; align-items: center; gap: .35rem; margin: 0; }
.tool-width label { min-width: 6.5em; margin: 0; font-weight: 600; font-size: .875rem; white-space: nowrap; }
.tool-width input, .range-field input[type="range"] { width: 8rem; min-height: 44px; padding: 0; border: 0; background: none; accent-color: var(--accent); }
.tool-width output, .range-field output { min-width: 2.5rem; font-variant-numeric: tabular-nums; font-size: .875rem; }
.swatches { border: 0; padding: 0; }
.swatch { position: relative; display: inline-flex; width: 44px; height: 44px; margin: 0; align-items: center; justify-content: center; cursor: pointer; }
.swatch input[type="radio"] { position: absolute; inset: 0; opacity: 0; margin: 0; min-height: 0; cursor: pointer; }
.swatch span { width: 28px; height: 28px; border-radius: 50%; background: var(--swatch); border: 2px solid var(--muted); }
.swatch input:checked + span { outline: 3px solid var(--accent); outline-offset: 2px; }
.swatch input:focus-visible + span { outline: 3px solid var(--focus); outline-offset: 4px; }
.swatch-pick input { position: absolute; inset: 0; width: 100%; height: 100%; min-height: 0; margin: 0; padding: 0; border: 0; opacity: 0; cursor: pointer; }
.swatch-pick span { display: inline-flex; align-items: center; justify-content: center; background: var(--surface); border-style: dashed; color: var(--text); }
.swatch-pick span .icon { width: 16px; height: 16px; }
.swatch-pick input:focus-visible + span { outline: 3px solid var(--focus); outline-offset: 4px; }
.draw-box { position: relative; contain: layout paint; height: 70vh; height: 70svh; min-height: 18rem; overflow: hidden; touch-action: none; user-select: none;
  -webkit-user-select: none; -webkit-touch-callout: none; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); cursor: crosshair; }
.draw-box canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.draw-background { margin-top: var(--gap); max-width: 32rem; padding: .75rem 1rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.draw-background summary { cursor: pointer; font-weight: 600; min-height: 44px; display: flex; align-items: center; }
.draw-background .stack { margin-top: .5rem; }
.range-field { display: flex; flex-wrap: wrap; align-items: center; gap: .25rem 1rem; }
.range-field label { flex: 0 0 8rem; margin: 0; }
.range-field input[type="color"] { width: 44px; height: 44px; padding: 2px; }
.link-label { display: inline; font-weight: 400; margin: 0; color: var(--accent); text-decoration: underline; cursor: pointer; }
.link-label:focus-within { outline: 3px solid var(--focus); outline-offset: 2px; }
/* No pull-to-refresh or rubber-band scrolling while drawing. */
:root:has(#draw-box) { overscroll-behavior: none; }
.draw-box { overscroll-behavior: contain; }
.finger-toggle { min-height: 44px; }
.perf { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-top: var(--gap); }
.perf-out { width: 100%; margin: 0; font-size: .875rem; font-variant-numeric: tabular-nums; }
.perf-feel { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; width: 100%; margin: 0; font-size: .875rem; }
.perf-feel input[type=range] { flex: 1 1 8rem; }
/* Drawing page: taps on controls never double-tap-zoom the page; the drawing area handles its own touches. */
:root:has(#draw-box) { touch-action: manipulation; }
.draw-tools { contain: layout; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.fullscreen-toggle { min-height: 44px; }
.fullscreen-exit, .draw-problem { display: none; }
/* Full screen: just the tools along the top and the drawing area filling the rest. The top-left corner is kept free for
   an [X]: iPad Safari's own (pages can't hide it), or ours everywhere else. A save problem overlays the drawing's top. */
.drawing.is-fullscreen { position: fixed; inset: 0; z-index: 20; display: grid; grid-template-rows: auto minmax(0, 1fr); gap: .35rem;
  padding: max(.35rem, env(safe-area-inset-top)) max(.35rem, env(safe-area-inset-right)) max(.35rem, env(safe-area-inset-bottom)) max(.35rem, env(safe-area-inset-left));
  background: var(--bg); touch-action: none; }
.drawing.is-fullscreen > :not(.draw-tools, .draw-box, .draw-problem, .fullscreen-exit, #draft-offer) { display: none; }
.drawing.is-fullscreen .draw-tools { grid-row: 1; position: static; margin: 0 0 0 3.5rem; padding: .25rem .5rem; gap: .25rem .75rem; touch-action: manipulation; }
.drawing.is-fullscreen .fullscreen-toggle, .drawing.is-fullscreen .finger-toggle { display: none; }
.drawing.is-fullscreen .fullscreen-exit { display: inline-flex; position: absolute; z-index: 1;
  top: max(.35rem, env(safe-area-inset-top)); left: max(.35rem, env(safe-area-inset-left)); touch-action: manipulation; }
.drawing.is-fullscreen.browser-x .fullscreen-exit { display: none; }
.drawing.is-fullscreen .draw-box { grid-row: 2; height: auto; min-height: 0; }
.drawing.is-fullscreen #draft-offer { grid-row: 2; align-self: start; z-index: 1; margin: .5rem; }
.drawing.is-fullscreen .draw-problem:not(:empty) { display: block; grid-row: 2; align-self: start; justify-self: center; z-index: 1; margin: .5rem;
  padding: .25rem .75rem; font-size: .875rem; background: var(--err-bg); color: var(--err-text); border-radius: var(--radius); pointer-events: none; }
.drawing:fullscreen { background: var(--bg); }
:root.drawing-fullscreen { overflow: hidden; }
.drawing button, .drawing input, .drawing label, .drawing select, .drawing summary, .drawing a { touch-action: manipulation; }
/* Job page: details and pictures/drawings/expenses side by side on wide screens and landscape tablets. */
.job-layout { display: grid; gap: 0 2rem; }
.job-layout .job-form { max-width: none; }
.job-layout:not(:has(.job-side)) .job-details { max-width: 40rem; }
/* House rule: a field holding something short — a date, a percentage, an amount, a count, a code, a short list — shares
   a row with its neighbours instead of taking a line of its own. auto-fit means it collapses to one column on a phone,
   so it costs nothing at 400px. Used everywhere, not just the job page. */
.field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); gap: var(--gap); }
/* Fields whose choices need more room than 9.5rem: side by side only when each gets 15rem. */
.pair-row { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
/* Sales settings is one long form: as wide as the other settings sections, so fields don't stretch across a desktop. */
.invoice-settings { max-width: 40rem; }
.layout-examples { display: flex; flex-wrap: wrap; gap: .5rem; margin: .25rem 0 var(--gap); }
/* A dropdown on its own line is as wide as one of a pair: short choices don't need the whole form's width. */
.invoice-settings fieldset.plain > .field:has(> select) { max-width: calc(20rem - var(--gap) / 2); }
.job-details .receipt-delete { margin-top: 1.5rem; }
.job-side .job-section:first-child { margin-top: 2rem; }
.job-section h2 { margin: 0 0 .5rem; }
@media (min-width: 60rem) {
  .job-layout:has(.job-side) { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); align-items: start; }
  .job-side .job-section:first-child { margin-top: 0; }
  .job-side .job-section { margin-top: 1.5rem; }
  .job-side .thumbs { grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr)); gap: .5rem; }
}
/* A newer version of the site is available while this page has unsaved changes (js/update.js). */
.update-banner { position: fixed; inset: auto 0 0 0; z-index: 30; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: .25rem 1rem;
  padding: .5rem 1rem max(.5rem, env(safe-area-inset-bottom)); background: var(--surface); color: var(--text); border-top: 2px solid var(--accent); }
.update-banner button { min-height: 40px; }
/* Drawing toolbar rows (drawing.php): actions, then ink. On wide screens both sit on one line when they fit and wrap as
   whole rows; the buttons keep the same order everywhere. */
.tool-row { display: flex; flex-wrap: nowrap; align-items: center; gap: .5rem 1rem; min-width: 0; }
.tool-row-ink { flex-wrap: wrap; }
.tool-group { flex: 0 0 auto; }
.tool-labelled { flex: 0 0 auto; min-height: 44px; }
.finger-toggle[aria-pressed="true"] { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
#draw-feel[aria-expanded="true"] { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
/* Pen feel: how hard pressure is answered, set by the user and kept on their device. Wraps rather than scrolls,
   since the labels are sentences. */
.pen-feel { flex-wrap: wrap; gap: .35rem 1rem; padding-top: .35rem; border-top: 1px solid var(--border); }
.pen-feel label { margin: 0; font-weight: 600; font-size: .875rem; }
.pen-feel input[type="range"] { width: 7rem; min-height: 44px; padding: 0; border: 0; background: none; accent-color: var(--accent); }
.pen-feel output { min-width: 4.5rem; font-size: .875rem; }
.pen-feel .hint { flex: 1 0 100%; margin: 0; }
.pen-feel .tool-group { flex: 1 1 18rem; flex-wrap: wrap; }
.pen-feel .tool-group input[type="range"] { flex: 1 1 6rem; width: auto; }
/* iPad Safari's own full-screen [X] is larger than ours: leave it more room. */
.drawing.is-fullscreen.browser-x .draw-tools { margin-left: calc(5rem + env(safe-area-inset-left)); }
@media (max-width: 35.99rem) {
  /* Phones are mostly for looking at a drawing and small touch-ups: compact rows that never wrap inside themselves,
     scrolling sideways on the narrowest screens rather than growing taller. */
  .draw-tools { flex-direction: column; align-items: stretch; gap: .35rem; padding: .35rem; }
  .tool-row { gap: .25rem .5rem; overflow-x: auto; scrollbar-width: none; }
  .tool-row-actions { justify-content: space-between; }
  .tool-group { gap: .15rem; }
  .draw-tools .icon-btn, .draw-tools .tool-labelled { width: 36px; height: 36px; min-height: 36px; padding: 0; justify-content: center; }
  .draw-tools .tool-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  /* Sentences and sliders wrap onto their own lines here rather than scrolling sideways with the tool rows. */
  .pen-feel { overflow-x: visible; }
  .pen-feel .tool-group { flex: 1 0 100%; }
  .pen-feel label { flex: 1 0 100%; }
  .draw-tools .swatch { width: 36px; height: 36px; }
  .draw-tools .swatch span { width: 24px; height: 24px; }
  .tool-width { flex: 1 1 auto; }
  .tool-width label { min-width: 0; }
  .tool-width input { flex: 1 1 auto; width: auto; min-width: 5rem; }
  /* Normal layout: the drawing area is the sheet's shape, so the whole drawing shows with no empty bands. */
  .drawing:not(.is-fullscreen) .draw-box { height: auto; min-height: 0; aspect-ratio: 4 / 3; }
  .drawing.is-fullscreen .draw-tools { margin-left: 3rem; }
  .drawing.is-fullscreen .fullscreen-exit { width: 36px; height: 36px; }
}
.photo-add:not(.js *) { display: none; }   /* adding pictures needs JavaScript (js/job.js) */

/* ── dashboard (index.php) ───────────────────────────────────────────────── */
.dash-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 var(--gap); }
.dash-actions .button { flex: 1 1 10rem; justify-content: center; min-height: 52px; font-weight: 600; }
.dash { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); align-items: start; }
.dash-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: .75rem 1rem 1rem; min-width: 0; }
.dash-card h2 { margin: 0 0 .5rem; font-size: 1.1rem; }
.dash-list { max-width: none; }
.dash-list li { flex-wrap: wrap; gap: .15rem 1rem; padding: .5rem .75rem; }
.dash-list a { color: var(--text); min-width: 0; overflow-wrap: anywhere; }
.dash-amount { font-variant-numeric: tabular-nums; white-space: nowrap; }
.vehicle-summary li { display: grid; justify-content: stretch; }
.vehicle-summary .dash-finish { color: var(--accent); font-size: .875rem; }
.dash-more { margin: .5rem 0 0; font-size: .9375rem; }
.dash .empty { margin: 0; }
/* Working from home card (index.php): what's left to confirm, or setting the usual week first. */
.dash-card > p { margin: 0 0 .5rem; }
.dash-card > p:last-child { margin-bottom: 0; }
.dash-wfh-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; }
.dash-done { display: flex; align-items: center; gap: .4rem; }
.dash-done svg { flex: 0 0 auto; color: var(--ok-text); }
/* Delete confirmation beside an armed icon button (js/arm.js). Floats over the page, so nothing moves. */
.arm-bubble { position: fixed; z-index: 40; max-width: min(22rem, calc(100vw - 16px)); padding: .5rem .75rem; font-size: .9rem; line-height: 1.35;
  background: var(--surface); color: var(--text); border: 2px solid var(--danger); border-radius: var(--radius); pointer-events: none; }
/* Layered drawing thumbnail (job page): paper, background picture, grid, ink. Settings come from js/job.js. */
.drawing-thumb { position: relative; display: block; aspect-ratio: 4 / 3; background: var(--frame); overflow: hidden; }
.drawing-thumb > * { position: absolute; inset: 0; width: 100%; height: 100%; }
.thumbs .drawing-thumb img { aspect-ratio: auto; background: none; object-fit: contain; }
.drawing-thumb .dt-bg { opacity: var(--bg-opacity, 1); }
.drawing-thumb .dt-grid { background-image: linear-gradient(to right, var(--grid-line, transparent) 1px, transparent 1px),
  linear-gradient(to bottom, var(--grid-line, transparent) 1px, transparent 1px); background-size: var(--grid-x, 10%) var(--grid-y, 10%); }

/* ── assets (assets.php, asset.php, job.php "Assets used") ────────────────── */
.asset-search { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem; margin: 0 0 .5rem; max-width: 40rem; }
.asset-search .field { flex: 1 1 14rem; margin: 0; }
.asset-search button, .code-row button { min-height: 44px; }
.code-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem; }
.code-row .field { flex: 1 1 12rem; }
.form-section { font-size: 1.1rem; margin: 1.5rem 0 .25rem; }
.asset-photos { list-style: none; margin: 0 0 var(--gap); padding: 0; display: grid; gap: .75rem; }
.asset-photos li { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr) auto; gap: .5rem .75rem; align-items: start;
  padding: .5rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.asset-photos img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: var(--frame); border-radius: calc(var(--radius) - 2px); }
.photo-meta { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem; margin: 0; min-width: 0; }
.photo-meta .field { flex: 1 1 9rem; margin: 0; min-width: 0; }
.photo-meta button { min-height: 44px; }
.photo-delete { margin: 0; }
.photo-caption { margin: 0; overflow-wrap: anywhere; }
@media (max-width: 30rem) {
  .asset-photos li { grid-template-columns: 5.5rem minmax(0, 1fr) auto; }
}
.asset-add { display: grid; gap: .5rem; max-width: 40rem; margin-top: var(--gap); }
.asset-add .field, .asset-add .actions { margin: 0; }
/* The camera viewfinder (js/scan.js): floats above the page, capped so Close stays on screen. */
.scan-panel { position: fixed; z-index: 35; inset: auto 8px max(8px, env(safe-area-inset-bottom)) 8px; max-width: 30rem; margin: 0 auto;
  background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius); padding: .5rem;
  box-shadow: 0 4px 24px rgb(0 0 0 / .35); }
.scan-panel-bar { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin-bottom: .5rem; font-weight: 600; }
.scan-panel-bar button { min-height: 44px; }
.scan-reader { max-height: 40vh; overflow: hidden; border-radius: calc(var(--radius) - 2px); background: var(--frame); }
.scan-reader video { display: block; width: 100%; max-height: 40vh; object-fit: cover; }

/* ── about ────────────────────────────────────────────────────────────────── */
.about { max-width: 64rem; }
.about-lede { font-size: 1.15rem; line-height: 1.5; border-left: 4px solid var(--border); padding-left: .9rem; margin: 0 0 1rem; }
.about p { max-width: 44rem; }
.about-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr)); gap: .9rem; margin: .5rem 0 1.5rem; }
.about-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: .9rem 1rem; }
.about-card h3 { margin: 0 0 .5rem; font-size: 1.05rem; }
.about-card ul, .about-points { margin: 0; padding-left: 1.1rem; }
.about-card li, .about-points li { margin-bottom: .35rem; }
.about-points { max-width: 44rem; }
/* Tax page: marking each BAS period lodged, one line per period with its form folded under it. */
.bas-lodging { list-style: none; margin: 0; padding: 0; max-width: 48rem; }
.bas-lodging > li { padding: .5rem 0; border-top: 1px solid var(--border); }
.bas-lodge > summary { cursor: pointer; color: var(--accent); min-height: 44px; display: inline-flex; align-items: center; }
.bas-lodge form { margin: .25rem 0 .5rem; }
.bas-lodge-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr)); gap: .25rem .75rem; }
.bas-lodge-grid input { width: 100%; min-height: 44px; }
/* GST credits without a receipt: each "Find receipt" a full touch target on its own line. */
#credits .bas-lodging a.link { display: flex; align-items: center; min-height: 44px; }
#credits details > summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; }
.about-signin { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; margin-top: 1.5rem; }
/* Two tabs (js/about.js). Without JavaScript there are no tabs: both panels show in turn, each under its heading. */
.about-tabs { display: flex; flex-wrap: wrap; gap: .35rem; margin: 1.25rem 0 1rem; }
:root:not(.js) .about-tabs { display: none; }
.about-tabs [aria-selected="true"] { color: var(--accent-text); background: var(--accent); border-color: var(--accent); font-weight: 600; }
.js .about-panel-heading { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.about-panel { margin-bottom: 1.5rem; }
.about-panel:focus-visible { outline: 3px solid var(--focus); outline-offset: 4px; border-radius: var(--radius); }
.about-card p { margin: 0; }
.about .how > .about-cards { margin-top: .5rem; }

/* ── invoices (invoices.php, invoice.php, invoice-settings.php, contacts) ──── */
a.fy-tab { display: inline-flex; align-items: center; text-decoration: none; }
.invoice-links { margin: -.5rem 0 var(--gap); }
.invoice-list li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .15rem 1rem; }
.invoice-list .job-meta { grid-column: 1; }
.invoice-money { grid-column: 2; grid-row: 1 / span 2; align-self: center; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.invoice-money .tag { margin-left: .35rem; }
.tag-overdue { color: var(--attention); background: var(--attention-bg); border-color: var(--attention); }
.tag-paid { color: var(--ok-text); background: var(--ok-bg); border-color: var(--ok-text); }
.tag-void, .tag-draft { color: var(--muted); }
h1 .tag { font-size: .875rem; font-weight: 600; vertical-align: middle; }
.invoice-form, .invoice-view { max-width: 56rem; }
.invoice-section + .invoice-section { margin-top: 1.25rem; }
.invoice-lines { display: grid; gap: .5rem; }
.invoice-line { display: grid; grid-template-columns: minmax(0, 1fr) 7rem 5rem 7.5rem auto 6.5rem auto; gap: .35rem .5rem; align-items: end;
  padding: .5rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.invoice-line .field { margin: 0; min-width: 0; }
.invoice-line label { font-size: .8rem; }
.invoice-line .choice { margin: 0; min-height: 44px; }
.line-amount { min-height: 44px; display: flex; align-items: center; justify-content: flex-end; font-variant-numeric: tabular-nums; font-weight: 600; }
@media (max-width: 47.99rem) {
  .invoice-line { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .invoice-line .line-description { grid-column: 1 / -1; }
  .invoice-line .line-sku { grid-column: 1 / span 3; }
  .invoice-line .line-qty { grid-column: 4 / span 3; }
  .invoice-line .line-unit_price { grid-column: 1 / span 3; }
  .invoice-line .line-taxable { grid-column: 4 / span 3; }
  .invoice-line .line-amount { grid-column: 1 / span 5; justify-content: flex-start; }
  .invoice-line .line-remove { grid-column: 6; justify-self: end; }
}
.invoice-totals { margin: .75rem 0 0 auto; max-width: 20rem; display: grid; gap: .2rem; }
.invoice-totals div { display: flex; justify-content: space-between; gap: 1rem; }
.invoice-totals dt { color: var(--muted); }
.invoice-totals dd { margin: 0; font-variant-numeric: tabular-nums; }
.invoice-totals .grand { border-top: 1px solid var(--border); padding-top: .3rem; font-weight: 700; }
.invoice-totals .grand dt { color: var(--text); }
.invoice-actions { margin-top: 1.25rem; flex-wrap: wrap; }
.invoice-actions form { margin: 0; }
.invoice-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: .5rem 1rem; margin: 0 0 .75rem; }
.invoice-facts dt { color: var(--muted); font-size: .875rem; }
.invoice-facts dd { margin: 0; font-weight: 600; }
/* Quotes (quote.php): states, the customer's signature pad, and a deposit percentage beside its choice. */
.tag-quote-accepted { color: var(--ok-text); background: var(--ok-bg); border-color: var(--ok-text); }
.tag-quote-expired { color: var(--attention); background: var(--attention-bg); border-color: var(--attention); }
.tag-quote-declined, .tag-quote-draft { color: var(--muted); }
.signature-field .label { margin: 0 0 .25rem; font-weight: 600; }
.signature-pad { display: block; width: 100%; max-width: 36rem; aspect-ratio: 3 / 1; height: auto; border: 1px solid var(--border); border-radius: var(--radius); touch-action: none; }
.quote-signature { display: block; max-width: min(100%, 20rem); margin: 0 0 .75rem; border: 1px solid var(--border); border-radius: var(--radius); }
.choice-with-field { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.choice-with-field .choice { margin-right: 0; }
.percent-input { width: 4.5rem; }
/* A quote's sketches and photos (quote.php): tick boxes with thumbnails, wrapping in a grid. */
.quote-pictures { list-style: none; margin: 0 0 .5rem; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: .75rem; }
.quote-pictures label, .quote-pictures a { display: grid; gap: .35rem; cursor: pointer; font-size: .875rem; font-weight: 400; color: var(--text); }
.quote-pictures .pic-caption { display: flex; align-items: flex-start; gap: .4rem; }
.quote-pictures .pic-caption input { width: auto; min-height: 0; margin: .15rem 0 0; flex: 0 0 auto; }
.quote-pictures img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.quote-picture-add { display: flex; flex-wrap: wrap; align-items: center; gap: .25rem 1rem; }
/* Job requests (jobs.php, job.php): the group headings, where a request stands, and the customer's details. */
.jobs-group { margin: 1.25rem 0 .5rem; font-size: 1.1rem; }
.tag-stage-request, .tag-stage-quoted { color: var(--attention); background: var(--attention-bg); border-color: var(--attention); }
.tag-stage-declined { color: var(--muted); }
.request-stage { margin: 0 0 var(--gap); padding: .75rem 1rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.request-stage p { margin: 0 0 .5rem; }
.request-stage .actions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; margin: 0; }
.job-customer summary { cursor: pointer; font-weight: 600; min-height: 44px; display: flex; align-items: center; }
.job-customer[open] summary { margin-bottom: .5rem; }
/* The steps of a request (job.php): numbered, because they're an order; the current one filled in and bold. Links
   are told apart by the accent colour and the numbered circle rather than an underline. */
.job-steps ol { list-style: none; margin: 0 0 var(--gap); padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 0 .75rem; counter-reset: step; font-size: .9375rem; }
.job-steps li { counter-increment: step; display: flex; align-items: center; }
.job-steps a, .job-steps button.link, .job-steps span { display: inline-flex; align-items: center; gap: 0; min-height: 44px; padding: 0; text-decoration: none; }
.job-steps a:hover, .job-steps button.link:hover { text-decoration: underline; }
.job-steps a::before, .job-steps button.link::before, .job-steps span::before { content: counter(step); display: inline-grid; place-items: center;
  width: 1.35rem; height: 1.35rem; margin-right: .35rem; border: 1px solid currentColor; border-radius: 50%; font-size: .75rem; font-weight: 600; }
.job-steps [aria-current="step"] { font-weight: 700; color: var(--text); }
.job-steps [aria-current="step"]::before { color: var(--accent-text); background: var(--accent); border-color: var(--accent); }
.job-next { margin: 1.5rem 0 0; }
.inline-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem; }
.inline-form .field { flex: 1 1 14rem; margin: 0; }
.inline-form button { min-height: 44px; }
.logo-preview { display: block; max-width: 12rem; max-height: 6rem; object-fit: contain; margin: .25rem 0 .5rem; background: var(--frame); border: 1px solid var(--border); border-radius: var(--radius); padding: .25rem; }

/* ── tax ──────────────────────────────────────────────────────────────────── */
.tax-basis { margin: 0 0 var(--gap); }
.tax-section { margin: 0 0 2rem; max-width: 60rem; }
/* Nothing left to do ("All sorted", "Nothing needs attention"): bordered like the weekly win, so good news reads as its own thing. */
.ok-note { padding: .6rem .8rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--ok-bg); color: var(--ok-text); }
.ok-note { border-color: color-mix(in srgb, var(--ok-text) 35%, transparent); }
/* Work still to do: what it is, and the one button that goes and does it. */
.tax-attention { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.tax-attention li { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem 1rem;
  padding: .6rem .8rem; background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--attention); border-radius: var(--radius); }
.tax-attention li > span { flex: 1 1 16rem; min-width: 0; overflow-wrap: anywhere; }
.tax-attention form { margin: 0; }
/* Money lines up: every amount is tabular and right-aligned. */
.table-scroll { overflow-x: auto; }
.tax-table { width: 100%; border-collapse: collapse; background: var(--surface); border: 1px solid var(--border); }
.tax-table th, .tax-table td { padding: .5rem .75rem; border-bottom: 1px solid var(--border); text-align: right; font-variant-numeric: tabular-nums; }
.tax-table th:first-child { text-align: left; white-space: nowrap; }
.tax-table thead th { font-size: .875rem; font-weight: 600; color: var(--muted); vertical-align: bottom; }
.tax-table tfoot th, .tax-table tfoot td { font-weight: 600; border-bottom: 0; }
.tax-est { display: block; font-size: .8125rem; }
.tax-items { list-style: none; margin: 0; padding: 0; display: grid; gap: .35rem; }
.tax-items summary, .tax-total, .tax-net, .tax-items details li { display: flex; justify-content: space-between; gap: 1rem; }
.tax-items summary { cursor: pointer; min-height: 44px; align-items: center; padding: .35rem .75rem; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius); }
.tax-items summary > span:first-child { min-width: 0; overflow-wrap: anywhere; }
.tax-items details ul { list-style: none; margin: .25rem 0 .5rem; padding: 0 .75rem 0 1.75rem; display: grid; gap: .25rem; color: var(--muted); }
.tax-total, .tax-net { padding: .35rem .75rem; font-weight: 600; }
.tax-net { margin: 1rem 0; border-top: 2px solid var(--text); }
.tax-amount { flex: 0 0 auto; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Categories: one group per category, the two choices side by side where there's room. */
.tax-map { display: grid; gap: .5rem; max-width: 48rem; }
.tax-map-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 0 1rem; margin: 0; padding: .5rem .75rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.tax-map-row legend { padding: 0 .25rem; font-weight: 600; }
.tax-map-row .field { margin: .25rem 0; }
.tax-map-row select { width: 100%; min-height: 44px; }
.tax-map button { justify-self: start; }
/* Depreciation: name on the left, amount on the right, one line each. */
.dep-list > li { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; padding: .35rem .75rem;
  border-bottom: 1px solid var(--border); }
.dep-list > li > span:first-child { min-width: 0; overflow-wrap: anywhere; }
.dep-pool-form { max-width: 36rem; }
.dep-pool-form .form-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: var(--gap); }
/* Categories with their tax treatment: name and actions on top, the tax choices underneath. */
.cat-tax-list { max-width: 48rem; }
.cat-tax-list > li { display: block; padding: .5rem .5rem .5rem 1rem; }
.cat-head { display: flex; justify-content: space-between; align-items: center; gap: .75rem; }
.cat-head > span:first-child { min-width: 0; overflow-wrap: anywhere; }
.cat-tax-list .tax-map-row { padding: 0; background: none; border: 0; margin-top: .25rem; }
.cat-tax-text { margin: .25rem 0 0; }
.tag-suggested { color: var(--text); background: var(--bg); border: 1px dashed var(--border); }
.cat-save { margin: var(--gap) 0 0; }
/* Starter packs: open one to tick what to add. */
.packs { display: grid; gap: .5rem; max-width: 48rem; }
.pack { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.pack > summary { display: flex; justify-content: space-between; align-items: center; gap: .75rem; min-height: 44px;
  padding: .5rem .75rem .5rem 1rem; cursor: pointer; }
.pack > summary > span:first-child { min-width: 0; overflow-wrap: anywhere; }
.pack > summary .tag { flex: 0 0 auto; }
.pack[open] > summary { border-bottom: 1px solid var(--border); }
.pack-form, .pack-present { margin: 0; padding: .5rem 1rem .75rem; }
.pack-form fieldset { border: 0; margin: 0 0 .5rem; padding: 0; }
.pack-items { list-style: none; margin: 0; padding: 0; display: grid; gap: .25rem; }
.pack-items li { display: flex; flex-wrap: wrap; align-items: center; gap: 0 .5rem; min-height: 44px; }
.pack-items input[type="checkbox"] { width: 1.25rem; height: 1.25rem; margin: 0; }
.pack-item-meta { flex-basis: 100%; padding-left: 1.75rem; font-size: .875rem; }
.tag-done { color: var(--ok-text); background: var(--ok-bg); border-color: var(--ok-bg); }
/* Expandable cards (starter packs, tax items): a chevron at the end that points down when closed and up when open,
   in place of the browser's own marker. Drawn from borders so it takes the text colour in either theme. */
.pack > summary, .tax-items summary, .txn-more > summary, .fold > summary { list-style: none; }
.pack > summary::-webkit-details-marker, .tax-items summary::-webkit-details-marker, .txn-more > summary::-webkit-details-marker,
.fold > summary::-webkit-details-marker { display: none; }
.pack > summary::after, .tax-items summary::after, .txn-more > summary::after, .fold > summary::after {
  content: ""; flex: 0 0 auto; width: .5rem; height: .5rem; margin: 0 .25rem 0 .25rem;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: translateY(-.15rem) rotate(45deg); transition: transform .15s ease;
}
.pack[open] > summary::after, .tax-items details[open] > summary::after, .txn-more[open] > summary::after,
.fold[open] > summary::after { transform: translateY(.1rem) rotate(-135deg); }
@media (prefers-reduced-motion: reduce) { .pack > summary::after, .tax-items summary::after, .txn-more > summary::after, .fold > summary::after { transition: none; } }
/* Classify: one card per group of unclassified lines, with its one choice. */
.classify-list { list-style: none; margin: 0; padding: 0; display: grid; gap: calc(.5rem * var(--density)); max-width: 48rem; }
.classify-list > li { padding: calc(.5rem * var(--density)) .75rem calc(.6rem * var(--density)) 1rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.classify-head { display: flex; flex-wrap: wrap; gap: .25rem .75rem; align-items: baseline; }
.classify-head > span { min-width: 0; overflow-wrap: anywhere; }
.classify-list .field { margin: .35rem 0 0; max-width: 24rem; }
.classify-list select { width: 100%; min-height: 44px; }
.classify-form { margin: 0; }
/* Money in, the same everywhere it's listed: bold, the "good" colour, and a plus sign. */
.money-in { font-weight: 700; color: var(--ok-text); }
/* Progress through a job of work: a count and a bar that fills (a native <progress>, like the admin gauges). */
.work-progress { margin: 0 0 var(--gap); max-width: 48rem; }
.work-progress p { margin: 0 0 .35rem; }
.work-bar { display: block; width: 100%; max-width: 48rem; height: .75rem; accent-color: var(--accent); }
/* Detail on request: a short "How this works" under a one-line intro. */
.how { margin: 0 0 var(--gap); max-width: 48rem; }
.how > summary { cursor: pointer; color: var(--accent); min-height: 44px; display: inline-flex; align-items: center; }
.how > p, .how > ul { margin: .25rem 0 .5rem; }
.classify-head { justify-content: space-between; }
.classify-amount { flex: 0 0 auto; font-variant-numeric: tabular-nums; white-space: nowrap; }
.classify-meta { margin: .1rem 0 0; font-size: .875rem; overflow-wrap: anywhere; }
.classify-done { background: var(--ok-bg); border-color: var(--ok-bg); }
.classify-done p { margin: 0; color: var(--ok-text); }
.classify-next { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; }
/* Confirming a group's lines before filing (js/classify.js), and the accountant pack before it's built
   (js/tax-pack.js): a modal dialog, its list scrolling when the group is long. */
.classify-confirm, .pack-confirm { width: min(34rem, calc(100vw - 2rem)); max-height: calc(100dvh - 2rem); padding: 1rem 1.1rem; color: var(--text);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.classify-confirm::backdrop, .pack-confirm::backdrop { background: color-mix(in srgb, var(--text) 35%, transparent); }
.classify-confirm h2, .pack-confirm h2 { margin: 0 0 .25rem; font-size: 1.1rem; }
.classify-confirm .hint, .pack-confirm .hint { margin: 0 0 .5rem; }
.classify-confirm-lines { list-style: none; margin: 0 0 .75rem; padding: 0; max-height: min(22rem, 50dvh); overflow-y: auto;
  border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.classify-confirm-lines li + li { border-top: 1px solid var(--border); }
.classify-confirm-lines label { display: flex; align-items: center; gap: .6rem; min-height: 44px; margin: 0; padding: .35rem .25rem; font-weight: 400; }
.classify-confirm-lines input { flex: 0 0 auto; width: 1.2rem; height: 1.2rem; margin: 0; }
.classify-line { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; font-size: .9375rem; font-weight: 400; }
.classify-confirm .actions, .pack-confirm .actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .75rem; }
.classify-byhand p { margin: 0 0 var(--gap); }
/* Home: the one thing to do next. */
.next-step { margin: 0 0 var(--gap); padding: .75rem 1rem; background: var(--surface); border: 1px solid var(--border);
  border-left: 4px solid var(--accent); border-radius: var(--radius); max-width: 48rem; }
.next-step h2 { margin: 0 0 .25rem; font-size: 1.125rem; }
.next-step p { margin: 0 0 .5rem; }
.next-step .work-bar { margin: 0 0 .75rem; }
/* Bank rows: one decision in view, the rest behind a chevron in the card's top-right corner (a 44px target in the
   card's right padding), which the top line also opens and closes (js/bank.js). */
.txn-attached { font-size: .8125rem; }
.txn:has(.txn-more) { padding-right: 2.75rem; }
.txn:has(.txn-more) .txn-main { cursor: pointer; }
.txn-more { display: inline-block; }
.txn-more > summary { position: absolute; top: .15rem; right: .1rem; width: 44px; height: 44px; display: flex; align-items: center;
  justify-content: center; cursor: pointer; color: var(--muted); border-radius: var(--radius); }
.txn-more > summary:hover { color: var(--text); }
.txn-more[open] { display: block; flex-basis: 100%; }
/* Wide screens: the category sits beside the entry, one line per card; open options or a chooser stack it again. */
@media (min-width: 48rem) {
  .txn { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 1.5rem; align-items: center; }
  .txn-classify { margin-top: 0; justify-content: flex-end; }
  .txn:has(.txn-more[open]), .txn.is-open { display: block; }
  .txn:has(.txn-more[open]) .txn-classify, .txn.is-open .txn-classify { margin-top: .6rem; justify-content: flex-start; }
}
.txn-more-body { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .75rem; padding: .25rem 0 .25rem .5rem;
  border-left: 2px solid var(--border); }
/* Add receipt: essentials first, the rest on request. */
.more-details > summary { cursor: pointer; min-height: 44px; display: flex; flex-wrap: wrap; align-items: center; gap: 0 .5rem; font-weight: 600; }
.more-details > summary .muted { font-weight: 400; font-size: .875rem; }
.more-details[open] > summary { margin-bottom: .5rem; }
.cat-saved { font-size: .8125rem; }
.cat-saved.is-error { color: var(--err-text); }

/* Work still to do is amber, not red: red stays for errors and deleting, so a to-do list doesn't read as failures. */
.tag-attention { color: var(--attention); background: var(--attention-bg); border-color: var(--attention-bg); }
/* A small, true win: what got done lately. */
/* A quiet well done, bordered so it reads as its own thing when it appears. */
.win { display: flex; align-items: center; gap: .4rem; margin: 0 0 var(--gap); padding: .5rem .75rem; max-width: 48rem;
  color: var(--ok-text); background: var(--ok-bg); border: 1px solid var(--border); border-radius: var(--radius); font-weight: 600; }
.win { border-color: color-mix(in srgb, var(--ok-text) 35%, transparent); }   /* the green of its own words, softened */
.win svg { flex: 0 0 auto; }
/* Undo for the last bulk change on Bank. */
.undo-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem 1rem; margin: 0 0 var(--gap);
  padding: .5rem .75rem; background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--accent); border-radius: var(--radius); }
/* Bank by month: each folds away with its own count to sort. */
.bank-months { display: grid; gap: .5rem; }
.bank-month { background: var(--bg); }
.bank-month > summary { display: flex; flex-wrap: wrap; align-items: center; gap: .25rem .75rem; min-height: 44px; cursor: pointer;
  padding: .4rem .75rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); list-style: none; }
.bank-month > summary::-webkit-details-marker { display: none; }
.bank-month > summary::after { content: ""; width: .5rem; height: .5rem; margin-left: auto; border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor; transform: translateY(-.15rem) rotate(45deg); transition: transform .15s ease; }
.bank-month[open] > summary::after { transform: translateY(.1rem) rotate(-135deg); }
@media (prefers-reduced-motion: reduce) { .bank-month > summary::after { transition: none; } }
.bank-month-name { font-weight: 600; }
.bank-month-money { font-size: .875rem; font-variant-numeric: tabular-nums; }
.bank-month[open] > .txn-list, .bank-month[open] > .table-wrap { margin-top: .35rem; }
.menu-choices { border: 0; margin: 0; padding: 0; display: grid; gap: .25rem; }
/* Readable line lengths for help text. */
.how > p, .tax-basis, .next-step p { max-width: 70ch; }
.dash-amount { font-variant-numeric: tabular-nums; }
.page-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-left: auto; }
/* Working from home: one month, one row a day. Days filled in from the usual week read lighter until confirmed. */
.wfh-nav { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; margin: 1rem 0 .25rem; }
.wfh-nav h2 { margin: 0; font-size: 1.2rem; min-width: 9rem; text-align: center; }
.wfh-pick { order: -1; flex: 1 1 100%; }   /* the month on its own line, Previous and Next side by side under it */
.wfh-pick select { width: auto; max-width: 100%; font-weight: 600; }
.wfh-totals { margin: 0 0 .75rem; }
.wfh-days { list-style: none; margin: 0 0 var(--gap); padding: 0; max-width: 40rem; border-top: 1px solid var(--border); }
.wfh-day { display: grid; grid-template-columns: 4.5rem 5rem minmax(0, 1fr) 6rem; align-items: center; gap: .5rem;
  padding: .2rem 0; border-bottom: 1px solid var(--border); }
.wfh-day input { min-height: 44px; width: 100%; }
.wfh-date { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }
.wfh-hours { text-align: right; font-variant-numeric: tabular-nums; }
.wfh-state { font-size: .8125rem; color: var(--muted); }
.wfh-day.is-usual .wfh-hours { color: var(--muted); font-style: italic; }
.wfh-day.is-weekend .wfh-date, .wfh-day.is-future .wfh-date { color: var(--muted); font-weight: 400; }
.wfh-day.is-future { opacity: .6; }
.wfh-weekdays { display: flex; flex-wrap: wrap; gap: .25rem 1rem; }
.wfh-weekdays legend { flex-basis: 100%; }
.wfh-weekdays > .choice { margin: 0; }   /* the flex gap spaces them, not fieldset.plain's stacking margin */
@media (max-width: 35.99rem) {
  .wfh-day { grid-template-columns: 3.75rem 4.5rem minmax(0, 1fr); }
  .wfh-state { grid-column: 2 / -1; padding-bottom: .25rem; }
  .wfh-state:empty { display: none; }
}

/* ── help: the page's dialog, the guide, Ask the help ───────────────────── */
.help-dialog { width: min(40rem, calc(100vw - 2rem)); max-height: calc(100dvh - 2rem); padding: 0 1.1rem 1rem; color: var(--text);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.help-dialog::backdrop { background: color-mix(in srgb, var(--text) 35%, transparent); }
.help-dialog-head { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .75rem 0 .5rem; background: var(--surface); }
.help-dialog-head h2 { margin: 0; font-size: 1.2rem; }
.help-more { margin: 1rem 0 0; }
.help-body, .guide-body { max-width: 68ch; line-height: 1.55; }
.help-body h3 { margin: 1.25rem 0 .35rem; font-size: 1.05rem; }
.help-body h4 { margin: 1rem 0 .25rem; font-size: 1rem; }
.help-body p, .guide-body p { margin: 0 0 .6rem; }
.help-body ul, .help-body ol, .guide-body ul, .guide-body ol { margin: 0 0 .6rem; padding-left: 1.3rem; }
.help-body li, .guide-body li { margin-bottom: .3rem; }
.help-icon { display: inline-flex; vertical-align: -.2em; color: var(--muted); }
.help-icon .icon { width: 1.1em; height: 1.1em; }
.help-ask { margin: 0 0 1rem; }
.help-ask label { display: block; font-weight: 600; margin-bottom: .25rem; }
.help-ask-row { display: flex; gap: .5rem; }
.help-ask-row input { flex: 1 1 auto; min-width: 0; }
.help-ask .wait-note { margin: .35rem 0 0; }
.help-ask-out:empty { display: none; }
.help-ask-out { margin-top: .75rem; }
.help-ask-out ol { list-style: none; margin: 0; padding: 0; }
.help-ask-out li + li { border-top: 1px solid var(--border); }
.help-ask-out a { display: block; padding: .5rem 0; color: var(--text); text-decoration: none; }
.help-ask-out a:hover .help-hit-head, .help-ask-out a:focus-visible .help-hit-head { text-decoration: underline; }
.help-hit-head { display: block; font-weight: 600; color: var(--accent); }
.help-hit-from { display: block; font-size: .875rem; color: var(--muted); }
.help-hit-text { display: block; font-size: .9375rem; }
.help-ask-out mark { background: var(--attention-bg); color: inherit; padding: 0 .1em; border-radius: 2px; }
.help-ask-busy { display: flex; align-items: center; gap: .6rem; margin: 0; color: var(--muted); }
.help-ask-busy::before { content: ""; width: 1rem; height: 1rem; border: 3px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .8s linear infinite; }
@media (prefers-reduced-motion: reduce) { .help-ask-busy::before { animation: none; border-color: var(--accent); } }

/* The guide: walkthroughs, then every page's help. Contents beside the text on wide screens, above it on phones. */
.guide { display: grid; gap: 1.5rem; }
.guide-lede { max-width: 60ch; }
.guide-toc { font-size: .9375rem; }
.guide-toc h2 { margin: .75rem 0 .35rem; font-size: .8125rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.guide-toc ol { list-style: none; margin: 0; padding: 0; }
.guide-toc .fold[open] > summary { margin-bottom: .25rem; }
.guide-toc a { display: block; padding: .3rem .6rem; border-left: 2px solid var(--border); color: var(--text); text-decoration: none; }
.guide-toc a:hover { border-left-color: var(--accent); text-decoration: underline; }
.guide-body > h2 { margin: 2rem 0 .5rem; font-size: 1.35rem; }
.guide-body > h2:first-child { margin-top: 0; }
.guide-body section { padding-top: .75rem; margin-top: 1.25rem; border-top: 1px solid var(--border); scroll-margin-top: 1rem; }
.guide-body section h3 { margin: 0 0 .4rem; font-size: 1.2rem; }
.guide-body section h4 { margin: 1.1rem 0 .3rem; font-size: 1.02rem; }
.guide-body [id] { scroll-margin-top: 1rem; }
@media (min-width: 60rem) {
  .guide { grid-template-columns: 15rem minmax(0, 1fr); gap: 2rem; align-items: start; }
  .guide-toc { position: sticky; top: 1rem; max-height: calc(100dvh - 2rem); overflow-y: auto; }
}
/* A row of steps ({{flow:…}} in help): pills with arrows between, wrapping on a phone. */
ol.help-flow { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; list-style: none; margin: .25rem 0 1rem; padding: 0; }
.help-flow li { display: flex; align-items: center; gap: .4rem; margin: 0; font-size: .875rem; line-height: 1.3; }
.help-flow span { padding: .3rem .65rem; background: var(--bg); border: 1px solid var(--border); border-radius: 999px; }
.help-flow li:not(:last-child)::after { content: "→" / ""; color: var(--muted); }
/* Screenshots in help: small and framed on a tint, so they read as pictures of the page, not part of it. */
.help-shot { margin: .5rem 0 1rem; max-width: 15rem; padding: .5rem; background: color-mix(in srgb, var(--text) 8%, var(--surface));
  border-radius: var(--radius); }
.help-shot img { display: block; width: 100%; height: auto; border-radius: calc(var(--radius) - 3px); box-shadow: 0 1px 3px color-mix(in srgb, var(--text) 25%, transparent); }
.help-shot figcaption { margin-top: .4rem; font-size: .8125rem; color: var(--muted); }
.classify-density { margin: 0 0 .5rem; }

/* ── the on-screen keyboard ──────────────────────────────────────────────── */
/* While a text field has focus (the keyboard is probably up), things pinned to the bottom of the screen step aside:
   on Android they'd ride up on the keyboard and sit over the field. They come back when typing's done. */
:root:has(input:not([type=checkbox], [type=radio], [type=range], [type=color], [type=file], [type=button], [type=submit]):focus, textarea:focus)
  :is(.update-banner, .scan-panel) { visibility: hidden; }
/* While a receipt is being read its panel stays pinned at the top; a field scrolled into view lands below it. */
html:has(.progress.is-busy) { scroll-padding-top: 7rem; }
/* A tall text box never outgrows the space the keyboard leaves (phone landscape); it scrolls inside instead. */
textarea { max-height: max(6rem, 60dvh); }

/* A record's change history (history.php). */
.history-list { list-style: none; margin: 0; padding: 0; max-width: 48rem; }
.history-list li { padding: .6rem 0; border-bottom: 1px solid var(--border); }
.history-meta { margin: 0; font-size: .875rem; color: var(--muted); }
.history-what { margin: .15rem 0 0; overflow-wrap: anywhere; }
.record-history { margin: 1.5rem 0 0; font-size: .9375rem; }
/* Home: put aside for BAS, money owed, renewals coming up (index.php). Amber for what's late, never red. */
.dash-figure { font-size: 1.25rem; font-variant-numeric: tabular-nums; }
.attention-text { color: var(--attention); }
.dash-remind { font-size: .9375rem; color: var(--accent); }
.dash-list .dash-remind { color: var(--accent); }
/* A new receipt that looks like one already saved (receipt.php): amber, with the yes beside it. */
.duplicate-notice { border-left-color: var(--attention); background: var(--attention-bg); }
.duplicate-notice p { margin: 0 0 .5rem; }
.duplicate-notice .choice { margin: 0; }
/* Renewals on vehicle and asset pages (inc/renewals.php). */
.renewals { margin: 2rem 0 0; max-width: 40rem; }
.renewals h2 { font-size: 1.2rem; margin: 0 0 .25rem; }
.renewal-list { max-width: none; margin: .5rem 0 1rem; }
.renewal-list li { flex-wrap: wrap; gap: .5rem 1rem; }
.renewal-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; margin-left: auto; }
.renewal-form { margin: 0; display: flex; align-items: center; gap: .35rem; }
.renewal-change > summary { cursor: pointer; color: var(--accent); min-height: 44px; display: inline-flex; align-items: center; }
.renewal-change[open] > summary { margin-bottom: .35rem; }
.renewal-add .actions { margin-top: .25rem; }
.renewal-cost { width: 7rem; }
/* A receipt's payments received (invoice.php): how, and how much, side by side. */
.receipt-payments { display: grid; gap: .5rem; max-width: 30rem; }
.receipt-payment { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: .75rem; }
.receipt-payment .field { margin: 0; }
/* Receipts for payments on an invoice (sql/044): a row's remove button beside its amount; where a receipt's cash
   went, each way folded until wanted. */
.invoice-section .row-end { display: inline-flex; align-items: center; gap: .5rem; white-space: nowrap; }
.invoice-section .row-end .inline-form { display: inline-flex; margin: 0; }
.cash-way > summary { cursor: pointer; color: var(--accent); min-height: 44px; display: inline-flex; align-items: center; }
.cash-way form { margin: .25rem 0 .75rem; max-width: 30rem; }
/* Address suggestions under an address box (js/address.js): full-width rows, each a 44px touch target. */
.address-suggestions { list-style: none; margin: .25rem 0 0; padding: 0; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); max-height: calc(5.5 * 44px); overflow-y: auto; overscroll-behavior: contain; }
.address-suggestions li + li { border-top: 1px solid var(--border); }
.address-suggestions button { display: block; width: 100%; min-height: 44px; padding: .5rem .75rem; border: 0; border-radius: 0;
  background: transparent; color: var(--text); font: inherit; text-align: left; cursor: pointer; }
.address-suggestions button:hover { background: var(--bg); }
.address-suggestions button:focus-visible { outline-offset: -3px; background: var(--bg); }
/* A second row of tabs under the first (Invoices: where invoices stand, under the kind of document): smaller, quieter. */
.sub-tabs { margin-top: calc(var(--gap) * -.5); }
.sub-tabs .fy-tab { min-height: 44px; padding: .35rem .75rem; font-size: .9rem; background: transparent; }
/* Bank's occasional tools, written twice (bank.php): a plain row where there's room, a fold where there isn't.
   Exactly one is ever displayed, and CSS decides, so no button appears for a moment and then goes away. */
.more-actions-wide { display: none; flex-wrap: wrap; gap: .5rem; }
.more-actions > summary { cursor: pointer; min-height: 44px; display: inline-flex; align-items: center; padding: 0 .9rem;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--text); }
/* Open, the buttons sit beside More rather than dropping it onto a line of its own. */
.more-actions[open] { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.more-actions[open] > summary { margin: 0; }
.more-actions-list { display: flex; flex-wrap: wrap; gap: .5rem; }
@media (min-width: 60rem) {
  .more-actions-wide { display: flex; }
  .more-actions { display: none; }
}

/* Money moved between the business's own accounts, offered as pairs to confirm. */
.transfers-found { margin-bottom: var(--gap); }
.transfer-pair { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .35rem .75rem; }
.transfer-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.transfer-actions form { margin: 0; }
/* The Cash screen (cash.php): made for a thumb. Which way first, as two big targets, then one field under another
   so nothing is hunted for on a phone. */
.cash-quick { max-width: 30rem; }
.cash-way { display: flex; gap: .5rem; margin: 0 0 var(--gap); }
.cash-choice { flex: 1 1 0; display: flex; flex-direction: column; align-items: flex-start; gap: .1rem;
  min-height: 60px; padding: .6rem .75rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.cash-choice:has(input:checked) { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.cash-choice:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }
.cash-choice .muted { font-size: .875rem; }
.cash-amount input { font-size: 1.5rem; min-height: 56px; }
.cash-note.is-error { color: var(--err-text); }
.cash-waiting li { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .35rem .75rem; }
.bank-cash-link { margin: .25rem 0 var(--gap); }
/* Cash typed in: one row of small fields, wrapping on a phone, with What it was taking the room left over. */
.cash-form { display: flex; flex-wrap: wrap; align-items: end; gap: .5rem .75rem; margin: .5rem 0; }
.cash-form .field { margin: 0; min-width: 0; flex: 0 1 9rem; }
.cash-form .field:has(input[name="details"]) { flex: 2 1 14rem; }
.cash-form input, .cash-form select { width: 100%; min-width: 0; }
/* Naming an account: the name beside its Save, the line count as the label above it. */
.bank-account-row { display: flex; flex-wrap: wrap; align-items: end; gap: .5rem; margin: .5rem 0; }
.bank-account-row .field { margin: 0; flex: 1 1 14rem; max-width: 22rem; }
.bank-account-row label { font-weight: 400; color: var(--muted); font-size: .875rem; }
.sub-tabs .fy-tab[aria-current="true"] { background: transparent; color: var(--accent); border-color: var(--accent); }

/* Every fold shows that it opens: a chevron before its words (pointing right, down once open), whatever its own
   display. The browser's own triangle disappears on summaries laid out with flex, so it's drawn here for all of them.
   Folds with their own chevron after the words (.fold, .pack, .tax-items, .txn-more, .bank-month) and the account
   finder (its "Change" is the cue) keep theirs. */
details > summary { cursor: pointer; list-style: none; }
details > summary::-webkit-details-marker { display: none; }
details:not(.fold, .pack, .txn-more, .bank-month, .account-finder) > summary::before {
  content: ""; display: inline-block; flex: 0 0 auto; width: .45rem; height: .45rem; margin: 0 .6rem 0 .1rem;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(-45deg); transition: transform .15s ease;
}
details:not(.fold, .pack, .txn-more, .bank-month, .account-finder)[open] > summary::before { transform: translateY(-.1rem) rotate(45deg); }
.tax-items details > summary::before { content: none; }
@media (prefers-reduced-motion: reduce) { details > summary::before { transition: none; } }

/* ── to-do (todo.php) ─────────────────────────────────────────────────────── */
/* Adding lives in its own panel, so what makes a task and what only changes the view can't be mistaken for each other. */
.task-add { display: flex; flex-wrap: wrap; align-items: end; gap: .5rem 1rem; margin-bottom: var(--gap);
  padding: .75rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
/* The add box grows with what's typed (js/todo.js sets the height); it never shows its own scrollbar until the cap. */
.grow-box { resize: none; overflow-y: auto; min-height: 44px; }
.add-note { flex: 1 1 100%; margin: 0; }
.add-note.is-error { color: var(--err-text); }
.add-note:empty { display: none; }
/* Project and Due keep each other company, and join the box's own line wherever the screen allows: the panel for
   adding shouldn't push the list off the screen. A date field asks for a wide intrinsic size, so it's told what it
   gets rather than left to decide. */
.task-add-fields { flex: 1 1 18rem; display: flex; flex-wrap: wrap; align-items: end; gap: .5rem 1rem; }
.task-add-fields .field { margin: 0; min-width: 0; }
.task-add-fields .field:has(select) { flex: 1 1 7rem; max-width: 14rem; }
.task-add-fields .field:has(input) { flex: 0 0 auto; }
.task-add-fields select { width: 100%; min-width: 0; }
.task-add-fields input[type="date"] { min-width: 0; }
.task-add > .field { flex: 3 1 13rem; margin: 0; }   /* the box itself takes the room that's left over */
.task-add .hint { flex: 1 1 100%; margin: 0; }
/* The view, not the task: a quiet bar over the lists, set apart from the panel above it. */
.task-filters { display: flex; flex-wrap: wrap; align-items: end; justify-content: flex-end; gap: .5rem 1rem;
  margin-bottom: .5rem; padding-top: .25rem; border-top: 1px solid var(--border); }
/* Label beside the control, not above it: one 44px line for the whole bar instead of two. */
.task-filters .field { display: flex; align-items: center; gap: .4rem; margin: 0; min-width: 0; flex: 1 1 9rem; max-width: 15rem; }
.task-filters select { width: 100%; min-width: 0; }
.task-filters label { margin: 0; white-space: nowrap; font-size: .875rem; font-weight: 400; color: var(--muted); }

.task-list { list-style: none; margin: 0 0 var(--gap); padding: 0; }
/* Tick and words; the chevron is pinned to the row's top right (see below) so it stays put as the task opens, and
   the panel is a full-width block underneath. */
.task-list .task { position: relative; display: grid; grid-template-columns: auto 44px minmax(0, 1fr); align-items: start;
  gap: .25rem .6rem; padding: calc(.5rem * var(--density)) 2.75rem calc(.5rem * var(--density)) 0;
  border-bottom: 1px solid var(--border); }
.task-list { min-height: 3rem; }
.task-none { padding: .5rem 0; }
/* Written with no signal and still on the phone (js/todo-queue.js): no tick, no grip, nothing to drag — it isn't a
   task on the list yet, so the row is its words and a quiet tag, across the whole width. */
.task-list .task.task-waiting { grid-template-columns: minmax(0, 1fr); padding-right: 0; color: var(--muted); }

/* No signal (js/offline.js): what's left is what still works. Figures from the last time the phone was on are not
   shown as though they were today's, and a link nobody can follow is plain words rather than a dead blue one. */
body.is-offline [data-offline-hide] { display: none; }
.offline-line { margin: 0 0 var(--gap); padding: .5rem .75rem; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); }
.was-link { color: inherit; }

/* The grip is the only thing that drags, so a finger anywhere else on the row still scrolls the page.
   touch-action: none, or the browser takes the pointer for a scroll and the drag never starts. */
.task-grip { display: flex; align-items: center; justify-content: center; width: 32px; min-height: 44px; padding: 0;
  color: var(--muted); background: none; border: 0; cursor: grab; touch-action: none; }
.task-grip:hover { color: var(--text); }
@media (pointer: coarse) { .task-grip { width: 44px; } }
body.is-dragging { cursor: grabbing; user-select: none; }
/* Lifted: this is the one in your hand. Dimmed and ringed, said before anything moves; invisible to the hit test,
   or elementFromPoint would keep answering with the row being dragged. */
.task.is-lifted { opacity: .55; pointer-events: none; box-shadow: inset 0 0 0 2px var(--focus); border-radius: var(--radius); }
/* Where the drop will land: painted on the target's own edge, never inserted between rows. An indicator that takes up
   space shifts the list under the pointer, and the line then moves out from under the very finger aiming at it. */
.task.drop-before { box-shadow: inset 0 3px 0 0 var(--focus); }
.task.drop-after, .task-list.drop-after { box-shadow: inset 0 -3px 0 0 var(--focus); }
@media (prefers-reduced-motion: no-preference) {
  .task.just-moved { animation: task-landed 1s ease-out; }
  @keyframes task-landed {
    from { background: color-mix(in srgb, var(--focus) 22%, transparent); }
    to   { background: transparent; }
  }
}
.task-drag-hint { margin: 0 0 var(--gap); }
.task-tick { margin: 0; }
.task-tick-btn { width: 44px; padding: 0; justify-content: center; }
.task-body { min-width: 0; }
.task-words { overflow-wrap: anywhere; }
.task-words ul, .task-words ol { margin: .25rem 0; padding-left: 1.2rem; }
.task-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .5rem; margin: .25rem 0 0; font-size: .875rem; }
.task-meta:empty { display: none; }
/* Done stays in sight for a day, quieter: undoing a mistake is one tap, and the list still reads as work left. */
.task-done .task-words { color: var(--muted); text-decoration: line-through; }
/* The chevron is the way in to a task's project and dates. It's pinned to the row's own top right, so opening and
   closing turns it without moving it, and the panel below gets the full width. */
.task-more { grid-column: 1 / -1; }
.task-list .task > .task-tick { grid-column: 2; }
.task-more > summary { position: absolute; top: .3rem; right: 0; width: 44px; height: 44px; display: flex;
  align-items: center; justify-content: center; cursor: pointer; color: var(--muted); border-radius: var(--radius); }
.task-more > summary:hover { color: var(--text); }
.task-more[open] > .txn-more-body { padding-top: .5rem; }
.task-body { cursor: pointer; }
.task-fields { display: flex; flex-wrap: wrap; gap: .5rem 1rem; }
.task-fields .field { flex: 1 1 10rem; margin: 0; }
.task-edit .actions, .task-delete { display: inline-flex; flex-wrap: wrap; gap: .75rem; align-items: center; }
.task-saved { font-size: .8125rem; }
.task-saved.is-error { color: var(--err-text); }
.task-fields input[type="date"] { min-width: 0; }
/* With JavaScript the details save themselves, so the button is the quiet fallback and the line says so. */
:root:not(.js) .task-autosave { display: none; }
.task-delete { margin-left: .75rem; }

/* A project's colour is a dot beside its name: the name carries the meaning, the colour only helps you find it. */
.task-chip { display: inline-flex; align-items: center; gap: .35rem; color: var(--text); text-decoration: none; }
.task-chip::before { content: ""; width: .6rem; height: .6rem; border-radius: 50%; background: var(--project-slate); }
.task-chip[data-colour="teal"]::before  { background: var(--project-teal); }
.task-chip[data-colour="plum"]::before  { background: var(--project-plum); }
.task-chip[data-colour="amber"]::before { background: var(--project-amber); }
.task-chip[data-colour="moss"]::before  { background: var(--project-moss); }
.task-chip[data-colour="sky"]::before   { background: var(--project-sky); }
.task-chip[data-colour="clay"]::before  { background: var(--project-clay); }
.task-chip[data-colour="rose"]::before  { background: var(--project-rose); }
.task-chip[data-colour="job"]::before   { background: var(--project-job); }
a.task-chip:hover { text-decoration: underline; }
.task-colour-form { display: inline-flex; align-items: center; gap: .5rem; }
.task-colour { min-height: 44px; }

/* The rich box: a plain textarea without JavaScript, a formatted box with it (js/todo.js). */
.rich-tools { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: .35rem; }
/* Each button looks like what it does: the mark is the picture, the accessible name is still the word. */
.rich-tools button { min-width: 44px; min-height: 44px; font-size: 1rem; line-height: 1; }
.rich-tools button[data-cmd="bold"] { font-weight: 800; }
.rich-tools button[data-cmd="italic"] { font-style: italic; font-family: Georgia, "Times New Roman", serif; }
.rich-tools button[data-cmd="underline"] { text-decoration: underline; text-underline-offset: 2px; }
.rich-tools button[data-cmd="strikethrough"] { text-decoration: line-through; }
.rich-tools button[data-cmd="insertUnorderedList"] { letter-spacing: .1em; }
.rich-area { min-height: 3rem; padding: .55rem .7rem; background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius); overflow-wrap: anywhere; }
.rich-area:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.rich-area ul, .rich-area ol { margin: .25rem 0; padding-left: 1.2rem; }
.scratch-form .rich-area { min-height: 16rem; }
