/* MojaWallet, in a browser.
 *
 * The customer here is not the operator in admin/app.css. Where that file
 * optimises for density on a laptop at 2am, this one optimises for a person
 * on a 5-inch phone, one-handed, in daylight, deciding whether to press a
 * button that moves their money. So:
 *
 *   1. MOBILE FIRST and mobile ONLY at the base. Every wider layout is an
 *      enhancement in one media query at the bottom. Nothing here is
 *      narrower than 320px and nothing scrolls sideways.
 *   2. BIG. 16px body minimum (iOS zooms any smaller input), 48px tap
 *      targets, amounts at 15-40px. Nothing important is grey-on-grey.
 *   3. DARK IS THE PRODUCT. Not "dark if your phone asks for it" - dark is
 *      what this wallet looks like, the way every wallet people already
 *      trust looks. Light exists, and it is properly designed, but it is a
 *      choice a customer makes, never the default a stranger lands on.
 *   4. ONE LOUD ACCENT on near-black, and a real typeface. Those two things
 *      are the entire difference between this and a government form, which
 *      is what it looked like when the accent was #141416 and the type was
 *      whatever the OS had lying around.
 *   5. Money is still never coloured for decoration: green means value
 *      arrived, red means it left, amber warns. The accent is a surface
 *      colour and never a figure.
 *   6. STATE IS NEVER COLOUR ALONE. Every state marker in here is a SHAPE as
 *      well as a hue - a disc, a clock, a bar, a dashed ring around a '?' -
 *      so a colour-blind customer, or one reading a cracked screen in the
 *      sun, can still tell "waiting" from "we do not know".
 */

/* The typeface. Plus Jakarta Sans, SIL OFL (see assets/fonts/OFL.txt), one
 * 27KB variable woff2 covering latin at every weight we use. Self-hosted
 * because this app is served from its own origin with no CDN, which is also
 * why it loads fast and works offline once cached. */
@font-face {
  font-family: 'Jakarta';
  src: url('/app/assets/fonts/plus-jakarta-sans-latin.woff2') format('woff2');
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------- tokens */
:root {
  /* DARK IS THE BASE, and this line is what makes the browser agree: without
     it a machine set to light draws light date pickers, light selects, light
     scrollbars and a white autofill wash inside a near-black wallet. The
     <meta name="color-scheme" content="light dark"> in index.html only says
     both are SUPPORTED; this says which one this page IS. */
  color-scheme: dark;

  --font: 'Jakarta', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* DARK IS THE BASE. Near-black, not black: pure #000 kills the sense of
     depth that separates a surface from the page behind it. */
  --bg: #0A0A0B;  --bg2: #0F0F11;  --card: #141417;  --card2: #1B1B20;
  --line: rgba(255,255,255,0.075); --line-2: rgba(255,255,255,0.15);
  /* tx3 was #74747E, which measured 3.71:1 on card2 and failed AA on all
     four grounds it is drawn on - and it is not decoration: it carries the
     balance eyebrow, the rate line, the tab labels and the date headings.
     Walked up in lightness until the "4.6:1 floor" DESIGN.md claims for it
     is true rather than aspirational: 5.35 / 5.17 / 4.97 / 4.63. */
  --tx: #FFFFFF;  --tx2: #9E9EA8;  --tx3: #84848E;

  /* The accent. Acid lime on near-black, loud on purpose: it is the one
     colour a customer should be able to find on any screen without looking.
     Text on it is the ground colour, which measures ~15:1 either way. */
  --acc: #C6F24E; --acc-deep: #B2E02F; --on-acc: #0A0A0B; --acc-dim: rgba(198,242,78,0.12);
  /* Secondary, for links and quieter emphasis, so the lime never has to
     compete with itself. It is also the only accent that survives being a
     FILL in light mode: the lime measures 1.5:1 on paper and disappears. */
  --acc-2: #7FA6FF; --acc-2-dim: rgba(127,166,255,0.14);

  --up: #3FE08A;  --up-dim: rgba(63,224,138,0.13);
  --down: #FF6B6B; --down-dim: rgba(255,107,107,0.13);
  --warn: #FFC24D; --warn-dim: rgba(255,194,77,0.13);
  --scrim: rgba(0,0,0,0.66);

  /* The block a skeleton is made of, and the sweep that crosses it. They are
     their own tokens because `card2` on `card` is 1.07:1 in dark - a track
     nobody can see, which turns a loading list into an empty one. These are
     1.22 and 1.49 against a card, which reads as "something is coming"
     without competing with the content that lands on top of them. */
  --skel: #26262E; --skel-hi: #34343E;

  /* DESIGN.md's four radii, by the name of the thing they belong to, so a
     new component cannot quietly invent a fifth. */
  --r-ctl: 16px;    /* anything a finger presses: button, field, tile */
  --radius: 18px;   /* a card */
  --radius-lg: 24px; /* a panel that is the whole screen */
  --r-pill: 999px;

  --ctl-line: #6E6E78;
  --focus-ring: #C6F24E;
  --focus: 0 0 0 3px var(--focus-ring);
  --shadow: none;
  --aura: radial-gradient(60% 60% at 12% 0%, rgba(198,242,78,0.16), transparent 70%);
  /* The sticky top bar's real height, so a date heading parks under it
     rather than sliding beneath it. One number, one place to change it. */
  --topbar-h: 60px;
}

/* Light is a real, designed theme - but it is a choice, never a default.
   A customer who asks their phone for light gets it; a stranger landing on
   the wallet for the first time gets the product's own face.

   This block used to be unreachable. `:root` above never closed, so with CSS
   nesting `:root[data-theme="light"]` parsed as a DESCENDANT of :root - html
   inside itself, which matches nothing - and the light theme was dead in
   every browser for as long as it has shipped. Nothing looked broken because
   the dark declarations before the missing brace still applied. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #FAFAF8;  --bg2: #F1F1EE;  --card: #FFFFFF;  --card2: #EEEEEA;
  --line: rgba(11,11,13,0.09); --line-2: rgba(11,11,13,0.18);
  --tx: #0B0B0D;  --tx2: #55555F;  --tx3: #686872;
  /* the lime is unreadable as text on paper, so light mode uses it only as a
     fill under near-black text, exactly as dark mode does. */
  --acc: #B2E02F; --acc-deep: #97C020; --on-acc: #0A0A0B; --acc-dim: rgba(120,160,20,0.14);
  --acc-2: #2F5BD0; --acc-2-dim: rgba(47,91,208,0.10);
  --up: #17794C;  --up-dim: rgba(23,121,76,0.10);
  --down: #C0392F; --down-dim: rgba(192,57,47,0.10);
  --warn: #8A5E12; --warn-dim: rgba(138,94,18,0.10);
  --scrim: rgba(11,11,13,0.40);
  /* on paper the sweep is the LIGHT one, so the pair inverts */
  --skel: #E7E7E3; --skel-hi: #F6F6F3;
  /* #8A8A92 was 2.94:1 on card2 - a field nested on a card had no visible
     edge in daylight. #86868E is 3.10:1 there and 3.46:1 on the page. */
  --ctl-line: #86868E;
  --focus-ring: #0B0B0D;
  --focus: 0 0 0 3px var(--focus-ring);
  --shadow: 0 1px 2px rgba(16,24,40,.05), 0 8px 28px rgba(16,24,40,.07);
  --aura: radial-gradient(60% 60% at 12% 0%, rgba(120,160,20,0.10), transparent 70%);
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  /* NOTHING SCROLLS SIDEWAYS. `overflow-x: hidden` on <body> alone does not
     achieve that: the first of <html>/<body> with a non-visible overflow
     PROPAGATES to the viewport and <body> then computes back to visible, so
     the page still scrolls - it just loses the scrollbar that would have
     shown you it was happening. `clip` on <html> makes the viewport
     unscrollable on that axis instead of silently scrollable, and leaves the
     y axis alone so sticky headers keep working. body keeps `hidden` as the
     fallback for a browser without `clip`. */
  overflow-x: clip;
}
body {
  background: var(--bg); color: var(--tx);
  font: 16px/1.5 var(--font);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}
.hidden { display: none !important; }
/* DESIGN.md's type scale, literally: screen title 24/700, section header
   18/700, row title 16/600, body 16/400, meta 13/400. */
h1 { font-size: 24px; line-height: 1.2; font-weight: 700; letter-spacing: -0.02em; }
h2 { font-size: 15px; font-weight: 600; color: var(--tx2); letter-spacing: 0.2px; }
a { color: inherit; }
small { font-size: 13px; }
.muted { color: var(--tx2); }
.faint { color: var(--tx3); }
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.lede { color: var(--tx2); font-size: 15px; margin: 0 0 4px; }
.note { color: var(--tx2); font-size: 14px; line-height: 1.5; }
/* The small print - the disclosure, the ceiling, the "we do not record who
   was paid" - carries one rhythm rather than a different inline margin at
   every call site. A note that opens a block still sits flush. */
* + .note { margin-top: 12px; }
.rule { height: 1px; background: var(--line); margin: 12px 0; }
/* every figure in this wallet, without exception: a balance whose digits
   change width as it updates is the cheapest possible tell */
.figures { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------- controls */
button, input, select, textarea { font: inherit; color: inherit; }
/* An outline with an offset, not an inner glow: it stays visible on the
   primary button, whose own background IS the ring colour. */
:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; border-radius: 8px; box-shadow: none; }

/* read out, never drawn */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* Straight to the screen. A customer on a keyboard, or on a switch, should
   not walk the whole top bar and tab strip to reach Send. */
.skip {
  position: fixed; left: 10px; top: -80px; z-index: 90;
  background: var(--card); color: var(--tx); border: 2px solid var(--focus-ring);
  border-radius: 10px; padding: 11px 16px; font-weight: 700; text-decoration: none;
}
.skip:focus { top: 10px; }
main:focus { outline: none; }
/* A row that fills its panel edge to edge would have its ring clipped by the
   panel's own rounding, so it draws the ring inside itself instead. */
.oprow:focus-visible, nav#tabs a:focus-visible { outline-offset: -3px; }

/* THE BUTTON, in all seven states DESIGN.md asks for: default, hover, focus,
   active, disabled, loading, error. Focus is the shared ring above; loading
   is `aria-busy`, which is the same attribute a screen reader reads, so the
   look and the announcement can never drift apart. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 48px; padding: 12px 18px; border-radius: var(--r-ctl);
  border: 1px solid var(--ctl-line); background: var(--card); color: var(--tx);
  font-size: 15.5px; font-weight: 600; cursor: pointer; text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: background .16s ease, border-color .16s ease, transform .12s ease;
}
.btn:hover { background: var(--card2); }
.btn:active { transform: scale(.97); }
.btn.primary { background: var(--acc); color: var(--on-acc); border-color: var(--acc); font-weight: 700; }
.btn.primary:hover { background: var(--acc-deep); border-color: var(--acc-deep); }
.btn.ghost { background: transparent; border-color: transparent; color: var(--tx2); }
.btn.ghost:hover { background: var(--card2); color: var(--tx); }
.btn.danger { color: var(--down); border-color: var(--down); background: transparent; }
.btn.danger:hover { background: var(--down-dim); }
.btn.small { min-height: 44px; padding: 8px 14px; font-size: 14px; border-radius: 12px; }
.btn.block { display: flex; width: 100%; margin-top: 14px; }
.btn:disabled { opacity: .45; cursor: default; }
.btn:disabled:hover { background: var(--card); }
.btn.primary:disabled:hover { background: var(--acc); }
.btn:disabled:active { transform: none; }
/* Loading. A button that is working says so with a mark that turns, and it
   is `aria-busy` rather than a class so the same attribute drives the
   announcement. It never becomes a spinner in CONTENT - content gets a
   skeleton - but a control the customer just pressed has to answer. */
.btn[aria-busy="true"] { cursor: progress; }
.btn[aria-busy="true"]::before {
  content: ""; width: 15px; height: 15px; flex: none;
  border-radius: 50%; border: 2px solid currentColor; border-top-color: transparent;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(1turn); } }
/* Error. A control that was refused carries the refusal on its own edge as
   well as in the message beside it. */
.btn[aria-invalid="true"] { border-color: var(--down); }

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; min-height: 44px; margin-left: -10px;
  border: 0; background: none; color: var(--tx2); cursor: pointer;
  border-radius: var(--r-pill); text-decoration: none;
  transition: background .16s ease, color .16s ease;
}
.icon-btn:hover { background: var(--card2); color: var(--tx); }

.field { display: block; margin-top: 18px; }
.field > span {
  display: block; font-size: 13px; font-weight: 600; color: var(--tx2);
  letter-spacing: .3px; margin-bottom: 7px;
}
.field input, .field select, select.pill {
  width: 100%; min-height: 52px; padding: 13px 15px; font-size: 17px;
  background: var(--card); color: var(--tx);
  border: 1px solid var(--ctl-line); border-radius: var(--r-ctl);
  transition: border-color .16s ease, background .16s ease;
}
.field input:hover, .field select:hover { border-color: var(--tx3); }
.field input:focus, .field select:focus { border-color: var(--acc); }
.field input::placeholder { color: var(--tx3); }
/* The refused field. flow.js sets aria-invalid and aria-describedby together,
   so the edge and the sentence under it are the same fact. */
.field input[aria-invalid="true"], .field select[aria-invalid="true"] {
  border-color: var(--down); background: var(--down-dim);
}
.field input:disabled, .field select:disabled {
  opacity: .55; cursor: default; background: var(--bg2);
}
.field input[readonly] { background: var(--bg2); color: var(--tx2); }
.amount-box { display: flex; align-items: center; gap: 10px; }
.amount-box input {
  font-size: 28px; font-weight: 700; letter-spacing: -0.5px;
  font-variant-numeric: tabular-nums; min-height: 62px;
}
.amount-box b { font-size: 15px; font-weight: 700; color: var(--tx2); }
.preview { display: block; margin-top: 7px; color: var(--tx2); font-variant-numeric: tabular-nums; }
.row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.choice { display: grid; gap: 10px; margin-top: 14px; }
.choice label {
  display: flex; gap: 12px; align-items: flex-start; padding: 14px;
  border: 1px solid var(--ctl-line); border-radius: var(--r-ctl);
  background: var(--card); cursor: pointer;
  transition: border-color .16s ease, background .16s ease;
}
.choice label:hover { background: var(--card2); }
/* the accent marks the CURRENT SELECTION, which is one of the three jobs
   DESIGN.md gives it. It is not a hover tint anywhere in this file. */
.choice label:has(input:checked) { border-color: var(--acc); background: var(--acc-dim); }
.choice label:has(input:disabled) { opacity: .5; cursor: default; }
.choice input { margin-top: 3px; width: 20px; height: 20px; accent-color: var(--acc); }
.choice b { display: block; font-size: 16px; font-weight: 600; }
.choice small { color: var(--tx2); }

/* ------------------------------------------------------------- messages */
/* The container a screen keeps for an answer it does not have yet. Empty, it
   must take no room at all, or every form carries a 14px hole. */
.msg:empty { display: none; }
.err, .ok-msg {
  display: block; border-radius: 14px; padding: 12px 14px; margin-top: 14px;
  font-size: 14.5px; line-height: 1.45;
}
.err { background: var(--down-dim); color: var(--down); }
.ok-msg { background: var(--up-dim); color: var(--up); }
.err b, .ok-msg b { display: block; font-weight: 700; }
.err span { display: block; margin-top: 3px; opacity: .95; }
.err-link { display: inline-block; margin-top: 6px; font-weight: 600; color: inherit; }
.tech {
  margin-top: 10px; font-family: var(--mono); font-size: 11.5px; color: var(--tx3);
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------- layout */
#shell { min-height: 100vh; padding-bottom: calc(72px + env(safe-area-inset-bottom, 0px)); }
.wrap { max-width: 560px; margin: 0 auto; padding: 0 18px; }
#topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: 12px; min-height: var(--topbar-h);
  padding: 8px 18px; background: var(--bg); border-bottom: 1px solid var(--line);
}
#topbar .mark { font-weight: 800; letter-spacing: -0.3px; font-size: 17px; }
#topbar .spacer { flex: 1; }
#topbar .tools { display: flex; gap: 4px; }
#topbar .tools button {
  border: 0; background: none; color: var(--tx2); font-size: 13px; font-weight: 600;
  padding: 8px 10px; border-radius: 12px; cursor: pointer; min-height: 44px; min-width: 44px;
  transition: background .16s ease, color .16s ease;
}
#topbar .tools button:hover { background: var(--card2); color: var(--tx); }
.screen { padding: 18px 0 24px; }

/* Offline. A bar the width of the screen, and a marker, because "amber" is
   not a message to somebody who cannot see amber. */
#offlinebar {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 18px; background: var(--warn-dim); color: var(--warn);
  font-size: 13.5px; font-weight: 600; text-align: center;
}
#offlinebar::before {
  content: ""; flex: none; width: 10px; height: 10px; border-radius: 50%;
  border: 1.5px solid currentColor;
  background: linear-gradient(currentColor, currentColor) 50% 50% / 8px 1.5px no-repeat;
  transform: rotate(-45deg);
}

/* THE BOTTOM BAR. Five, which is what fits a thumb and a phone, and the
   current one is the only thing on the screen wearing the accent. */
nav#tabs {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  display: flex;
  background: var(--card); border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom, 0);
}
nav#tabs a {
  flex: 1 1 0; min-width: 0; min-height: 56px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  padding: 8px 2px; text-decoration: none; color: var(--tx3);
  font-size: 11px; font-weight: 600; line-height: 1.2; text-align: center;
  transition: color .16s ease;
}
nav#tabs a span { overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
nav#tabs a:hover { color: var(--tx2); }

/* THE CURRENT TAB IS THE ACCENT, AND THE ACCENT IS A FILL.
 *
 * This was `color: var(--acc)` on the whole item. In light that is `#B2E02F`
 * on `--card` `#FFFFFF`: 1.9:1, so the one tab a customer needs to find is
 * the one that disappears in daylight - and it breaks DESIGN.md's own rule
 * that lime is a ground under near-black ink and never text on paper.
 *
 * The mark is a filled pill behind the glyph with `--on-acc` ink: 14.9:1 in
 * dark, 9.3:1 in light, the same mark in both themes. It is also not colour
 * alone, because the pill is a SHAPE the other four items do not have, and
 * the label steps from `--tx3` to `--tx` underneath it. */
nav#tabs a .tabmark {
  display: flex; align-items: center; justify-content: center;
  min-width: 46px; height: 28px; border-radius: var(--r-pill);
  transition: background .16s ease, color .16s ease;
}
nav#tabs a[aria-current="page"] { color: var(--tx); }
nav#tabs a[aria-current="page"] .tabmark { background: var(--acc); color: var(--on-acc); }

/* ---------------------------------------------------------------- panel */
/* The balance panel is the one section that is NOT a card. A hero inside a
   box is a dashboard tile; on the ground with the aura behind it, it is the
   first thing you see. */
.panel.balance-panel {
  background: none;
  border: 0;
  padding: 0;
  overflow: visible;
}

.panel {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px; margin-bottom: 14px; box-shadow: var(--shadow);
}
.panel > .ph {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin: -2px 0 12px;
}
/* the section header, DESIGN.md's 18/700 - a panel title is a heading, not a
   caption, and it is the same weight as the screen title one step down */
.panel > .ph h2 {
  font-size: 18px; font-weight: 700; color: var(--tx); letter-spacing: -0.01em;
}
.panel > .ph [data-acts] { flex: none; }
/* "See all" is two words and it stays two words: a section action that wraps
   to two lines is what makes a header look like it broke. */
.panel > .ph [data-acts] .btn { white-space: nowrap; }
.panel.flush { padding: 0; overflow: hidden; }
.panel.flush > .ph { padding: 16px 16px 0; margin-bottom: 8px; }
/* A list panel pads its rows rather than its own box, so a row can carry a
   full-width pressed ground.
   `overflow: visible` is not cosmetic: an ancestor with `overflow: hidden`
   becomes the scroll container for `position: sticky`, and history's date
   headings would then stick to the panel instead of to the top bar. The
   rows are inset by the padding, so nothing needs clipping here anyway. */
.ops-panel { overflow: visible; }
.ops-panel .oplist { padding: 0 16px; }
.ops-panel .statebox, .ops-panel .skel-wrap { padding-left: 16px; padding-right: 16px; }

/* --------------------------------------------------------------- states */
/* SKELETONS, NEVER SPINNERS, in content. Blocks shaped like the thing that
   is coming, so nothing jumps when the truth lands - and, more importantly,
   so nothing FALSE is on screen in the meantime. */
.skel-wrap { display: flex; flex-direction: column; gap: 10px; padding: 4px 0; }
.skel {
  height: 13px; border-radius: 7px; display: block;
  background: linear-gradient(90deg, var(--skel) 25%, var(--skel-hi) 37%, var(--skel) 63%);
  background-size: 400% 100%; animation: shimmer 1.3s ease infinite;
}
.skel-sm { height: 10px; margin-top: 7px; }
/* the same idea in the shape of a transaction row, so a list does not jump.
   The circle restates the shimmer because `.oprow .ic` paints a flat ground
   and would otherwise win on specificity, leaving a dead grey disc. */
.skel-rows .oprow { cursor: default; }
.skel-rows .ic.skel {
  width: 40px; height: 40px; border-radius: 50%; border: 0;
  background: linear-gradient(90deg, var(--skel) 25%, var(--skel-hi) 37%, var(--skel) 63%);
  background-size: 400% 100%;
}
.skel-rows .amt { display: flex; flex-direction: column; align-items: flex-end; }
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }

/* Empty, offline and failed, as first-class screens. An empty state teaches
   the next step; a failure says what happened and offers the one thing that
   helps. Neither is ever a bare "Loading" left in place forever. */
.statebox { text-align: center; padding: 30px 14px; color: var(--tx2); }
.statebox .ic {
  width: 44px; height: 44px; margin: 0 auto 12px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--card2); color: var(--tx3);
}
.statebox b { display: block; color: var(--tx); font-size: 16.5px; font-weight: 700; margin-bottom: 6px; }
.statebox p { font-size: 14.5px; line-height: 1.5; max-width: 34ch; margin: 0 auto; }
.statebox .btn { display: inline-flex; margin-top: 16px; }
.statebox.bad .ic { color: var(--down); background: var(--down-dim); }
.statebox.offline .ic { color: var(--warn); background: var(--warn-dim); }

/* --------------------------------------------------------------- money */
/* THE BALANCE BLOCK. Left-aligned, not centred: centring wastes the strongest
   corner of the screen and reads as a receipt. It sits on the page ground with
   the aura behind it rather than inside a card, because putting the hero in a
   box is what turns a wallet into a dashboard. */
.balance {
  position: relative;
  text-align: left;
  padding: 22px 0 10px;
}
.balance::before {
  content: '';
  position: absolute;
  /* It bleeds off the LEFT, where the gradient's centre is at 12%, and stops
     at the block's own right edge, where the gradient is already fully
     transparent. Bleeding right too used to push the page 14px wider than
     the screen at 320px - the whole page scrolled sideways to reveal a strip
     of nothing. Left overflow is not reachable in a left-to-right document,
     so the glow keeps the side that matters. */
  inset: -40px 0 0 -32px;
  background: var(--aura);
  pointer-events: none;   /* it is atmosphere; it must never eat a tap */
  z-index: -1;
}
/* THE ONE EYEBROW on the surface. Not above every section: that is the
   saturated AI tell DESIGN.md names by name. */
.balance .label {
  font-size: 12px; font-weight: 700; color: var(--tx3);
  text-transform: uppercase; letter-spacing: .09em;
}
.balance .big {
  display: flex; align-items: baseline; justify-content: flex-start;
  gap: 10px; margin-top: 10px;
}
.balance .big b {
  font-size: clamp(40px, 13vw, 52px); font-weight: 800; letter-spacing: -.028em;
  font-variant-numeric: tabular-nums; line-height: 1; overflow-wrap: break-word;
}
/* the unit is smaller and quieter than the figure, never the same weight */
.balance .big span { font-size: 20px; font-weight: 700; color: var(--tx2); }
.balance .sub { margin-top: 12px; color: var(--tx); font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; }
.balance .rate { margin-top: 4px; color: var(--tx3); font-size: 13px; font-variant-numeric: tabular-nums; }

/* The same block, one transaction instead of a balance, inside the detail
   panel. The aura is dropped: a card CLIPS a radial gradient, which is why
   DESIGN.md says it never sits inside one. */
.balance.detail-hero { padding-top: 4px; }
.balance.detail-hero::before { display: none; }
.balance.detail-hero .big b { font-size: clamp(30px, 9vw, 36px); }
/* direction is already in the sign; the colour only confirms it */
.balance .big b.up { color: var(--up); }

/* THE ACTION ROW. Rectangles, not circles, because a rectangle holds a real
   label. Exactly one of them is filled: a screen with two primary actions has
   none. */
.actions {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 10px; margin: 20px 0 8px;
}
.actions a {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 9px;
  min-height: 84px; padding: 16px 4px; border-radius: var(--r-ctl); background: var(--card);
  border: 1px solid var(--line); color: var(--tx); text-decoration: none;
  font-size: 13px; font-weight: 600; text-align: center; line-height: 1.2;
  transition: transform .16s ease, background .16s ease, border-color .16s ease;
}
.actions a:hover { background: var(--card2); }
.actions a:active { transform: scale(.97); }
.actions a.primary {
  background: var(--acc); border-color: var(--acc); color: var(--on-acc); font-weight: 700;
}
.actions a.primary:hover { background: var(--acc-deep); border-color: var(--acc-deep); }

.kv {
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
  padding: 9px 0; font-size: 15px;
}
.kv .k { color: var(--tx2); flex: none; }
.kv .v { text-align: right; font-variant-numeric: tabular-nums; overflow-wrap: break-word; }
.kv .v small { display: block; color: var(--tx2); font-size: 12.5px; }
.kv.strong { font-size: 17px; font-weight: 700; }
/* An operation id, or a provider's own reference. Quieter than the figures
   around it and allowed to wrap, because it is looked up rather than read. */
.kv .v.ref { font-size: 12.5px; color: var(--tx2); }
.kv .v.up { color: var(--up); }
.kv .v.down { color: var(--down); }
.kv .v.warn { color: var(--warn); }

/* ------------------------------------------------------------ the row */
/* THE TRANSACTION ROW, one anatomy for the home screen, history and every
   list in between: a 40px circular leading icon tinted by direction, the
   kind over its state and time on the left, and on the right the signed
   amount over its shilling equivalent. 68px tall, which is a comfortable
   thumb and two readable lines.
   There is deliberately no rail badge on the icon: GET /api/operations does
   not say which rail carried a payment, and an M-Pesa mark nobody computed
   is exactly the decoration this product has already deleted once. */
.oplist { display: flex; flex-direction: column; }
.oprow {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 68px; padding: 12px 0; border: 0; border-bottom: 1px solid var(--line);
  background: none; color: inherit; text-align: left; cursor: pointer; text-decoration: none;
  transition: background .16s ease;
}
.oprow:last-child { border-bottom: 0; }
/* the hover ground is the pressed surface, NOT an accent tint: the lime
   marks the primary action, the current selection and focus, and nothing
   else. A row that lights up lime on hover is decoration. */
.oprow:hover { background: var(--card2); }
.oprow .ic {
  flex: none; width: 40px; height: 40px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--card2); color: var(--tx2); border: 1px solid var(--line);
}
/* money arriving is the only row that is tinted, and the icon inside it
   already points the other way */
.oprow .ic.in { background: var(--up-dim); color: var(--up); border-color: transparent; }
.oprow .who { flex: 1; min-width: 0; }
.oprow .who b { display: block; font-size: 16px; font-weight: 600; line-height: 1.3; }
.oprow .who small {
  display: flex; align-items: center; gap: 5px; flex-wrap: wrap;
  color: var(--tx2); font-size: 13px; margin-top: 2px;
}
.oprow .amt {
  flex: none; text-align: right;
  font-variant-numeric: tabular-nums; font-weight: 700; font-size: 16px; white-space: nowrap;
}
.oprow .amt.in { color: var(--up); }
.oprow .amt small {
  display: block; font-weight: 500; font-size: 12.5px; color: var(--tx2); margin-top: 2px;
}

/* ------------------------------------------------------- state vocabulary */
/* STATE IS NEVER COLOUR ALONE, and the two that must never be confused -
   "we are waiting" and "we do not know" - differ in SHAPE, not hue:
 *
 *   settled     a solid disc
 *   waiting     a clock: a hollow ring with two hands
 *   returned    a bar
 *   unknown     a dashed ring around a literal '?'
 *
 * The marker is drawn in CSS rather than typed as a character, because a
 * latin-subset webfont has no clock glyph and would silently fall back to
 * whatever the OS had. The WORD is always there too, in ops.js, and the word
 * is what a screen reader reads.
 *
 * `.state` is the row form: a marker and a word, no fill, so it can sit
 * inside a line of meta text. `.pill` is the detail form: the same marker on
 * its own tinted ground. */
.state, .pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 13px; font-weight: 600; white-space: nowrap;
  border: 1px solid transparent; border-radius: var(--r-pill);
}
.pill {
  padding: 3px 11px 3px 9px; font-size: 12.5px; font-weight: 700;
  letter-spacing: .2px; background: var(--card2); color: var(--tx2);
}
.state::before, .pill::before {
  content: ""; flex: none; width: 10px; height: 10px; border-radius: 50%;
  background: currentColor;
}
.state.up, .pill.up { color: var(--up); }
.pill.up { background: var(--up-dim); }
/* WAITING. The ring plus a long hand at twelve and a short one at three:
   two background bars inside one circle, so the whole clock is a single
   element and needs no glyph and no second node. */
.state.warn, .pill.warn { color: var(--warn); }
.pill.warn { background: var(--warn-dim); }
.state.warn::before, .pill.warn::before {
  background:
    linear-gradient(currentColor, currentColor) 50% 1.6px / 1.4px 3.6px no-repeat,
    linear-gradient(currentColor, currentColor) 5px 50% / 2.8px 1.4px no-repeat;
  box-shadow: inset 0 0 0 1.4px currentColor;
}
/* RETURNED. A bar, which is neither a disc nor a clock nor a question. */
.state.bad, .pill.bad { color: var(--down); }
.pill.bad { background: var(--down-dim); }
.state.bad::before, .pill.bad::before { height: 3px; border-radius: 2px; }
/* UNKNOWN. A state this build has never heard of. It is NOT drawn as failed,
   because the difference between "we do not know" and "it failed" is the
   difference between waiting and sending the same money twice. It carries a
   dashed ring AND a literal '?', exactly as the ops console does. */
.state.unknown, .pill.unknown {
  color: var(--warn); border: 1px dashed currentColor;
}
.state.unknown { padding: 1px 8px 1px 7px; }
.pill.unknown { background: var(--warn-dim); }
.state.unknown::before, .pill.unknown::before {
  content: "?"; width: auto; height: auto; border-radius: 0;
  background: none; box-shadow: none; font-size: 11px; font-weight: 800; line-height: 1;
}

/* --------------------------------------------------- the feature card */
/* The slot MetaMask gives "Money balance". Ours carries the customer's own
   remaining limit and their tier, both straight from GET /api/limits, which
   reads the same rows the money engine enforces. NEVER A PROMO. */
.feature-panel .feature-top {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 10px 12px;
  /* the tier badge drops under the figure rather than sitting on top of it
     when a customer has text at 200% - reflow, not truncation */
  flex-wrap: wrap;
}
.feature-panel .feature-lead { flex: 1 1 auto; min-width: 0; }
.feature-panel h2 { margin-bottom: 6px; }
.feature-figure {
  display: block; font-size: 30px; font-weight: 800; letter-spacing: -0.025em;
  line-height: 1.05; font-variant-numeric: tabular-nums; overflow-wrap: break-word;
}
.feature-sub { margin-top: 6px; color: var(--tx2); font-size: 13px; font-variant-numeric: tabular-nums; }
.feature-tier {
  flex: none; padding: 5px 12px; border-radius: var(--r-pill);
  background: var(--acc-dim); color: var(--tx); border: 1px solid var(--line-2);
  font-size: 12.5px; font-weight: 700; white-space: nowrap;
}

/* THE METER. A limit is a fraction, and a fraction drawn as two sentences is
   a fraction nobody reads. The fill is the customer's REMAINING share of a
   real denominator the server sent - never a percentage this file invented -
   and it is aria-hidden because both of its numbers are already on the
   screen as text directly above it.
   The fill is the SECOND accent, not the lime: the lime measures 1.5:1 on
   paper and would be an invisible bar for every customer in light mode. */
.meter {
  margin-top: 12px; height: 10px; border-radius: var(--r-pill);
  background: var(--card2); box-shadow: inset 0 0 0 1px var(--ctl-line);
  overflow: hidden;
}
.meter-fill {
  display: block; height: 100%; border-radius: var(--r-pill);
  background: var(--acc-2);
  transition: width .32s ease;
}
/* Nothing left. An empty track would read as "no data", so the exhausted
   meter keeps a visible sliver in the colour of the thing that stopped it -
   and the figure above it says 0 in words. */
.meter.spent { box-shadow: inset 0 0 0 1px var(--warn); }

/* The next tier. This is NOT a warning - it is an offer, and amber is the
   colour this wallet uses to say something needs attention. It gets the
   quiet second accent, so the genuine warnings below keep their amber to
   themselves.

   TWO THINGS THIS DELIBERATELY IS NOT.

   It is not a 3px accent bar down one edge, which is what it was. A rail
   on one side of a tinted block is the side-stripe every templated
   dashboard wears, and this one sits INSIDE the feature card on the home
   screen - the first thing anybody looks at - where a bordered, rounded,
   tinted block reads as the nested card DESIGN.md bans outright.

   And it is not a hue on its own. The rail was amber or blue and nothing
   else: `flow.js` draws `.warn-note` as a bare sentence with no bold lead,
   so before this the ONLY thing separating "we are telling you something"
   from "you must act on this" was a colour - which a colour-blind customer,
   or anyone in daylight on a cracked screen, does not have. Both now carry
   a MARK drawn in CSS as well: an `i` in a ring for the offer, a `!` in a
   ring for the warning. Same technique as the state vocabulary above, and
   for the same reason - a latin-subset webfont has no such glyph, and the
   mark is a shape rather than a character a screen reader would read out
   on top of the sentence that already says it. */
.tier-note, .warn-note {
  position: relative; display: block; margin-top: 12px;
  padding: 12px 14px 12px 40px;
  border-radius: 12px; border: 0;
  font-size: 14px; line-height: 1.5;
}
.tier-note::before, .warn-note::before {
  content: ""; position: absolute; left: 13px; top: 14px;
  width: 17px; height: 17px; box-sizing: border-box;
  border-radius: 50%; border: 1.5px solid currentColor;
}
.tier-note {
  background: var(--acc-2-dim); color: var(--tx2);
}
/* the offer's mark: a dot over a bar - an `i` */
.tier-note::before {
  color: var(--acc-2);
  background:
    linear-gradient(currentColor, currentColor) 50% 2.4px / 1.7px 1.7px no-repeat,
    linear-gradient(currentColor, currentColor) 50% 6px / 1.7px 5.6px no-repeat;
}
.tier-note b { display: block; margin-bottom: 3px; color: var(--acc-2); font-weight: 700; }
.tier-note .faint { color: var(--tx2); }
/* a real warning: something the customer has to act on before they proceed */
.warn-note {
  background: var(--warn-dim); color: var(--tx2);
}
/* the warning's mark: a bar over a dot - a `!`, which is not the `i` above */
.warn-note::before {
  color: var(--warn);
  background:
    linear-gradient(currentColor, currentColor) 50% 2.8px / 1.7px 5.6px no-repeat,
    linear-gradient(currentColor, currentColor) 50% 10.6px / 1.7px 1.7px no-repeat;
}
.warn-note b { display: block; margin-bottom: 3px; color: var(--warn); font-weight: 700; }
.warn-note .faint { color: var(--tx2); }

/* ----------------------------------------------------------------- flow */
.flow-head { display: flex; align-items: center; gap: 6px; margin-bottom: 14px; }
.flow-head h1 { flex: 1; min-width: 0; }
.pane { padding-bottom: 8px; }
.pane.review .kv:first-child { padding-top: 0; }
.hold { margin-top: 14px; font-size: 13.5px; color: var(--tx2); text-align: center; font-variant-numeric: tabular-nums; }
.hold.expired { color: var(--warn); font-weight: 600; }
.pin-pane { text-align: center; }
.pin-amount { padding: 12px 0 18px; }
.pin-amount b { display: block; font-size: 30px; font-weight: 800; letter-spacing: -0.8px; font-variant-numeric: tabular-nums; }
.pin-amount span { color: var(--tx2); font-size: 14.5px; }
.pin-pane input {
  width: 100%; max-width: 240px; margin: 0 auto; display: block;
  text-align: center; font-size: 30px; letter-spacing: 12px; padding: 14px;
  background: var(--card); border: 1px solid var(--ctl-line); border-radius: var(--r-ctl); min-height: 66px;
}
.pin-pane .note { margin-top: 14px; }
.working { text-align: center; margin-top: 10px; }
.result { text-align: center; padding-top: 10px; }
.result .result-ic {
  width: 66px; height: 66px; margin: 0 auto 14px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--up-dim); color: var(--up);
}
/* the mark draws itself, ONCE, and only after the server has answered.
   PENDING and UNKNOWN never borrow it - they get the amber circle. */
.result .result-ic svg path { stroke-dasharray: 32; stroke-dashoffset: 0; animation: draw .42s ease-out; }
@keyframes draw { from { stroke-dashoffset: 32; } to { stroke-dashoffset: 0; } }
.result.wait .result-ic { background: var(--warn-dim); color: var(--warn); }
.result.wait .result-ic svg path, .result.bad .result-ic svg path { animation: none; }
.result.bad .result-ic { background: var(--down-dim); color: var(--down); }
.result h1 { margin-bottom: 8px; }
.result > p { color: var(--tx2); font-size: 15px; max-width: 36ch; margin: 0 auto 14px; }
.result .kv { text-align: left; }

/* ------------------------------------------------------------- listings */
.item {
  display: block; width: 100%; text-align: left; padding: 15px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  color: inherit; cursor: pointer; margin-bottom: 10px; text-decoration: none;
  transition: background .16s ease, border-color .16s ease;
}
.item:hover { background: var(--card2); }
.item b { display: block; font-size: 16px; font-weight: 600; }
.item small { color: var(--tx2); }
.item .top {
  display: flex; justify-content: space-between; gap: 10px; align-items: baseline;
  font-variant-numeric: tabular-nums;
}
/* A fine the customer has not seen yet. It is drawn ONLY when there is one -
   an empty banner slot teaches people to ignore the space - so when it is
   here it earns the top of the screen.

   It used to earn it with a 3px amber rail down its left edge, which was two
   problems in one line. It is the side-stripe, on the home screen, above the
   four verbs. And it made this banner amber ground plus amber rail plus amber
   title - three channels of the same hue and not one SHAPE - where the app
   draws the same thing with a shield in a disc (`FineNotice`, home.tsx). The
   rail is gone and the mark is here, so the two surfaces say it the same way
   and a customer who cannot separate amber from grey still sees a warning
   rather than a row. */
.item.alert-item {
  position: relative; padding-left: 48px;
  border-color: var(--warn-dim); background: var(--warn-dim);
}
/* the same `!` in a ring that `.warn-note` wears, because it is the same
   state saying the same thing - one mark for "act on this", not two */
.item.alert-item::before {
  content: ""; position: absolute; left: 15px; top: 17px;
  width: 21px; height: 21px; box-sizing: border-box;
  border-radius: 50%; border: 1.8px solid currentColor; color: var(--warn);
  background:
    linear-gradient(currentColor, currentColor) 50% 3.4px / 2px 7px no-repeat,
    linear-gradient(currentColor, currentColor) 50% 13.2px / 2px 2px no-repeat;
}
.item.alert-item:hover { background: var(--warn-dim); border-color: var(--warn); }
.item.alert-item .top b:first-child { color: var(--warn); }

.stack { display: flex; flex-direction: column; gap: 10px; }
.inline { display: flex; flex-wrap: wrap; gap: 9px; }
.inline .btn { flex: 1 1 auto; }
/* A strip of secondary controls under a screen title. They do NOT stretch:
   a lone "Statement" button spread across the width reads as this screen's
   primary action, and the primary action here is reading the list. */
.toolbar { margin-bottom: 14px; }
.toolbar .btn { flex: 0 0 auto; }
/* the API's own ceiling, said out loud where the next page would be */
.cap-note { text-align: center; padding: 6px 0 2px; }
.date-head {
  position: sticky; top: var(--topbar-h); z-index: 1;
  padding: 12px 0 6px; background: var(--card);
  font-size: 12.5px; font-weight: 700; color: var(--tx3); letter-spacing: .4px;
  text-transform: uppercase;
}
.addr {
  font-family: var(--mono); font-size: 13.5px; background: var(--bg2);
  border: 1px solid var(--line); border-radius: var(--r-ctl); padding: 13px;
  overflow-wrap: anywhere; cursor: pointer;
}

/* ---------------------------------------------------------- the account */
/* One signed-in device. The same two-lines-left, action-right shape as a
   transaction row, so the screen that ends a stranger's session reads like
   the rest of the wallet rather than like a settings dump. */
.sesrow {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 0; border-bottom: 1px solid var(--line);
}
.sesrow:last-of-type { border-bottom: 0; }
.sesrow .who { flex: 1; min-width: 0; }
.sesrow .who b { display: block; font-size: 16px; font-weight: 600; }
.sesrow .who small { display: block; color: var(--tx2); font-size: 13px; margin-top: 2px; }
.sesrow .btn { flex: none; }
.sesrow .here {
  flex: none; padding: 3px 10px; border-radius: var(--r-pill);
  background: var(--acc-dim); border: 1px solid var(--line-2);
  font-size: 12px; font-weight: 700; color: var(--tx);
}

/* --------------------------------------------------------------- signin */
#login {
  position: relative;
  min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px 18px;
}
#login .card {
  width: 100%; max-width: 420px; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 26px 24px;
  box-shadow: var(--shadow);
}
/* the same aura as the balance, once, behind the one card a stranger sees
   first - it is what separates "designed" from "a form on a dark page" */
#login::before {
  content: ''; position: absolute; inset: 0; background: var(--aura);
  pointer-events: none; z-index: -1;
}
#login .mark { font-size: 24px; font-weight: 800; letter-spacing: -0.6px; }
#login .tag { color: var(--tx2); margin: 4px 0 20px; }
#login h1 { margin-bottom: 2px; }
#login .flip { margin-top: 10px; }

/* ---------------------------------------------------------- statement */
.statement { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px; }
.statement .stmt-for { color: var(--tx); font-size: 17px; font-weight: 700; letter-spacing: -0.01em; }
.statement table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.statement th, .statement td { padding: 8px 6px; border-bottom: 1px solid var(--line); text-align: left; }
.statement th {
  font-size: 11.5px; font-weight: 700; color: var(--tx3);
  text-transform: uppercase; letter-spacing: .05em; white-space: nowrap;
}
.statement td.num, .statement th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.statement tbody tr:last-child td { border-bottom: 0; }
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ---------------------------------------------------------------- motion */
/* One block, at the end, so it wins: nothing in this wallet animates for a
   customer who has asked their machine to stop moving things. */
@media (prefers-reduced-motion: reduce) {
  .skel { animation: none; background: var(--skel); }
  .btn[aria-busy="true"]::before { animation: none; }
  .actions a:active, .btn:active { transform: none; }
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

@media print {
  #topbar, nav#tabs, .btn, #offlinebar, .no-print { display: none !important; }
  body { background: #fff; color: #000; }
  .statement { border: 0; padding: 0; }
  .statement th, .statement td { border-bottom: 1px solid #ccc; }
}

/* --------------------------------------------------------- wider screens */
@media (min-width: 720px) {
  #shell { padding-bottom: 24px; }
  .wrap { max-width: 640px; padding: 0 24px; }
  nav#tabs {
    position: static;
    max-width: 640px; margin: 0 auto 6px; border: 1px solid var(--line);
    border-radius: 14px; background: var(--card);
  }
  nav#tabs a { flex-direction: row; gap: 8px; font-size: 13px; padding: 12px 6px; }
  /* the glyph sits BESIDE the label here, so the pill is snug rather than
     thumb-sized: the phone's 46px mark steals 25px a column from the label
     and truncates "Standing" on a 640px bar. Same shape, same fill. */
  nav#tabs a .tabmark { min-width: 0; padding: 0 7px; }
  .date-head { top: 0; }
}
@media (min-width: 380px) {
  .actions a { font-size: 13px; }
}


/* ===================================================== the money flows (start)
 * Send, cash out, buy, a bill, a fine and a standing order: the amount hero,
 * the review, the four outcomes and the biller list.
 *
 * These rules were written in web/lib/flowStyle.js and injected as a <style>
 * from each screen's mount(), because app.css and the flow screens were being
 * built at the same time by two people. That cost a real defect while it
 * lasted - the sheet only arrived once a customer had opened Send, so a chip
 * on a history row could change appearance mid-session - and it is gone: the
 * bytes are here, they ship with the page, and nothing in this wallet
 * appends a stylesheet at runtime any more.
 *
 * TWO RULES THIS BLOCK KEEPS, and test/webFlows.render.test.ts reads them off
 * these exact bytes:
 *   1. NOT ONE COLOUR IS WRITTEN HERE. Every colour is a var() on a token
 *      declared in the two theme blocks at the top of this file, so
 *      test/a11y.test.ts's contrast pass over the token values is still the
 *      whole truth about this surface.
 *   2. STATE IS NEVER COLOUR ALONE. A result carries a word written in the
 *      markup and a SHAPE: PENDING is a solid ring with a clock, UNKNOWN is
 *      a DASHED ring with a literal '?'. That difference is what decides
 *      whether somebody pays the same bill twice.
 *
 * The state vocabulary itself (.state, .pill) is NOT here: it is above, in
 * one place, and the unknown result mark below deliberately never wears
 * .result-ic, which is the GREEN circle.
 * ------------------------------------------------------------------------ */
/* ------------------------------------------------------------ the rail */
/* Four panes, and which one you are standing in. Drawn as segments rather
   than numbered circles: it is orientation, not a component competing with
   the button. The sentence next to it is what a screen reader reads. */
.fl-steps { display: flex; gap: 6px; margin: 0 0 18px; padding: 0; list-style: none; }
.fl-steps li { flex: 1 1 0; height: 3px; border-radius: var(--r-pill, 999px); background: var(--line-2); }
.fl-steps li.on { background: var(--acc); }

/* ---------------------------------------------------------- the amount */
/* The figure a customer is deciding about, at the scale it deserves. It is
   the balance hero's type one step down - the balance stays the largest
   figure in the product - left aligned, tabular, with the unit beside it as
   a smaller, quieter mark exactly as .balance .big does it. There is NO aura
   behind it: DESIGN.md puts the aura behind the balance block and nowhere
   else. */
.amount-box.fl-amount {
  align-items: baseline; gap: 12px;
  border-bottom: 1px solid var(--ctl-line);
  padding-bottom: 8px;
  transition: border-color .16s ease;
}
.amount-box.fl-amount:focus-within { border-color: var(--acc); }
.amount-box.fl-amount input {
  flex: 1; min-width: 0;
  border: 0; border-radius: 0; background: none; padding: 0;
  min-height: 56px;
  font-size: clamp(32px, 10.5vw, 42px); font-weight: 800; letter-spacing: -.028em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.amount-box.fl-amount b { font-size: 17px; font-weight: 700; color: var(--tx2); }
/* a readOnly box is the biller's figure, not the customer's: it says so by
   being quiet rather than by looking broken */
.amount-box.fl-amount input[readonly] { color: var(--tx2); }

/* Shortcuts, not claims. A pill fills the box with an amount the customer
   then confirms against a price the SERVER issues - it says nothing about
   their balance, their limit or what they can afford, and nothing is
   rendered from it until the quote comes back. */
.fl-qa { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; padding: 0; list-style: none; }
.fl-qa button {
  min-height: 44px; padding: 10px 15px;
  border: 1px solid var(--ctl-line); border-radius: var(--r-pill, 999px);
  background: var(--card); color: var(--tx);
  font-size: 14.5px; font-weight: 700; font-variant-numeric: tabular-nums;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: transform .14s ease, background .14s ease, border-color .14s ease;
}
.fl-qa button:hover { background: var(--card2); }
.fl-qa button:active { transform: scale(.97); }
.fl-qa button[aria-pressed="true"] {
  background: var(--acc); border-color: var(--acc); color: var(--on-acc);
}

/* ---------------------------------------------------------- the review */
/* Spacious on purpose. This is the one screen where a customer is reading
   numbers to decide, and a row every 9px reads as a receipt printed to save
   paper. */
.pane.fl-review .kv { padding: 12px 0; }
.pane.fl-review .rule { margin: 14px 0; }

/* The exact cost, as the figure and not as a row. Its label is plain 13/600
   and NOT an uppercase eyebrow: DESIGN.md gives the surface exactly one
   eyebrow and the balance has it. */
.fl-total { padding: 4px 0 2px; }
.fl-total .k { display: block; font-size: 13px; font-weight: 600; color: var(--tx2); }
.fl-total b {
  display: block; margin-top: 6px;
  font-size: clamp(28px, 8.5vw, 36px); font-weight: 800; letter-spacing: -.026em;
  line-height: 1.05; font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
/* Shillings are the unit of thought, so the figure that leaves the wallet
   carries its shilling equivalent - floored to whole shillings, at the rate
   the server published on this very quote, and labelled 'about'. */
.fl-total small {
  display: block; margin-top: 7px;
  color: var(--tx2); font-size: 14px; font-variant-numeric: tabular-nums;
}

/* How long the price stands. A chip rather than a sentence, because the
   number inside it changes every second and a sentence that reflows is
   noise. The expired state changes the WORD as well as the colour. */
.fl-hold {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 14px; border-radius: var(--r-pill, 999px);
  border: 1px solid var(--line); background: var(--card);
  color: var(--tx2); font-size: 13px; font-variant-numeric: tabular-nums;
}
.fl-hold svg { flex: none; }
.hold.expired .fl-hold {
  background: var(--warn-dim); border-color: transparent;
  color: var(--warn); font-weight: 700;
}

/* ------------------------------------------------------------- the PIN */
.pin-pane .fl-pinamount b { font-size: clamp(28px, 8.5vw, 34px); letter-spacing: -.026em; }
.pin-pane .fl-pinamount .fl-tzs {
  display: block; margin-top: 6px; color: var(--tx2); font-size: 14px;
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------- the result */
/* FOUR outcomes, and only one of them is a tick.
 *
 * app.css owns three of them - the filled circle, the check that draws
 * itself, and the amber circle that PENDING and FAILED get instead. This
 * file adds only the fourth, because it did not exist before: UNKNOWN, the
 * outcome of a payment whose answer never arrived.
 *
 * It is the only mark that is NOT a solid disc: an unfilled, DASHED ring
 * carrying a warning mark AND a literal '?', which is the same vocabulary
 * .state.unknown uses one line down the page and the ops console uses in
 * .pill.t-unknown. A customer who reads "we do not know" as "it failed"
 * pays the same money twice, so the two never share a shape.
 *
 * The markup gives this mark ONLY fl-mark, never result-ic, so a browser
 * that failed to apply this stylesheet falls back to a bare mark under an
 * honest word rather than to .result .result-ic, which is the GREEN one.
 * A dropped stylesheet must not be able to turn "we do not know" into a tick.
 */
.result.unknown .fl-mark {
  width: 66px; height: 66px; margin: 0 auto 14px;
  border-radius: 50%; border: 2px dashed var(--warn);
  display: flex; align-items: center; justify-content: center; gap: 1px;
  background: none; color: var(--warn);
}
.result.unknown .fl-mark::after {
  content: "?"; font-size: 21px; font-weight: 800; line-height: 1;
}
.result .fl-note { margin-bottom: 16px; }
/* The sentence a customer must not skim past: it is the one that stops the
   second payment. It is app.css's own .warn-note - amber ground and an amber
   `!` in a ring, the shape this surface already uses for "act on this before
   you go on" - and this rule only stops the result pane's centring from
   turning it into a poster. (It said "amber rail" until the rail came off:
   a 3px bar down one edge is a side-stripe, and it was the only thing
   separating this note from the tier note, by hue alone.) */
.result .fl-loud {
  max-width: 36ch; margin-left: auto; margin-right: auto; margin-bottom: 16px;
  text-align: left;
}
.result .fl-receipt { text-align: left; margin-top: 4px; }
.result .fl-receipt .kv { padding: 11px 0; border-bottom: 1px solid var(--line); }
.result .fl-receipt .kv:last-child { border-bottom: 0; }

/* ------------------------------------------------------------ listings */
/* The biller list is a choose step, so its rows read as one: a leading badge
   carrying the name's own initial - never a logo we do not have a licence to
   - and a chevron saying it leads somewhere. */
.item.fl-pick { display: flex; align-items: center; gap: 13px; min-height: 62px; padding: 13px 14px; }
.item.fl-pick .fl-badge {
  flex: none; width: 38px; height: 38px; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  background: var(--acc-dim); color: var(--tx);
  font-size: 15px; font-weight: 800;
}
.item.fl-pick .fl-who { flex: 1; min-width: 0; }
.item.fl-pick .fl-chev { flex: none; color: var(--tx3); }

/* A card for one thing a customer can act on: a fine, a run that failed. */
.fl-card { padding: 15px; margin-bottom: 12px; }
.fl-card .fl-lead { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; }
.fl-card .fl-lead b {
  font-size: clamp(22px, 6.5vw, 26px); font-weight: 800; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.fl-card .fl-lead span { color: var(--tx2); font-size: 14px; }

/* An outcome that is not finished, stated inline. The metrics are .err's and
   .ok-msg's to the pixel, because it belongs to that family: it is the third
   answer a form can give, and the one those two never had. Amber and a
   clock, never green. */
.fl-inline {
  display: flex; align-items: flex-start; gap: 9px;
  border-radius: 14px; padding: 12px 14px; margin-top: 14px;
  font-size: 14.5px; line-height: 1.45;
  background: var(--warn-dim); color: var(--warn);
}
.fl-inline svg { flex: none; margin-top: 1px; }
.fl-inline b { display: block; font-weight: 700; }
.fl-inline.fl-unknown {
  background: none; border: 1px dashed var(--warn);
}

/* --------------------------------------------------------------- motion */
/* Every animation above is decoration on top of a finished state, so the OS
   saying "no" simply lands on that state. */
@media (prefers-reduced-motion: reduce) {
  .amount-box.fl-amount,
  .fl-qa button { transition: none; }
  .fl-qa button:active { transform: none; }
}

/* ======================================================= the money flows (end) */
