/* ═══ Vantage — the Phase B design system, ported from the frozen mockup
   (design/vantage.html, FROZEN 24-Jul-26). Tokens + shell + the shared
   components. Screen-specific state classes stay namespaced per screen
   (the lg-ready collision lesson); derived color-mix tokens must live
   where --acc/--attn are defined or the mix silently falls back. ═══ */

:root{
  --brand:#f0b429;
  --pos:#43c08a; --neg:#e0768f; --warn:#e0b552; --info:#7fa8d4;
  /* hard-tag hues — refund green (money returned) · transfer azure (your own
     money in motion) · recurring violet · investment teal · excluded slate.
     Custom tags stay --info. */
  --t-refund:#43c08a; --t-transfer:#4d9fff; --t-recurring:#a78bfa;
  --t-investment:#22c1a6; --t-excluded:#7f8ba3;
  /* pending bronze, deliberately NOT the excluded slate: "doesn't count"
     and "isn't settled yet" are different facts, and pending money very
     much counts (verdict 129). Two facts can't wear one colour. */
  --t-pending:#b8935a;
  --mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --r:10px; --r2:6px; --r3:4px;
  /* THE LAYER LADDER (audit §8's component gap). Every stacking level is named
     here, in order, because the numbers were being guessed one at a time — and
     a guess had already put the ? card UNDER the why sheet, so pressing ? over
     an open why rendered the card behind it. Add a layer by naming it here,
     where its neighbours are visible, never by picking a number at the call
     site. Reading up: the veil dims home, the floating chrome sits above it but
     below a sheet, a sheet's own ✕ and snackbars ride above the sheet, menus
     above those, and the two summonable overlays are last — the ? card topmost,
     since it can be called from inside a why and takes Esc first. The DOCK is
     the phone's chrome, so it sits with the chrome — above the page, below a
     sheet, and below the snackbar and the menu that must reach over it. */
  --z-veil:60; --z-chrome:62; --z-dock:63; --z-sheet:71; --z-sheet-x:73; --z-snack:74;
  --z-menu:92; --z-why:96; --z-keys:97;
  /* MOTION (audit §8). Three speeds, named for the job rather than the number:
     a hover or reveal (.14s) is the app's default and by far the most used, a
     card lifting or a colour settling (.2s) is the slower one, and a breath
     (.3s) is for the first-run mark. .16s was a one-off that read as .14s. */
  --t-fast:.14s; --t-med:.2s; --t-slow:.3s;
  /* DRIFT's row height, a token because the bar's warm-up gradient computes
     its stops from it. */
  --dr-h:46px;
  /* THE WARM-UP (owner, 25-Jul-26: "have them all warm up to the color").
     A bar against a POCKET stays cool for its first stretch and warms into the
     money-out amber as it approaches its line — so the approach is visible, not
     just the crossing. `--warm-from` is where cool ends, as a fraction of the
     pocket: before it the bar is calm, which is what makes the warming mean
     something. An OVER bar sets it to 0 and warms across its whole length, so
     "past the line" still reads hotter than "right at it".
     NOT applied to goals — see `.goal-bar`.
     UNITLESS on purpose: it is a multiplier, and drift multiplies it by a length.
     As `55%` the drift stop computed to `calc(39.6% * 92px * (1 / 100%))` — an
     unresolvable mix of units, because calc may only divide by a NUMBER. Caught
     by reading the computed value; it looked fine in the source. */
  --warm-from:.55;
}
body{
  --side:#0a0d13; --canvas:#0f1218; --card:#151924; --card2:#1c2130;
  --line:rgba(170,200,255,.08); --line2:rgba(170,200,255,.16);
  --tx:#e4e9f4; --tx2:#93a0b6; --tx3:#7c89a6;
  --acc:#4d9fff; --acc2:#79b6ff; --onacc:#06162a;
  --attn:#4d9fff; --attn2:#79b6ff; --onattn:#06162a;
  --chart:#4d9fff;
  --accsoft:color-mix(in srgb,var(--acc) 12%,transparent);
  --accline:color-mix(in srgb,var(--acc) 40%,transparent);
  --attnsoft:color-mix(in srgb,var(--attn) 11%,transparent);
  --attnline:color-mix(in srgb,var(--attn) 38%,transparent);
  --brand-fill:var(--brand);
  /* drift's neutral bar. It lives HERE and not in :root for the documented
     reason: a color-mix over --tx silently falls back where --tx isn't defined. */
  --dr-base:color-mix(in srgb,var(--tx) 22%,transparent);
}
/* NO LIGHT THEME — owner reversal of verdict 2 (15-Jul-26): Prowl is dark, full stop. */
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{background:var(--canvas);color:var(--tx);font:13px/1.55 var(--mono);
     font-variant-numeric:tabular-nums;-webkit-font-smoothing:antialiased}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
input,select{font:inherit;color:inherit}
a{color:inherit;text-decoration:none}
.n{font-variant-numeric:tabular-nums;letter-spacing:-.01em}
[hidden]{display:none!important}
.kc{display:inline-block;border:1px solid var(--line2);border-bottom-width:2px;border-radius:4px;
    padding:0 5px;font-size:11px;line-height:16px;color:var(--tx3);vertical-align:1px}

/* ---------- shell (Perch) ---------- */
.shell{display:flex;flex-direction:column;min-height:100vh}
.controls{position:absolute;top:-2px;right:0;z-index:var(--z-chrome);display:flex;align-items:center;gap:8px}
.cbtn{position:relative;display:grid;place-items:center;width:34px;height:34px;border-radius:var(--r2);color:var(--tx2)}
.cbtn svg{width:17px;height:17px}
.cbtn:hover{background:color-mix(in srgb,var(--tx) 6%,transparent);color:var(--tx)}
.cbtn.here{background:color-mix(in srgb,var(--tx) 8%,transparent);color:var(--tx)}
.controls .badge{position:absolute;top:-3px;right:-1px;background:var(--attn);color:var(--onattn);
                 font-size:11px;font-weight:700;min-width:16px;height:16px;border-radius:8px;
                 display:grid;place-items:center;padding:0 4px}
.cslash{font:600 15px var(--mono);line-height:1;margin-right:-4px}
/* THE REPORT EYE (owner, 25-Jul-26) — what prowl just did, waiting to be
   opened. Neutral, not amber: in the cluster the eye is chrome like its four
   neighbours, and amber here would read as a money figure. The mark is wider
   than it is tall, so it takes the cbtn's box and sizes to its own ratio
   rather than the square 17px its neighbours use. */
.creport svg{width:19px;height:auto}
.creport:hover{color:var(--acc2)}
main{flex:1;min-width:0;padding:32px 44px 96px}
.wrap{max-width:1200px;margin:0 auto;position:relative}
.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:22px;padding-right:210px}
h1{font-size:16px;font-weight:600;letter-spacing:.02em}
.sub{color:var(--tx3);font-size:11px;margin-top:4px}

.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
     background:var(--acc);color:var(--onacc);font-weight:700;border-radius:var(--r2);
     padding:9px 14px;transition:background var(--t-fast)}
.btn:hover{background:var(--acc2)}
.btn-q{background:var(--card2);color:var(--tx);font-weight:500;border:1px solid var(--line2)}
.btn-q:hover{background:color-mix(in srgb,var(--card2) 86%,#fff 14%)}
.btn-g{background:none;color:var(--tx2);font-weight:500}
.btn-g:hover{color:var(--tx)}

.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r);
      box-shadow:0 1px 2px rgba(0,0,0,.25)}
.card-h{display:flex;align-items:center;justify-content:space-between;gap:12px;
        padding:13px 18px;border-bottom:1px solid var(--line)}
.card-h b{font-size:11px;font-weight:500;color:var(--tx3);letter-spacing:.06em}
.card[data-go]{cursor:pointer;position:relative;transition:border-color var(--t-med) ease,box-shadow var(--t-med) ease}
.card[data-go]:hover{border-color:rgba(170,200,255,.34);
      box-shadow:0 1px 2px rgba(0,0,0,.25), 0 0 0 1px rgba(170,200,255,.12)}
.card:has(.card-h .door[data-go]){transition:border-color var(--t-med) ease,box-shadow var(--t-med) ease}
.card:has(.card-h .door[data-go]):hover{border-color:rgba(170,200,255,.34);
      box-shadow:0 1px 2px rgba(0,0,0,.25), 0 0 0 1px rgba(170,200,255,.12)}
/* the door arrow — one affordance for every card that opens somewhere, visible at rest */
.door{color:var(--tx3);font-size:14px;line-height:1;transition:color var(--t-fast)}
.card[data-go]:hover .door,.card[data-go]:focus-visible .door,button.door:hover,a.door:hover{color:var(--acc2)}
button.door,a.door{cursor:pointer;padding:0 2px}
.hbtns{display:flex;align-items:center;gap:10px}

/* ---------- home: stance hero ---------- */
.topgrid{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-bottom:18px}
.topgrid>.card{margin-bottom:0}
.hero{padding:22px 24px 20px;display:flex;flex-direction:column}
.hero .lab{font-size:11px;color:var(--tx3);letter-spacing:.06em;margin-bottom:6px}
.hero .big{font-size:36px;font-weight:600;letter-spacing:-.02em}
.hero .cap{font-size:11px;color:var(--tx3);margin-top:4px}
.hero .door{position:absolute;top:18px;right:20px}
.acct-strip{display:flex;flex-direction:column;margin-top:18px}
.acct{display:flex;justify-content:space-between;align-items:baseline;gap:12px;
      padding:11px 0;border-top:1px solid var(--line)}
.acct .nm{font-size:13px;color:var(--tx2)}
.acct .bal{font-size:13px;font-weight:600}
/* balances by what the account IS: debit azure, credit amber (a card balance IS money out) */
.bal-d{color:var(--acc2)}
.bal-c{color:var(--brand)}

/* provenance badge: one boxed letter — P statements · S connected; ↻ rollover wears the shape too */
/* 11px, not 10: the token pass set 11px as the floor for chrome text and this
   badge was the one place still under it (measured on the accounts sheet). The
   16px box was already big enough for the extra pixel. */
.ps{display:inline-grid;place-items:center;min-width:16px;height:16px;padding:0 3px;margin-left:7px;
    border:1px solid var(--line2);border-radius:4px;font:600 11px var(--mono);color:var(--tx3);
    vertical-align:1px;cursor:help;text-transform:none}
.ps.sp{border-color:var(--accline);color:var(--acc2)}
.ps.rollb{color:var(--acc2);border-color:var(--accline);font-size:11px}

/* ---------- home: the tracks (pulse) card ---------- */
.flow{display:flex;flex-direction:column}
.pulse{padding:4px 8px 10px}
.plrow{display:flex;align-items:center;gap:16px;width:100%;text-align:left;padding:10px 10px;
       border-bottom:1px solid var(--line);border-radius:var(--r3)}
.plrow:last-child{border-bottom:0}
.plrow:has(+ .plcont){border-bottom:0}
.plrow:hover{background:color-mix(in srgb,var(--tx) 5%,transparent)}
.plrow .plnm{width:72px;font-size:12px;color:var(--tx2)}
.plrow svg{flex:none}
.plrow .plval{margin-left:auto;font-size:13px;color:var(--tx);font-weight:600}
.plrow .pldl{width:150px;text-align:right;font-size:11px;color:var(--tx3)}
.pl-spark{flex:none}
.pl-spark polyline{stroke:var(--acc)}
.pl-pair{flex:none;width:96px;display:flex;flex-direction:column;gap:4px}
.pl-pair i{display:block;height:6px;border-radius:2px}
.pl-pair i.in{background:var(--acc)}
.pl-pair i.out{background:var(--brand-fill)}
.pl-track{flex:none;width:96px;height:6px;border-radius:3px;
          background:color-mix(in srgb,var(--tx) 8%,transparent);overflow:hidden}
.pl-track i{display:block;height:100%;border-radius:3px;background:var(--acc)}
.pl-mo{flex:none;width:96px;height:16px;display:flex;align-items:flex-end;gap:2px}
.pl-mo i{flex:1;background:var(--acc);border-radius:1px;opacity:.85}
.pl-week{flex:none;width:96px;display:flex;gap:3px}
.pl-week i{flex:1;height:10px;border-radius:2px;background:color-mix(in srgb,var(--tx) 6%,transparent)}
.pl-week i.l1{background:color-mix(in srgb,var(--brand-fill) 16%,transparent)}
.pl-week i.l2{background:color-mix(in srgb,var(--brand-fill) 34%,transparent)}
.pl-week i.l3{background:color-mix(in srgb,var(--brand-fill) 62%,transparent)}
.pl-week i.l4{background:var(--brand-fill)}
.pulse-home{padding:0 6px 6px}
.pulse-home .plrow{gap:10px;padding:15px 8px}
.pulse-home .plnm{width:54px;font-size:11px}
.pulse-home .pl-pair,.pulse-home .pl-track,.pulse-home .pl-week,.pulse-home .pl-mo{width:56px}
/* a fact not yet earned: the row waits quietly (verdict 96's earned-range rule at row scale) */
.plrow.pl-wait .plval{color:var(--tx3);font-weight:500}
.plrow.pl-wait:hover{background:none}

/* ---------- home: the grid2 cards (categories · recurring · merchants) ---------- */
.grid2{display:grid;grid-template-columns:4fr 3fr 3fr;gap:18px}
.grid2>.card{display:flex;flex-direction:column;height:412px;position:relative}
.grid2>.card::after{content:"";position:absolute;left:1px;right:9px;bottom:1px;height:32px;
  pointer-events:none;border-radius:0 0 var(--r) var(--r);
  background:linear-gradient(transparent,var(--card))}
.grid2 .w-body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
               scrollbar-width:thin;scrollbar-color:transparent transparent}
.grid2 .w-body::-webkit-scrollbar{width:8px}
.grid2 .w-body::-webkit-scrollbar-thumb{background:transparent;border-radius:8px}
.grid2 .card:hover .w-body{scrollbar-color:var(--line2) transparent}
.grid2 .card:hover .w-body::-webkit-scrollbar-thumb{background:var(--line2)}
.grid2 .li:last-child,.grid2 .env:last-child{border-bottom:0}
.w-body{padding:14px 18px 16px}
.env{display:flex;align-items:center;gap:12px;padding:8px 0}
/* min-width, not width: a name that carries the ↻ roll-over badge can run past
   132px (measured: "subscriptions ↻" = 133px), and a fixed cell clipped the
   badge by 5px on the home card. The bar is the flexible partner, so letting
   the name breathe costs bar length, never the layout.

   AND the cell is a flex ROW, because giving it room is not enough on a phone,
   where the card is narrow whatever the desktop does (measured 28-Jul-26: 128px
   of box against 133px of content, so the ellipsis ate the ↻). The name is the
   only part allowed to truncate — `.nmt` takes the ellipsis, the badge is
   `flex:none` and always drawn. THE RULE, already law for tag chips: a name is
   a label and may be cut; a mark is MEANING and may not. */
.env .nm{min-width:132px;max-width:170px;flex:none;font-size:13px;color:var(--tx2);white-space:nowrap;
         display:flex;align-items:center;gap:5px}
.env .nmt{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.env .nm .rollb{flex:none}
.env .bar{position:relative;flex:1;height:5px;border-radius:3px;background:color-mix(in srgb,var(--tx) 7%,transparent);overflow:hidden}
/* THE SAME WARM-UP, SIDEWAYS (owner, 25-Jul-26: "do all the bars actually").
   A pocket bar is cool through its first stretch and warms into the money-out
   amber as the spend approaches the pocket — so you can see a pocket filling
   up, not only a pocket already crossed.

   THE SAME TRAP AS DRIFT, on the other axis: a gradient's percentage stops
   resolve against the element they are painted on, and here that element is the
   FILL, whose width is the spend. Left alone, every bar would show the whole
   ramp squeezed into itself and every tip would look equally amber — a bar at
   10% would read exactly as hot as one at 99%. So the gradient IMAGE is sized
   to the track instead (`--fwn` is the fill's width as a plain number, written
   beside the width itself), which puts the stops back in the pocket's own
   coordinate space where they mean something. */
.env .bar i,.pockets .use .bar i{display:block;border-radius:3px;
  background-image:linear-gradient(to right,
    var(--acc) 0, var(--acc) calc(var(--warm-from) * 100%), var(--warn) 100%);
  background-size:calc(100% / var(--fwn,100) * 100) 100%;
  background-repeat:no-repeat}
.env .bar i{height:100%}
/* PAST the line, the warming starts at the very beginning: an over-pocket bar
   is clamped at full width, so without this it would look identical to one
   sitting exactly ON its line. Temperature keeps the two apart. */
.env.warn .bar i,.pockets .use.warn .bar i{--warm-from:0}
/* rolled-over cash is spent first: the dark stub at left:0 is the rolled proportion, capped at the spend */
.env .bar u{position:absolute;left:0;top:0;height:100%;border-radius:3px;
            background:color-mix(in srgb,var(--acc) 55%,#000)}
.env.warn .bar u{background:color-mix(in srgb,var(--warn) 55%,#000)}
.env .amt{width:150px;flex:none;text-align:right;font-size:11px;color:var(--tx2)}
.cats-card{container-type:inline-size}
/* SQUISHED: below 520px of card the " of $X" half drops — the bar already draws the pocket */
@container (max-width: 520px){
  .cats-card .env .amt{width:auto}
  .cats-card .env .amt .of{display:none}
  .cats-card .env .nm{min-width:128px}
}
.li{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--line);font-size:13px}
.li:last-child{border-bottom:0}
.li .nm{flex:1}
.li .amt{color:var(--tx2)}

/* ---------- transactions table (shared by the home preview and the coming sheet) ---------- */
table{width:100%;border-collapse:collapse}
th{font-size:11px;font-weight:500;color:var(--tx3);text-align:left;padding:9px 12px;
   border-bottom:1px solid var(--line2)}
th.r,td.r{text-align:right}
td{padding:10px 12px;border-bottom:1px solid var(--line);font-size:13px;vertical-align:middle}
tr:last-child td{border-bottom:0}
tbody tr{transition:background var(--t-fast)}
tbody tr:hover{background:color-mix(in srgb,var(--tx) 3%,transparent)}
.date{font-size:11px;color:var(--tx3);white-space:nowrap}
.cat{color:var(--tx2)}
.exc{color:var(--attn);cursor:help}
/* amounts: azure-in / amber-out, only where direction varies row-to-row */
.in{color:var(--acc2)}
.out{color:var(--brand)}
.tag{display:inline-flex;font-size:11px;color:var(--info);
     border:1px solid color-mix(in srgb,var(--info) 35%,transparent);border-radius:99px;padding:2px 7px;margin-left:8px}
.stag{display:inline-flex;font-size:11px;color:var(--tx3);border:1px dashed var(--line2);
      border-radius:99px;padding:2px 7px;margin-left:8px}
/* hard-tag colours ride both bases; solid-vs-dashed still tells who set the tag */
.t-refund{color:var(--t-refund);border-color:color-mix(in srgb,var(--t-refund) 42%,transparent)}
.t-transfer{color:var(--t-transfer);border-color:color-mix(in srgb,var(--t-transfer) 42%,transparent)}
.t-recurring{color:var(--t-recurring);border-color:color-mix(in srgb,var(--t-recurring) 46%,transparent)}
.t-investment{color:var(--t-investment);border-color:color-mix(in srgb,var(--t-investment) 44%,transparent)}
.t-excluded{color:var(--t-excluded);border-color:color-mix(in srgb,var(--t-excluded) 42%,transparent)}
.t-pending{color:var(--t-pending);border-color:color-mix(in srgb,var(--t-pending) 44%,transparent)}
/* the merchant sheet: the bank's own wording under the tidied name. Kept
   visible, never replaced — it is the evidence every match is computed from
   (hard rule 2), and it is how you recognise a shop the tidy-up couldn't. */
.um-raw{font-size:11px;color:var(--tx3);margin-top:3px;letter-spacing:.02em}
.um-pick{cursor:pointer}
/* measured: without the clearance the sheet's ✕ sits ON TOP of "looks right",
   so clicking the button's right edge closes the sheet instead of accepting */
.um-head{padding-right:52px;margin-bottom:16px}
/* a category prowl guessed, not one you answered — dimmed until accepted,
   the same "this is prowl's, not yours" distinction the dashed tags carry */
.um-guess .catw{opacity:.62}
td.nm .tag,td.nm .stag{margin-left:16px}
.tag button{background:none;border:0;padding:0;color:inherit;font:inherit;cursor:pointer;letter-spacing:inherit}
.tag .tagx{margin-left:6px;color:var(--tx3)}
.tag .tagx:hover{color:var(--tx)}
a.stag:hover{color:var(--acc2);border-color:var(--accline)}
tr.dead td{opacity:.42}
tr.dead .amt,tr.dead .nm{text-decoration:line-through}
tr.dead td:last-child{opacity:1}
tr.mdiv td{padding:0;height:0;line-height:0;border:0;border-top:1px solid rgba(170,200,255,.34)}
tr.mdiv td *{display:none}
tr:has(+ tr.mdiv) td{border-bottom:0}
/* one cue for "this opens a place": the fitted azure box (the targeting law) */
/* ═══ THE PILL — one geometry for every link that highlights on hover ═══
   (Sweep #4 of the one-component rule, owner 26-Jul-26: "do all of them".)
   Five near-copies of `radius + 1px pad + negative margin + hover fill` lived
   in five places with two paddings and three hover colours — variation that
   was accidental, not chosen. The GEOMETRY is one rule now; colour stays with
   each context, because faint chrome (whisper) and azure prose (.legal a) are
   different jobs sharing one shape. */
.wdoor,.whisper a,.au-links a,.legal-links a,.legal a,.ltop-door a{
  border-radius:var(--r3);padding:1px 7px;margin:0 -7px;
  transition:background var(--t-fast),color var(--t-fast)}
.wdoor{display:inline-block;cursor:pointer}
.wdoor:hover,.wdoor:focus-visible{background:var(--accsoft);color:var(--acc2)}
/* the home transactions preview scrolls the month in place */
.tscroll{max-height:300px;overflow-y:auto;margin:0 -4px;padding:0 4px}
.tscroll::-webkit-scrollbar{width:10px}
.tscroll::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--tx) 13%,transparent);border-radius:5px;border:3px solid transparent;background-clip:content-box}
.tscroll::-webkit-scrollbar-track{background:transparent}

/* THE WHISPER LINE — the shared stylesheet for templates/_whisper.html.

   Lives in its own file rather than in each entry screen's <style> block
   because the duplication was the bug: two copies drifted into two designs
   (see _whisper.html's comment for the three ways they disagreed).

   Pinned to the BOTTOM EDGE on every entry screen. `position:fixed`, not
   `absolute`: _auth_base's column is not a positioned ancestor, and absolute
   inside it is exactly how the line ended up floating mid-page. Fixed answers
   to the viewport, which is what "the bottom edge" means on all five screens. */
/* the join form at the end of /waitlist's pitch. The page reads in the legal
   column, so the form needs the auth family's field without the auth family's
   320px card — same input, sized to the prose it follows. */
/* ONE field now, not a field stacked over a button (owner, 26-Jul-26). The
   button became the ↵ inside the box — `.efield`, the login's own component —
   so this is just the width the form sits at. */
.joinform{max-width:340px}
.joinerr{margin-top:10px}
.joinnote{margin-top:26px;font-size:13px;color:var(--tx3)}

/* VISUALLY HIDDEN, STILL ANNOUNCED. It lived in two <style> blocks
   (_auth_base and login) and nowhere else, so a `class="sr"` label on any
   OTHER page simply rendered — which is how /waitlist came to print "email"
   above a box whose placeholder already said email. A hiding rule that only
   exists on some pages is worse than no rule: it makes the markup look
   correct while the screen disagrees. One place, like every other rule. */
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
    clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* THE JOIN MOMENT (owner, 26-Jul-26: "centre this and give it some more
   padding, make it more of a moment"). It is the end of a long read and the
   only thing on the page that ASKS for anything, so it stops being another
   left-aligned paragraph and becomes the place the page was walking to.
   Centred, and given room above it rather than a second hairline — the
   footer's own rule sits a few lines below, and two rules that close read as
   noise. */
.joinmoment{margin-top:104px;padding-bottom:80px;text-align:center}
.joinmoment h2{margin-bottom:12px}
.joinmoment p{max-width:440px;margin-left:auto;margin-right:auto}
.joinmoment .joinform{margin:34px auto 0}
.joinmoment .joinerr{margin-left:auto;margin-right:auto;max-width:340px}

/* ═══ THE FIELD THAT ANSWERS ITSELF — one component, every screen ═══
   (Owner, 26-Jul-26: "whenever two items are similar, try to use the same
   item instead of replicating it", stated while asking for the waitlist's
   button to behave like the login's.)

   A single field whose ↵ arrives with the first character, sitting inside the
   box rather than beneath it. The login invented it; the waitlist would have
   COPIED it, which is how the two footers got into the state that produced
   this rule. So it moved out of login.html and lives here, worn by both.

   The state class is `ef-on`, prefixed on purpose: a bare `.ready` and a bare
   `.empty` have each already collided with something else in this stylesheet.
   It sits on the FIELD, not the body, so two of these could share a page. */
.efield{position:relative;width:100%}
/* the box itself is `.au-in`'s, declared once with the auth atoms below the
   case law — this block owns only the ↵ */
.efield .ef-go{position:absolute;right:7px;top:50%;transform:translateY(-50%);
               width:30px;height:30px;border-radius:var(--r3);display:none;place-items:center;
               background:var(--acc);color:var(--onacc);font-size:14px;font-weight:700;
               border:0;cursor:pointer}
.efield.ef-on .ef-go{display:grid}
.efield .ef-go[disabled]{opacity:.5;cursor:default}

/* THE ONE FOOTER LINE (owner, 26-Jul-26, fourth pass: "i want them
   identical"). It is no longer fixed: every host page is a full-height flex
   column and this takes the slack, so a short screen puts it on the bottom
   edge and a long document lets it end the page — one rule that is correct for
   a 320px login card and a privacy policy alike. No hairline above it and no
   `|` between the links: at 11px in the faintest colour on the page a rule and
   a pipe are both debris, and the gap already separates. `.lfoot` used to be a
   second copy of this and is deleted. */
.whisper{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;
         gap:26px;margin-top:auto;padding-top:30px;font-size:11px;color:var(--tx3)}
.whisper a{color:var(--tx3);text-decoration:none}
.whisper a:hover{color:var(--acc2);background:var(--accsoft)}

/* The phone. Four links plus three hairlines need more than 354px of line, and
   a 390px phone minus its gutters IS 354px — measured on the mockup's drawing,
   which is why `flex-wrap` is on above: the line takes a second row rather than
   pushing a link off the screen. It is a whisper at the bottom edge, so two
   faint rows is the honest failure mode. Divergence ledger row 31. */
@media (max-width:560px){
  .whisper{gap:10px;padding:0 14px}
  /* the touch floor without moving the line (audit §3-6, ≥44px on touch): the
     padding grows the tap area to 45px and the negative margin cancels its
     effect on layout, so the 11px ink stays exactly where it was. */
  .whisper a{padding:14px 8px;margin:-14px 0}
}

/* ---------- the failure snackbar (what prowl COULDN'T do) ----------
   Was `.vflash`, a banner above the page: a Phase B invention the frozen
   design never drew, retired 26-Jul-26 on the owner's ruling. Failures now
   speak in the one component everything else prowl says speaks in.
   The stack neutralises `.snack`'s own fixed anchor so several can pile up. */
.snackstack{position:fixed;left:50%;bottom:64px;transform:translateX(-50%);
            z-index:var(--z-snack);display:flex;flex-direction:column-reverse;
            gap:8px;align-items:center;max-width:calc(100vw - 32px)}
.snackstack .snack{position:static;left:auto;bottom:auto;transform:none;max-width:100%}
.snack .prose details{margin-top:6px;font-size:12px;color:var(--tx2)}
.snack .prose summary{cursor:pointer;color:var(--tx3)}
.snack .prose ul{margin:6px 0 0 18px}
/* a failure wears the attention azure, never red (14-Jul): prowl flags, it
   does not alarm. The left stripe the banner used is gone with it — Apple and
   Raycast toasts wear a uniform hairline, which is why v101–110 took the
   stripe off `.snack` in the first place. */
.snack-warning .prose,.snack-error .prose{color:var(--tx)}
.snack-x{margin-left:auto}

/* ---------- sheets over frosted home (the overlay model, verdict 70) ----------
   One anchor: top 10vh, max-height 84vh, width 1000 — import alone sits at 28vh
   (verdict 79). The veil is 50% for destination sheets. ✕ / Esc / veil-click all
   close by navigating home — a sheet IS a route, so back closes and refresh
   restores (verdict 30). */
body.has-overlay{overflow:hidden}
.ovl-veil{position:fixed;inset:76px 0 0 0;background:rgba(6,9,14,.5);
          backdrop-filter:blur(4px);z-index:var(--z-veil);border:0;padding:0;cursor:default}
.vsheet{position:fixed;top:10vh;left:50%;transform:translateX(-50%);
        width:min(1000px,92vw);max-height:84vh;
        background:var(--canvas);border:1px solid var(--line2);border-radius:16px;
        box-shadow:0 40px 120px rgba(0,0,0,.6);padding:26px 30px 34px;z-index:var(--z-sheet);
        display:flex;flex-direction:column;overflow:hidden}
.sbody{flex:1;min-height:0;overflow:auto;margin:0 -30px -34px;padding:2px 30px 34px;
       overscroll-behavior:contain}
/* a sheet LOCKS the page behind it (the mockup's body.has-overlay{overflow:hidden}):
   without this, a wheel that reaches the sheet's end starts dragging the frosted
   home around underneath — measured, the home behind a sheet was 1282px of
   scrollable page. :has() is in every browser prowl supports. */
body:has(.vsheet){overflow:hidden}
.vsheet::-webkit-scrollbar,.sbody::-webkit-scrollbar{width:10px}
.vsheet::-webkit-scrollbar-thumb,.sbody::-webkit-scrollbar-thumb{
  background:color-mix(in srgb,var(--tx) 13%,transparent);border-radius:5px;
  border:3px solid transparent;background-clip:content-box}
.vsheet::-webkit-scrollbar-track,.sbody::-webkit-scrollbar-track{background:transparent}
/* the sheet close is a bare ✕ — no circle chip (v57) */
.ovl-x{position:fixed;top:calc(10vh + 10px);right:calc((100vw - min(1000px,92vw))/2 + 14px);
       z-index:var(--z-sheet-x);width:34px;height:34px;background:none;border:0;display:grid;
       color:var(--tx3);place-items:center;font-size:19px;line-height:1;cursor:pointer}
.ovl-x:hover{color:var(--tx)}

/* the search prompt — the transactions sheet's own bar, pixel-kin of the / spotlight */
.cprompt{display:flex;align-items:center;gap:12px;background:var(--card);border:1px solid var(--acc);
         border-radius:var(--r);padding:15px 18px;box-shadow:0 24px 80px rgba(0,0,0,.55)}
.cprompt .gt{color:var(--acc);width:19px;height:19px;flex:none}
.cprompt input{flex:1;background:none;border:0;outline:none;font-size:16px}
.vsheet .cprompt{margin-bottom:14px}
.cprompt .tmore{background:none;border:0;color:var(--tx3);font-size:16px;line-height:1;padding:0 2px;cursor:pointer}
.cprompt .tmore:hover{color:var(--tx)}

/* sortable table headers: quiet at rest, the active column carries the arrow */
th button.sortb{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:500;
   color:var(--tx3);letter-spacing:.02em;transition:color var(--t-fast)}
th button.sortb:hover{color:var(--tx)}
th button.sortb.on{color:var(--tx)}
th button.sortb .arr{color:var(--acc2);font-size:10px}

/* the category cell is a picker: the fitted neutral box + the caret hint (verdict 91b) */
.catv{cursor:pointer}
.catw{display:inline-flex;align-items:center;gap:6px;border-radius:var(--r3);
      padding:2px 8px;margin:-2px -8px;transition:background var(--t-fast),color var(--t-fast)}
.catw .car{opacity:0;color:var(--tx3);font-size:11px;transition:opacity var(--t-fast)}
tbody tr:hover .catv .catw .car{opacity:1}
td.catv:hover .catw{background:color-mix(in srgb,var(--tx) 8%,transparent);color:var(--tx)}
/* the grayed + after the description: opens add tag · recategorize (neutral box = menu) */
.addann{margin-left:8px;padding:1px 6px;border-radius:var(--r3);font-size:13px;line-height:1;
        color:var(--tx3);opacity:.5;vertical-align:baseline;cursor:pointer;
        transition:opacity var(--t-fast),background var(--t-fast),color var(--t-fast)}
tbody tr:hover .addann{opacity:.8}
.addann:hover{background:color-mix(in srgb,var(--tx) 8%,transparent);color:var(--tx);opacity:1}
.dots{opacity:0;width:24px;height:24px;border-radius:var(--r3);display:inline-grid;place-items:center;
      color:var(--tx2);border:1px solid transparent;font-size:14px;transition:opacity var(--t-fast);cursor:pointer}
tbody tr:hover .dots,.dots:focus-visible{opacity:1}
.dots:hover{border-color:var(--line2);color:var(--tx)}
.undo{width:26px;height:26px;border-radius:var(--r3);display:inline-grid;place-items:center;
      color:var(--tx2);border:1px solid var(--line2);opacity:0;transition:opacity var(--t-fast);cursor:pointer}
.undo:hover{color:var(--acc);border-color:var(--acc)}
tbody tr:hover .undo,.undo:focus-visible{opacity:1}
/* set-aside rows shown by the footer toggle: greyed, badged, no direction colour */
tr.aside td{opacity:.5}
tr.aside td:last-child{opacity:1}

/* the row context menu (⋯ / right-click) — the mockup's .ctx component */
.ctx{position:fixed;z-index:var(--z-menu);min-width:200px;background:var(--card2);border:1px solid var(--line2);
     border-radius:var(--r2);box-shadow:0 16px 48px rgba(0,0,0,.5);padding:5px}
.ctx button{display:flex;width:100%;gap:10px;align-items:center;padding:7px 10px;border-radius:var(--r3);
            font-size:13px;color:var(--tx2);text-align:left;cursor:pointer}
.ctx button:hover{background:color-mix(in srgb,var(--tx) 6%,transparent);color:var(--tx)}
.ctx .sep{height:1px;background:var(--line);margin:5px 4px}
.ctx .ctx-in{width:100%;padding:7px 9px;border-radius:var(--r3);background:var(--card);
     border:1px solid var(--line);color:var(--tx);outline:none;font-size:13px}
.ctx .ctx-in:focus{border-color:var(--acc)}
.ctx .ctx-hint{font-family:var(--sans);font-size:12px;color:var(--tx3);padding:6px 10px 4px;max-width:250px}
.ctx .ctx-list{max-height:296px;overflow-y:auto;margin-top:5px}
.ctx .ctx-panel{padding:4px}

/* snackbar: fixed bottom-centre, uniform hairline, azure text action (no stripe) */
.snack{position:fixed;left:50%;bottom:64px;transform:translateX(-50%);z-index:var(--z-snack);display:flex;
       align-items:center;gap:18px;background:var(--card2);border:1px solid var(--line2);
       border-radius:var(--r);box-shadow:0 16px 48px rgba(0,0,0,.45);padding:10px 16px;
       font-size:13px;white-space:nowrap}
.snack .prose{font-family:var(--sans);font-size:13px}
.snack-act{background:none;border:0;color:var(--acc2);font:600 13px var(--mono);letter-spacing:.01em;
           padding:8px 10px;margin:-5px -2px;border-radius:var(--r3);white-space:nowrap;cursor:pointer}
.snack-act:hover{background:var(--accsoft)}

/* tag spree: rows read as buttons, their controls inert, azure wash on hover */
table.spree tbody tr[data-index]{cursor:pointer}
table.spree tbody tr[data-index] *{pointer-events:none}
table.spree tbody tr[data-index]:hover{background:var(--accsoft)}

.tfoot{display:flex;align-items:center;gap:8px;padding:12px 4px 0;color:var(--tx3);font-size:11px}
.tfoot .link{color:var(--tx3);text-decoration:none;cursor:pointer;border-radius:var(--r3);padding:1px 5px;margin:-1px -5px}
.tfoot .link:hover{color:var(--acc2);background:var(--accsoft)}

/* ---------- import — the 28vh sheet (the single anchor exception, verdict 79) ---------- */
.vsheet.vs-import{top:28vh;max-height:66vh}
.ovl-x.vs-import{top:calc(28vh + 10px)}
.drop{display:flex;align-items:center;justify-content:center;margin-bottom:14px;cursor:pointer;
      border:1.5px dashed var(--line2);border-radius:var(--r);padding:13px}
.drop .eye{width:40px;height:32px;flex:none;color:var(--tx3);transition:color var(--t-fast),transform var(--t-fast)}
.drop:hover .eye,.drop.dragover .eye{color:var(--brand);transform:scale(1.04)}
.ok{color:var(--pos);font-weight:700}
.spin{display:inline-block;width:12px;height:12px;border:2px solid var(--tx3);border-top-color:var(--acc);
      border-radius:50%;animation:sp 1s linear infinite}
@keyframes sp{to{transform:rotate(360deg)}}
.fmeta{font-size:11px;color:var(--tx3);white-space:nowrap}
.rprog{width:110px;height:4px;flex:none;border-radius:2px;background:color-mix(in srgb,var(--tx) 8%,transparent);overflow:hidden}
.rprog i{display:block;height:100%;width:62%;background:var(--acc);border-radius:2px}
/* the file row (shared anatomy with automate's .iq rows) */
.iq{display:flex;align-items:center;gap:16px;padding:11px 18px;border-bottom:1px solid var(--line)}
.iq:last-child{border-bottom:0}
.iq>.stag{width:80px;flex:none;justify-content:center;margin-left:0}
.iq-rows{flex:1;min-width:0;transition:opacity var(--t-fast)}
.iq-li{display:flex;align-items:center;gap:10px;font-size:13px;padding:3px 0}
.iq-li .nm{flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
.iq-act{display:flex;gap:8px;align-items:center;flex:none}
.iq:hover .dots,.iq .dots:focus-visible{opacity:1}
.iq .ok{width:38px;text-align:center;flex:none}
.iq .spin{margin:0 13px;flex:none}
.iq-yes,.iq-no{width:38px;height:32px;flex:none;border:1px solid var(--line2);border-radius:var(--r3);
        display:inline-grid;place-items:center;font-size:14px;color:var(--tx2);background:var(--card2);cursor:pointer}
.iq-yes:hover{border-color:var(--acc);color:var(--acc2)}
.iq-no:hover{border-color:var(--tx3);color:var(--tx)}
/* the keyboard cursor on automate — a left marker, not a focus ring, because
   the row is where the answer lands rather than a control to be tabbed to. It
   only exists once a key is used (no cursor a mouse user didn't ask for). */
.iq.here{background:color-mix(in srgb,var(--tx) 4%,transparent);
         box-shadow:inset 2px 0 0 var(--acc)}
.iq.a-yes .iq-yes{background:var(--accsoft);border-color:var(--acc);color:var(--acc2)}
.iq.a-yes .iq-rows,.iq.a-yes>.stag,.iq.a-no .iq-rows,.iq.a-no>.stag{opacity:.45}
/* the answered control: what you chose + ↩, one click to reverse */
.iq-undo{height:32px;flex:none;border:1px solid var(--line2);border-radius:var(--r3);padding:0 12px;
         display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--tx2);
         background:var(--card2);cursor:pointer;white-space:nowrap}
.iq.a-yes .iq-undo{border-color:var(--accline);color:var(--acc2)}
.iq-undo:hover{border-color:var(--tx3);color:var(--tx)}
/* NEVER — the third answer (verdict 128). Deliberately the quietest thing in
   the row: it is the most permanent answer, and the two you reach for daily are
   the ✓ and the ✕. Text, not a box, and to their left. */
/* "never" is drawn QUIET (v128: "drawn quiet and to the left of the two answers
   you reach for daily") — a dashed pill, hover-revealed with the row's other
   quiet controls, exactly as the frozen mockup draws it */
.iq-never{opacity:0;transition:opacity var(--t-fast);border:1px dashed var(--line2);
          border-radius:99px;font-size:11px;color:var(--tx3);padding:3px 9px;
          background:none;cursor:pointer;white-space:nowrap}
.iq:hover .iq-never,.iq-never:focus-visible{opacity:1}
.iq-never:hover{color:var(--tx2);border-color:var(--tx3)}
/* a proposal line's own ✓ (questions with several possible answers) */
.li-yes{width:26px;height:22px;flex:none;border:1px solid var(--line2);border-radius:var(--r3);
        display:inline-grid;place-items:center;font-size:11px;color:var(--tx2);background:var(--card2);
        cursor:pointer;opacity:0;transition:opacity var(--t-fast)}
.iq:hover .li-yes,.li-yes:focus-visible{opacity:1}
.li-yes:hover{border-color:var(--acc);color:var(--acc2)}
.auto-empty{padding:36px 8px;text-align:center}
.auto-empty .prose{font-size:13.5px;color:var(--tx2);max-width:46ch;margin:0 auto}
/* the shared lockup replaced the stray azure buttons here (owner, 28-Jul) */
.auto-empty .frs-eye-btn{margin-top:10px}
/* the failure line under a row that needs a look (sans, GOV.UK grammar) */
.iq-warnline{font-family:var(--sans);font-size:12.5px;color:var(--attn2);padding:2px 0 0}   /* a failure wears attention azure, not the money-out amber (14-Jul) */
/* the eye sits BESIDE the filename (the 24-Jul mark law): the name stops
   growing and the mark absorbs the slack, so it never drifts to the far edge */
.st-parsing .iq-li>.nm,.st-confirm .iq-li>.nm{flex:0 1 auto;min-width:90px;max-width:45%}
.st-parsing .iq-li>.pmark,.st-confirm .iq-li>.pmark{margin-right:auto}
.st-confirm .iq-li>.facct-slot{margin-left:auto;flex:0 1 auto;min-width:0}
.st-confirm .iq-li>.fmeta{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis}
.st-confirm .facct{flex:0 1 150px;min-width:100px;width:auto}
.pmark{display:inline-grid;place-items:center;width:20px;height:16px;vertical-align:-2px;
       color:var(--acc2);cursor:pointer;border-radius:4px;border:0;background:none;padding:0}
.pmark svg{width:12px;height:10px}
.pmark:hover,.pmark:focus-visible{background:var(--accsoft)}
/* the same Safari trap as the first-run eye: this filter used to sit on
   `.pmark`, a BUTTON wrapping an svg, so Safari flashed a rounded amber tile
   where Chromium flashed the mark. On the svg CHILD it is the shape that
   glows — and a 14px blur inside a 12px mark's filter region is small enough
   not to be clipped, which is why the child works here and not for the eye. */
@keyframes twinkle{
    0%{filter:none;transform:scale(1)}
   35%{filter:drop-shadow(0 0 7px var(--acc)) drop-shadow(0 0 14px var(--acc));transform:scale(1.16)}
  100%{filter:none;transform:scale(1)}}
.pmark.twinkle svg{animation:twinkle .85s cubic-bezier(.32,.9,.4,1) 1}
/* the account slot: input and read-only text share one 150px footprint */
.facct-slot{display:inline-flex;align-items:center;gap:6px}
/* the read-only account slot (the mockup's own): a bound or already-known
   account shows its name as a fact, not a field */
span.facct.ro{display:inline-block;background:none;border:0;color:var(--tx2);
              padding:6px 0;white-space:nowrap;cursor:help}
.facct{width:150px;background:var(--card2);border:1px solid var(--line);border-radius:var(--r3);
       padding:6px 9px;font-size:13px;color:var(--tx);outline:none}
.facct:focus{border-color:var(--acc)}
/* the screen-level submit: full button size, ↵ commits · → advances */
.btn-enter{width:72px;height:44px;padding:0;font-size:18px;line-height:1;border-radius:var(--r2)}
.ip-nav{margin-left:auto;display:flex;align-items:center;gap:12px}
/* ‹ back is a real button in the ↵'s own family (owner, 25-Jul-26: bare text
   beside the 72×44 primary read as a mismatch) — same height and radius,
   quiet where the ↵ is loud, so the pair reads as one control group */
.btn-back{display:inline-flex;align-items:center;justify-content:center;height:44px;
      padding:0 18px;background:none;border:1px solid var(--line2);border-radius:var(--r2);
      font-family:var(--mono);font-size:12px;color:var(--tx2);letter-spacing:.02em;
      cursor:pointer;text-decoration:none;
      transition:color var(--t-fast),border-color var(--t-fast)}
.btn-back:hover,.btn-back:focus-visible{color:var(--tx);border-color:var(--tx3)}
.vs-import .tfoot{padding-top:22px}
/* the adjust inset under a file row (currency · flip · columns · peek · remove) */
.ip-adjust{margin:8px 0 4px;background:var(--card2);border:1px solid var(--line);
           border-radius:var(--r2);padding:12px 14px}
.ip-adj-grid{display:flex;flex-wrap:wrap;gap:14px;align-items:center}
.ip-adj-grid label{display:inline-flex;align-items:center;gap:8px}
.ip-peek{margin-top:10px;border-top:1px solid var(--line);padding-top:6px}
.iq-li .date{width:66px;flex:none}
.iq-li .amt{color:var(--tx2)}
/* skipped duplicates fold into a transactions-style inset */
details.inset{margin-top:10px;background:var(--card2);border:1px solid var(--line);
              border-radius:var(--r2);padding:0}
details.inset summary{cursor:pointer;list-style:none;padding:10px 14px;font-size:11px;color:var(--tx2)}
details.inset summary::before{content:"▸ ";color:var(--tx3)}
details.inset[open] summary::before{content:"▾ "}
.pair{display:flex;align-items:center;gap:14px;padding:10px 14px;border-top:1px solid var(--line);font-size:13px}
.pair .n{white-space:nowrap}
.pair .btn-g{opacity:0;transition:opacity var(--t-fast)}
.pair:hover .btn-g,.pair .btn-g:focus-visible{opacity:1}

/* ---------- accounts sheet ---------- */
.bdate{font-size:11px;color:var(--tx3);margin-left:8px;cursor:help}
/* an account and its compartments are ONE block (owner, 25-Jul): the hairline
   between them would say "two accounts", which is the opposite of what an
   indent says. Only the last compartment closes the block. */
.ac-parent>td,.ac-sub>td{border-bottom:0}
.ac-sub-last>td{border-bottom:1px solid var(--line)}
.ac-sub td.nm{padding-left:34px;color:var(--tx3)}
.ac-sub .ps{cursor:help}
/* the waitlist (the owner's screen): one decision per row — invite, or don't.
   The code sits between the address and the actions so it can be read off and
   passed on by hand when email isn't configured or a send fails. */
.wl-counts{font-size:12px;color:var(--tx3)}
.wl-note{font:13px/1.6 var(--sans);color:var(--tx2);margin-bottom:14px}
.wl-code{font-size:12px;color:var(--acc2);white-space:nowrap;padding:0 12px}
.wl-acts{display:flex;gap:8px;justify-content:flex-end;align-items:center}
.wl-acts form{display:contents}
.wl-drop{color:var(--tx3)}
.wl-drop:hover{color:var(--neg);border-color:var(--neg)}
/* `.ac-edit` (the row's expanding inline panel) went with the ⋯ becoming a
   menu, 25-Jul-26 — the menu is the shared `.ctx` component above */
/* the add-row law: an add affordance is a LIST ITEM — standard divider,
   standard row height, its box edge on the content keyline, no row hover */
.addrow td{padding:10px 12px}
.addrow:hover{background:none}
.addbtn{display:inline-block;background:none;border:1px dashed var(--line2);border-radius:var(--r3);
        color:var(--tx3);font:12px var(--mono);padding:4px 11px;cursor:pointer;
        white-space:nowrap;width:max-content}
.addbtn:hover{color:var(--acc);border-color:var(--accline)}
/* provenance decoded once, at rest (audit verdict 31) */

/* ---------- categories sheet (the pockets table) ---------- */
.pockets .use{width:52%}
.pockets .use .bar{position:relative;display:inline-block;width:min(300px,60%);height:5px;border-radius:3px;
   background:color-mix(in srgb,var(--tx) 8%,transparent);overflow:hidden;vertical-align:middle;margin-right:12px}
.pockets .use .bar i{height:100%}
.pockets .use .bar u{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:color-mix(in srgb,var(--acc) 55%,#000)}
.pockets .use.warn .bar u{background:color-mix(in srgb,var(--warn) 55%,#000)}
.pockets .use .amt{font-size:11px;color:var(--tx2)}
.pockets .catname{font-size:13px;color:var(--tx);white-space:nowrap}
/* THE SAME LAW AS THE HOME CARD'S `.env .nm`: the name is a label and may be
   cut, the ↻ is MEANING and may not. Here the cell cannot become a flex row —
   `display:flex` on a `td` stops it being a table cell and the columns stop
   lining up (the trap is recorded in mobile.css) — so the ellipsis rides the
   ANCHOR, and `:has()` reserves the badge's room only on rows that carry one.
   Measured 28-Jul-26 at 390px: 104px of cell against 121px of content, and the
   badge was pushed clean outside the cell. */
.pockets .cn{display:inline-block;max-width:100%;overflow:hidden;
             text-overflow:ellipsis;vertical-align:bottom}
.pockets .catname:has(.rollb) .cn{max-width:calc(100% - 26px)}
.pockets .pkcell{display:inline-flex;align-items:center;gap:8px;min-height:36px}
/* the pocket value is a QUIET inline figure, not a form box (the mockup's own
   drawing): everything saves live (v23), so the value needs no field chrome —
   the box the Phase B build gave it was a divergence, swept 25-Jul-26 */
.pockets input.pk{width:84px;text-align:right;background:none;border:0;
   font:13px var(--mono);color:var(--tx);outline:none;padding:7px 0}
.pockets input.pk:focus{color:var(--acc2)}
.pockets .pkcell .setpk{display:none}
.pockets .pkcell.nopocket .pk{display:none}
.pockets .pkcell.nopocket .setpk{display:inline-block}
.pockets .setpk{width:84px;background:none;border:1px dashed var(--line2);border-radius:var(--r3);
   color:var(--tx3);font:12px var(--mono);padding:4px 0;text-align:center;cursor:pointer;white-space:nowrap}
.pockets .setpk:hover{color:var(--acc);border-color:var(--accline)}
.pockets .addrow td{padding:15px 12px}
/* reorder: entered from the ⋯, iOS-style — grip on the trailing edge,
   commit/cancel in the sheet's own header, values recede while ordering */
.pockets .drag{display:none;color:var(--tx3);font-size:13px;line-height:1;cursor:grab;letter-spacing:-1px}
.reordering .pockets .drag{display:inline-block}
.reordering .pockets .dots{display:none}
.reordering .pockets .addrow{display:none}
.reordering .pockets tbody tr{cursor:grab}
.reordering .pockets .pk,.reordering .pockets .setpk,.reordering .pockets .use .bar,
.reordering .pockets .use .amt{opacity:.35;pointer-events:none}
.reobar{display:none;align-items:center;gap:10px;margin:0 0 14px}
.reordering .reobar{display:flex}
.reobar .lab{font:12px var(--mono);color:var(--tx3);margin-right:auto}
.reobar button{width:30px;height:30px;display:grid;place-items:center;border-radius:var(--r3);
   border:1px solid var(--line2);color:var(--tx2);font-size:13px;cursor:pointer}
.reobar .ok{border-color:var(--accline);color:var(--acc2)}
.reobar button:hover{color:var(--tx)}
.reobar .ok:hover{background:var(--accsoft)}

/* ---------- merchant sheet (verdict 33): summary strip over the rows ---------- */
.mv-rename{color:var(--tx3);font-size:13px;padding:2px 7px;border-radius:var(--r3);
           vertical-align:2px;margin-left:6px;background:none;border:0;cursor:pointer}
.mv-rename:hover{color:var(--acc2);background:var(--accsoft)}
.mv-stats{display:flex;gap:14px;margin-bottom:20px}
.mv-stat{flex:1;background:var(--card2);border:1px solid var(--line);border-radius:var(--r2);
         padding:14px 16px;display:flex;flex-direction:column;gap:5px}
.mv-n{font-size:20px;color:var(--tx);font-weight:500;line-height:1}
.mv-cat{font-size:14px;line-height:1.5}
.mv-l{font-family:var(--mono);font-size:11px;color:var(--tx3);letter-spacing:.02em}

/* ---------- settings sheet (v113: label · value · nothing else) ---------- */
.prof-groups{display:flex;flex-direction:column;gap:14px}
.prow{display:flex;align-items:center;gap:14px;min-height:54px;padding:0 18px;
      border-bottom:1px solid var(--line);font-size:13px}
.card>.prow:last-child,.card>form:last-child .prow{border-bottom:0}
.prow .pval{margin-left:auto;font-size:12px;color:var(--tx2);display:inline-flex;align-items:center;gap:8px}
.prow .pkeys{display:inline-flex;gap:6px;align-items:center}
.prow.pdoor{cursor:pointer}
.prow.pdoor:hover .wdoor,.prow.pdoor:focus-visible .wdoor{background:var(--accsoft);color:var(--acc2)}
.prow.pdoor:hover .pval{color:var(--tx)}
.prow-id{min-height:66px}
.prow-id .pmail{font-size:14px;color:var(--tx)}
.ava-id{width:30px;height:30px;border-radius:50%;background:var(--card2);border:1px solid var(--line2);
        display:grid;place-items:center;font-size:11px;font-weight:700;color:var(--brand)}
.ava-lg{width:36px;height:36px;font-size:12px}
.legal-links{margin-left:auto;font-size:12px;display:inline-flex;gap:8px;align-items:center;color:var(--tx3)}
.legal-links a{color:var(--tx2)}
.legal-links a:hover{background:var(--accsoft);color:var(--acc2)}
/* log out speaks the plain text colour — amber is money-out and the eye,
   azure is action, and log out is neither (24-Jul) */
.act-logout{color:var(--tx);font-weight:600;background:none;border:0;cursor:pointer;padding:0}
.act-logout:hover{color:var(--acc2)}
/* the disclosure panels: help · feedback · passkeys · clear data · delete */
.help-panel,.fb-form,.pw-panel,.clear-panel,.del-panel{display:none}
.help-panel.on,.fb-form.on,.pw-panel.on,.clear-panel.on,.del-panel.on{display:block}
.help-panel,.fb-form,.pw-panel{padding:2px 18px 16px;border-bottom:1px solid var(--line)}
/* one device per line inside the passkeys panel. They sit INSIDE a panel that
   already has the sheet's gutter, so they give theirs back rather than
   indenting twice; the last one drops its rule so the panel ends on the
   panel's own edge. */
.pk-dev{padding:0;min-height:42px;color:var(--tx2)}
.pk-dev:last-of-type{border-bottom:0}
.pk-add{margin:12px 0 0}
.pk-dev .btn-g{color:var(--tx3);padding:0 4px}
.pk-dev .btn-g:hover{color:var(--tx)}
#pk-err:empty{display:none}
.help-panel dl{display:grid;grid-template-columns:auto 1fr;gap:10px 16px;margin:4px 0 0;align-items:center}
.help-panel dt{text-align:right;white-space:nowrap}
.help-panel dd{margin:0;font:12px var(--mono);color:var(--tx2)}
.fb-form textarea{display:block;width:100%;min-height:88px;resize:vertical;background:var(--card2);
                  border:1px solid var(--line2);border-radius:var(--r2);padding:10px 12px;outline:none;
                  color:var(--tx);font:13px/1.55 var(--mono)}   /* mono — the sans ration
                  (v76) swept the feedback box to the chrome voice */
.fb-form textarea::placeholder{color:var(--tx3)}
.fb-form textarea:focus{border-color:var(--acc)}
.fb-foot{display:flex;align-items:center;gap:12px;margin-top:10px;min-height:32px}
.fb-send{margin-left:auto;display:none;width:34px;height:34px;padding:0;place-items:center;font-size:15px;border-radius:var(--r3)}
.fb-form.ready .fb-send{display:grid}
.fb-sent{font:600 14px var(--mono);color:var(--tx);padding:6px 0 2px}
.fb-sent .ok{font-size:13px;color:var(--pos)}
.fb-sent-sub{font:11px var(--mono);color:var(--tx3);margin-top:6px}
.clear-panel{padding:2px 18px 18px}
.clear-panel .prose{color:var(--tx2)}
.clear-list{display:flex;flex-direction:column;gap:8px;margin-top:12px}
.clear-opt{display:flex;flex-direction:column;gap:3px;width:100%;text-align:left;cursor:pointer;
           background:var(--card2);border:1px solid var(--line);border-radius:var(--r2);padding:10px 13px}
.clear-opt:hover{border-color:var(--line2)}
.clear-opt .co-t{font-size:13px;color:var(--tx)}
.clear-opt .co-s{font:12px/1.5 var(--sans);color:var(--tx3)}
.clear-opt.co-danger{border-color:color-mix(in srgb,var(--neg) 30%,transparent)}
.clear-opt.co-danger .co-t{color:var(--neg)}
/* the ONE place red survives: destructive territory */
.clear-del{margin-top:16px;font:12px var(--mono);color:var(--tx3);opacity:.8;padding:2px 0;
           background:none;border:0;cursor:pointer;display:block}
.clear-del:hover{color:var(--neg);opacity:1}
.del-panel{padding-bottom:4px}

/* ---------- first run (setup): one question at a time ---------- */
.su-wrap{min-height:calc(100vh - 128px);display:flex;flex-direction:column;
         align-items:center;justify-content:center;text-align:center}
.su-wm{margin-bottom:34px}
.su-step{max-width:44ch}
.su-q{font:600 16px var(--mono);letter-spacing:.02em;margin-bottom:10px}
.su-p{font-size:13.5px;color:var(--tx2);margin-bottom:22px}
.su-cur{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-bottom:16px}
.su-cbtn{min-width:76px}
.su-cbtn[disabled]{opacity:.35;pointer-events:none}
.su-other{display:flex;gap:8px;align-items:center;justify-content:center}
.su-name{width:220px;text-align:center;font-size:14px;padding:11px 14px}
.su-done{display:flex;gap:12px;align-items:center;justify-content:center;margin-top:18px}

/* ---------- match: the candidate rows ---------- */
.mt-row{display:flex;align-items:center;gap:12px;width:100%;text-align:left;padding:11px 16px;
        border-bottom:1px solid var(--line);font-size:13px;cursor:pointer;background:none;border-left:0;
        border-right:0;border-top:0;color:inherit}
.mt-row:last-child{border-bottom:0}
.mt-row:hover{background:color-mix(in srgb,var(--tx) 4%,transparent)}
.mt-row .nm{flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
.mt-row .amt{white-space:nowrap}

/* ---------- the why sheet (the trust framework, owner 19–20 Jul) ----------
   "This is the one screen where we really show our working": the claim
   sentence, then the rows it rests on, in the SAME row language the rest
   of the app uses — so evidence reads identically wherever it appears. */
.whysheet{display:none;position:fixed;inset:0;z-index:var(--z-why)}
.whysheet.on{display:block}
.whysheet .wv{position:absolute;inset:0;background:rgba(6,9,14,.5);backdrop-filter:blur(4px);border:0;padding:0}
.whysheet .wp{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
              width:min(1000px,92vw);max-height:86vh;overflow:auto;
              background:var(--canvas);border:1px solid var(--line2);
              border-radius:16px;box-shadow:0 40px 120px rgba(0,0,0,.6);
              padding:64px 30px 58px;text-align:center}
.whysheet .wp-x{position:absolute;top:14px;right:14px;display:grid;place-items:center;
                width:30px;height:30px;color:var(--tx3);font-size:17px;border-radius:8px;
                background:none;border:0;cursor:pointer}
.whysheet .wp-x:hover{color:var(--tx);background:color-mix(in srgb,var(--tx) 6%,transparent)}
/* the mark at hero size — deliberately NOT the 16px inline size law: here
   the eye IS the subject of the screen */
.whysheet .wp-eye{width:38px;height:30px;color:var(--acc2);margin:0 auto 20px;display:block}
.whysheet .wp-t{display:block;font:600 13px var(--mono);color:var(--tx3);letter-spacing:.04em;
                margin-bottom:16px}
.whysheet .wp-why{font:16px/1.65 var(--sans);color:var(--tx);max-width:52ch;margin:0 auto}
/* the working, shown: 52ch of sans measures ~524px — the evidence block
   matches the sentence's width so it never sits narrower than its claim */
/* A second and third sentence sit quieter than the headline claim, so the
   first line still reads as the answer and the rest as its company.
   NAMED `wp-also`, NOT `wp-note`: `.wp-note` was this sheet's undo footnote
   (11px mono) and the collision silently won — the sentences came out as mono
   chrome at 11px. Fourth time this class of bug has bitten (lg-`ready`, pocket
   `empty`, wp-`note`): grep a class name before adding it. (`.wp-note` itself
   was deleted 28-Jul-26, unused — the lesson outlives the rule, so the naming
   still stands: do not reuse the name.) */
.whysheet .wp-also{font-size:14px;color:var(--tx2);margin-top:10px}
.whysheet .wp-ev{margin:22px auto 0;width:524px;max-width:100%;text-align:left;
                 border:1px solid var(--line2);border-radius:10px;background:var(--card2);padding:2px 14px}
.whysheet .wp-ev .iq-li{font-size:12.5px;padding:7px 0}
.whysheet .wp-ev .iq-li+.iq-li{border-top:1px solid color-mix(in srgb,var(--line2) 55%,transparent)}
.whysheet .wp-ev .nm{color:var(--tx2)}
.whysheet .wp-ev .amt{font-variant-numeric:tabular-nums;white-space:nowrap}
.whysheet .wp-ev .ev-more{display:block;font:11px var(--mono);color:var(--tx3);padding:8px 0}
/* the reconciled pair: two figures that must read as ONE fact agreeing with itself */
.whysheet .wp-ev .iq-li.ev-sum .nm{color:var(--tx3)}
.whysheet .wp-ev .iq-li.ev-sum .amt{color:var(--tx)}
.whysheet .wp-undo{display:inline-flex;margin-top:30px}

/* ---------- tracks: the charts (audit §8 kit) ---------- */
.tr-card{margin-bottom:18px}
.tr-body{padding:16px 18px 4px}
.tr-body svg{display:block;width:100%;height:auto}
/* the unified chart header: every chart answers its question in words
   before the drawing — one headline figure + one context line, and the
   figure must agree with the chart below it */
.tr-hero{display:flex;align-items:baseline;gap:12px;padding:15px 18px 0;flex-wrap:wrap}
.tr-num{font-size:24px;font-weight:600;color:var(--tx);line-height:1}
.tr-delta{font-size:12px;color:var(--tx3)}
.tr-card .mv-stats{margin:16px 18px 0}
.rangec{display:inline-flex;gap:2px;font-family:var(--mono);font-size:11px;color:var(--tx3)}
.rangec a,.rangec button{padding:5px 9px;border-radius:var(--r3);color:inherit;cursor:pointer}
.rangec a.on,.rangec button.on{color:var(--tx);background:color-mix(in srgb,var(--tx) 7%,transparent)}
.rangec a:not(.on):hover{color:var(--tx2)}
.rangec [aria-disabled="true"]{opacity:.35;pointer-events:none}
.ax{font-family:var(--mono);font-size:11px;fill:var(--tx3)}
.axv{text-anchor:end}
.gl{stroke:var(--line2);opacity:.5}
/* the merchant "by month" bars (v93): neutral — a single-direction list takes
   no direction colour (v21) */
.bcat{fill:color-mix(in srgb,var(--tx) 22%,transparent)}
.bcat:hover{fill:color-mix(in srgb,var(--tx) 30%,transparent)}
/* its phone voice (ledger row 17): month · bar · amount rows */
.bym-phone{display:none}
.bym-row{display:flex;align-items:center;gap:10px;padding:7px 16px}
.bym-row .mcap{width:34px;flex:none;font-size:11px;color:var(--tx3)}
.bym-row .v{font-size:12px;color:var(--tx2);min-width:84px;text-align:right}
.mbar{position:relative;height:4px;flex:1;background:color-mix(in srgb,var(--tx) 8%,transparent);border-radius:2px;overflow:hidden}
.mbar i{display:block;height:100%;background:var(--acc);border-radius:2px}
.mbar i.cat{background:color-mix(in srgb,var(--tx) 22%,transparent)}
/* ONE line weight for every chart line */
.tl{fill:none;stroke:var(--acc);stroke-width:3;stroke-linejoin:round;stroke-linecap:round}
.tl.out{stroke:var(--brand-fill)}
.tdot{fill:var(--acc)}
.tdot.out{fill:var(--brand-fill)}
.tgap{fill:var(--acc);opacity:.07}
.tlab{font-family:var(--mono);font-size:11px;fill:var(--tx2)}   /* direct labels are
   quiet facts, not actions — azure would claim clickability (v75) */
.tlab.out-lab{fill:var(--brand)}
/* LAST YEAR, drawn behind this year (verdict 93): the same two colours at a
   quarter of the ink and a thinner stroke. A different hue would read as a
   third and fourth thing being measured rather than the same two, earlier. */
.tl-ghost{stroke-width:1.6;opacity:.32}
.tlab-ghost{fill:var(--tx3)}
/* what changed — the sentences went MONO with the sans ration (v76: borderline
   always goes mono; the frozen mockup draws them mono); the numbers inside
   stay a step brighter and heavier (facts, not directions) */
.ins{list-style:none;margin:0;padding:5px 18px 13px;font-family:var(--mono);font-size:12px}
.ins li{padding:8px 0;border-bottom:1px solid var(--line);font:13px/1.55 var(--mono);color:var(--tx2)}
.ins li:last-child{border-bottom:0}
.ins .n{font-family:var(--mono);font-size:12px;color:var(--tx);font-weight:600}
/* the field separator (verdict 97, owner: "switch to hairline") — the dot is
   retired from DISPLAY. A 1px bar at quarter-ink, .78em tall: the table's
   column line miniaturized, structure not punctuation. The `·` survives only
   in non-visual strings (native tooltips, aria labels, SVG <title> data).
   TRAP: `.fsep` is an HTML element and CANNOT go inside an SVG <text> — the
   browser neither draws it nor lays it out, and the label collapses into
   run-together words ("$432.96at&t"). Inside SVG the separator is a
   `<tspan dx>` GAP — space, not punctuation.
   It carries NO margin: a real space sits on either side of it in the markup,
   and those spaces do the spacing (~.6em in mono, which is what the design's
   .55em was). They are also the word break the dot used to give a screen
   reader — without them "…the statement itself" and "type one where none is
   printed" are announced as one run-together word. A space INSIDE the element
   does not work: a browser collapses it against the inline-block's own edges,
   so it reaches neither the screen nor the accessibility tree. */
.fsep{display:inline-block;width:1px;height:.78em;vertical-align:-.08em;
      background:color-mix(in srgb,var(--tx) 25%,transparent)}
/* THE ERROR LINE (audit §8's component gap). One rule for "this didn't work",
   wherever it appears: sans, sentence case, GOV.UK grammar — what happened and
   what to do, never "oops", never blame.

   It wears the ATTENTION AZURE, not amber. That is the owner's rule of
   14-Jul ("ALL failure states — import metas included — wear attention azure,
   never red… adjustments, not big problems") and it matters twice over: amber
   is the money-OUT colour, so an amber sentence reads as a figure rather than a
   message, and the colour law says one colour says one thing. The Phase B
   rebuild had left these amber in six places.

   (There was an `.errsum` here too — a form-level summary box for when a whole
   submission fails rather than one field. No screen ever wore it, so it was
   deleted 28-Jul-26 rather than left as a rule describing a component that does
   not exist. If a form ever needs one, it is this voice in a bordered box.) */
.errline{font:13px/1.55 var(--sans);color:var(--attn2);margin-bottom:14px}

/* the shared empty state (audit §8) — one padding, one colour, wherever the
   app has to say "there's nothing here". `.empty-mid` is the standalone case:
   a whole surface with nothing in it, rather than a card that has rows
   elsewhere. */
/* .prose IS the sans voice (principle 8: plain language at the moments of
   truth — empty states, decision support). The Phase B build never gave it
   the family, so every empty state was rendering mono; fixed 25-Jul-26. */
.prose{font-family:var(--sans)}
.prose.empty{padding:14px 2px;color:var(--tx2)}
.prose.empty-mid{text-align:center;padding:40px 0;color:var(--tx3)}
.prose.empty-card{padding:16px 18px 18px;color:var(--tx2)}   /* inside a card body, on its own keyline */

/* the ? shortcuts card — the keyboard taught once, on demand (audit §5). It
   opens on its own class rather than a body class, so it can sit above an open
   sheet and take Esc first. */
.scut{position:fixed;inset:0;z-index:var(--z-keys);display:none}
.scut.on{display:block}
.scut .cveil{position:absolute;inset:0;background:rgba(6,9,14,.5);backdrop-filter:blur(4px)}
.scut-box{position:relative;width:min(400px,90vw);margin:22vh auto 0;background:var(--card);
          border:1px solid var(--line2);border-radius:var(--r);padding:20px 22px;
          box-shadow:0 24px 80px rgba(0,0,0,.55)}
.scut-box h3{font:600 11px var(--mono);letter-spacing:.06em;color:var(--tx3);margin:0 0 15px}
.scut-box dl{display:grid;grid-template-columns:auto 1fr;gap:11px 18px;margin:0;align-items:center}
.scut-box dt{white-space:nowrap;text-align:right}
.scut-box dd{margin:0;font:12px var(--mono);color:var(--tx2)}

/* first run — nothing imported yet, so the screen is the breathing mark and
   one way in. The whole lockup is the button (verdict 14/17: the ghost-card
   dashboard was dropped; the mark IS the import door). */
/* 100vh − 2×32px of main padding puts the flex centre at exactly 50vh — the
   mockup's own arithmetic (the -160 the build shipped centred the eye 48px
   high; measured 312 vs the 360 ideal at 720 tall) */
.frs-wrap{position:relative;min-height:calc(100vh - 64px);display:flex;flex-direction:column;
          align-items:center;justify-content:center}
/* THE START LOCKUP (owner, 27-Jul-26, ruled from the on-device pass): the
   dock's commit element at hero scale — the line draws, tenses, springs into
   the eye (the W5 beats, same 820ms), then the mark BREATHES from very dim to
   full. One design on desktop and phone alike (owner: "desktop gets the same
   animated mark"). The wake is one-shot on arrival; markup lives in
   _start_lockup.html and nowhere else.

   THE GLOW IS THE MARK'S OWN LIGHT — a blurred amber COPY of the svg behind
   the crisp one. Not a css drop-shadow: Safari shadows the svg's BOX, the
   twice-bitten trap (25/26-Jul, the glowing amber tile). Not the radial
   gradient that replaced it: that was the circular halo the owner retired
   ("just what is amber" glows). A blurred copy is rendered the same by
   everyone, because blur() has no shape opinion — it blurs what's there. */
.frs-eye-btn{display:flex;flex-direction:column;align-items:center;gap:64px;
             background:none;border:0;padding:14px 22px;cursor:pointer;
             border-radius:var(--r);color:inherit;text-decoration:none}
.frs-eye-btn:focus-visible{outline:1px solid var(--accline);outline-offset:8px}
/* eye 84×67 (owner, 28-Jul: "the eye needs to be larger; text size is good").
   The seat holds exactly ONE layout child — the crisp eye; the lid and the
   glow are both absolute, because Safari spread two grid children into two
   rows and the word drifted ~45px further down than the mockup drew. */
.frs-seat{width:128px;height:92px;display:grid;place-items:center;position:relative}
.frs-lid{position:absolute;left:50%;top:52%;transform:translate(-50%,-50%);
         width:84px;height:6px;border-radius:3px;background:var(--brand);
         box-shadow:0 0 14px 2px color-mix(in srgb,var(--brand) 45%,transparent)}
.frs-eye{width:84px;height:67px;color:var(--brand);display:block;
         transform-origin:50% 58%;opacity:0}
/* THE GLOW IS A BAKED PNG (the fourth Safari cure — see _start_lockup.html):
   pre-blurred from the vector, strength baked into its alpha, absolutely
   positioned by MARGINS (an animated transform would replace a positioning
   translate — the recorded halo bug), animated on opacity only. */
.frs-glow{position:absolute;left:50%;top:50%;width:240px;height:195px;
          margin:-97px 0 0 -120px;max-width:none;pointer-events:none;opacity:0}
.frs-seat.wake .frs-lid{animation:frs-lid .82s ease-out forwards}
@keyframes frs-lid{
  0%  {opacity:1;transform:translate(-50%,-50%) scaleX(.04)}
  37% {opacity:1;transform:translate(-50%,-50%) scaleX(1)}
  50% {opacity:1;transform:translate(-50%,-50%) scaleX(1.20)}
  58% {opacity:0;transform:translate(-50%,-50%) scaleX(.45)}
  100%{opacity:0;transform:translate(-50%,-50%) scaleX(.45)}}
/* wake, then breathe: the second animation starts after the spring settles
   and owns opacity from there — very dim to full (owner's ask, 27-Jul) */
.frs-seat.wake .frs-eye{animation:frs-eyek .82s ease-out forwards,
                                  frs-breathe 4.2s ease-in-out 1.3s infinite}
/* the glow fades in with the spring and breathes in step — opacity only,
   its blur and its .55 strength are pixels in the file */
.frs-seat.wake .frs-glow{animation:frs-glowk .82s ease-out forwards,
                                   frs-breathe 4.2s ease-in-out 1.3s infinite}
@keyframes frs-glowk{0%,55%{opacity:0}88%,100%{opacity:1}}
@keyframes frs-eyek{
  0%,50%{opacity:0;transform:scale(1.12,.04)}
  59%   {opacity:1;transform:scale(.94,1.14)}
  70%   {opacity:1;transform:scale(1.04,.96)}
  80%   {opacity:1;transform:scale(.99,1.02)}
  88%,100%{opacity:1;transform:scale(1,1)}}
@keyframes frs-breathe{0%,100%{opacity:1}50%{opacity:.22}}
/* tracking adds trailing space after the last glyph — pull it back to re-centre */
.frs-word{font-size:13px;font-weight:600;letter-spacing:.2em;margin-right:-.2em;
          color:var(--tx2);transition:color var(--t-slow) ease}
/* the caret is the terminal's, not the word's: it keeps the text colour like
   every other `.wmcaret` and doesn't carry the word's tracking */
.frs-word .wmcaret{letter-spacing:normal}
.frs-eye-btn:hover .frs-word,.frs-eye-btn:focus-visible .frs-word{color:var(--tx)}
/* motion preference: the eye is simply open, at rest — no beats, no breath */
@media (prefers-reduced-motion:reduce){
  .frs-seat.wake .frs-lid{animation:none;opacity:0}
  .frs-seat.wake .frs-eye,.frs-seat.wake .frs-glow{animation:none;opacity:1;transform:none}}
/* the keyboard, taught once at the door */
.frs-keys{position:absolute;left:0;right:0;bottom:30px;text-align:center;
          font:11px var(--mono);color:var(--tx3)}

/* the month selector (owner 20-Jul) — `‹ june 2026 ›`. ‹ steps one month
   older, › steps newer and past now lands on ALL TIME. Chrome voice: mono,
   small, the chevrons recede until hovered. It lives on categories, the
   category view, the merchant view and the top-merchants sheet — NOT on
   recurring, accounts or transactions (the owner's screen-by-screen call).
   Never remembered: every carrier resets to its default on load. */
.msel{display:inline-flex;align-items:center;gap:2px;height:30px;font-family:var(--mono);
      font-size:12px;color:var(--tx2);letter-spacing:.02em;white-space:nowrap;user-select:none}
.msel .mval{padding:0 5px;min-width:96px;text-align:center}
.msel .mstep{width:24px;height:24px;border-radius:var(--r3);display:grid;place-items:center;
      background:none;border:0;color:var(--tx3);font-size:15px;line-height:1;cursor:pointer}
.msel .mstep:hover,.msel .mstep:focus-visible{color:var(--tx);background:color-mix(in srgb,var(--tx) 6%,transparent)}
.msel .mstep[aria-disabled="true"]{opacity:.28;cursor:default;pointer-events:none}
/* on a card header the selector sits inline-right, a touch smaller */
.card-h .msel{height:26px}
.card-h .msel .mstep{width:22px;height:22px}
/* recurring's "upcoming" strip — the month ahead. Dots are NEUTRAL: this list
   is all money out, and colour marks direction only where direction varies
   (verdict 21). One dot per DAY, sized by that day's total. */
.upline{stroke:var(--line2)}
.updot{fill:var(--tx3)}
/* HIT BANDS (verdict 105): the smallest dots measure ~4.5px across, a 5px
   hover target. Each dot owns a transparent band from midpoint to midpoint,
   so it lights from anywhere in its slice and no dot can cover its
   neighbour's centre. Same idiom as the charts' month bands. */
.upband{fill:transparent}
.upg:hover .updot{fill:var(--tx)}
/* the home card's miniature of the same strip: the sheet's component at card
   width, labels dropped (no room), so the dots carry the detail in their tip */
.upmini{padding:12px 18px 8px;border-bottom:1px solid var(--line)}
/* the axis labels live in HTML, not in the svg: the drawing is fitted to the
   card width, so text inside it would scale below the 11px chrome floor */
.upmini-ax{display:flex;justify-content:space-between;padding:2px 2px 0;
           font-size:11px;color:var(--tx3)}
.tr-body .upmini-ax{padding:2px 0 0}
/* the recurring table's own rows: the ✕ reveals on hover like every other
   per-row action, and the add row keeps its fields visible at rest */
.rc-row:hover .dots,.rc-row .dots:focus-visible{opacity:1}
.rc-add input,.rc-add select{font-size:12px}
/* targets: a goal is a pocket pointed at the future, so it wears the
   pockets language — the same track+fill bar, one block per goal, its
   figures in the ⋯ inset like every other structural edit */
/* NO divider between goals (verdict 107: rows that continue one thing are not
   divided — the targets card groups its goals, it doesn't separate them) */
.goal{padding:15px 18px 16px}
.goal-row{display:flex;justify-content:space-between;align-items:baseline;gap:12px}
.goal-nm{font-size:13px;color:var(--tx)}
.goal-amt{font-size:11px;color:var(--tx2);margin-left:auto}
.goal:hover .dots,.goal .dots:focus-visible{opacity:1}
.goal-bar{height:5px;border-radius:3px;background:color-mix(in srgb,var(--tx) 7%,transparent);
          overflow:hidden;margin:10px 0 8px}
/* GOALS DO NOT WARM (deliberately excluded from the 25-Jul sweep). A pocket is
   a line you can CROSS and amber is the money-out colour; a goal is a line you
   want to REACH, and warming it would make hitting a savings target look like
   overspending. Same shape, opposite meaning — so the same treatment would say
   something false. The pulse card's target track (`.pl-track`) is excluded for
   the same reason — and its old `.over` amber rule went with this ruling: no
   template ever emitted it, so it was styling nothing. */
.goal-bar i{display:block;height:100%;border-radius:3px;background:var(--acc)}
.goal-cap{font-size:11px;color:var(--tx3);margin:0}
.goal-edit{padding:12px 0 2px}
.goal-add .ip-adj-grid{gap:10px}
/* habits: a day grid — brighter amber, more spent that day */
.hm-grid{display:flex;flex-wrap:wrap;gap:3px}
.hm-d{width:11px;height:11px;border-radius:2px;display:block;
      background:color-mix(in srgb,var(--tx) 6%,transparent)}
.hm-d.l1{background:color-mix(in srgb,var(--brand-fill) 16%,transparent)}
.hm-d.l2{background:color-mix(in srgb,var(--brand-fill) 34%,transparent)}
.hm-d.l3{background:color-mix(in srgb,var(--brand-fill) 62%,transparent)}
.hm-d.l4{background:var(--brand-fill)}
.hm-d:hover{outline:1px solid var(--acc);outline-offset:1px}
/* THE MONTH VIEW (verdict 96) — the same squares on a real weekday grid, so
   "which day does money leave" can be read as a weekday and not only as a
   date. Seven fixed columns; the cells carry their date because at this size
   there is room to say it, and a labelled cell needs no tooltip to be read. */
.hm-cal{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;max-width:340px}
.hm-cal .hm-wd{font-size:11px;color:var(--tx3);text-align:center;padding-bottom:2px}
.hm-cal .hm-d{width:auto;height:auto;aspect-ratio:1;display:grid;place-items:center;
              font-size:11px;color:var(--tx2);border-radius:var(--r3)}
.hm-cal .hm-d.l3,.hm-cal .hm-d.l4{color:var(--onattn)}
/* a leading blank is a real cell — without it the 1st lands under the wrong
   weekday and the layout stops meaning anything */
.hm-cal .hm-void{background:none;pointer-events:none}
/* drift: the part of a bar above the pocket line pokes out in the warn
   amber — position carries the meaning, colour only underlines it.
   The row's height is a token because the bar's gradient stops are computed
   from it (see `.dr-slot i`). */
.dr-row{display:flex;align-items:center;gap:14px;padding:9px 0;border-bottom:1px solid var(--line)}
.dr-row:last-of-type{border-bottom:0}
.dr-nm{width:120px;flex:none;font-size:13px;color:var(--tx2);white-space:nowrap;
       overflow:hidden;text-overflow:ellipsis}
/* the slots fill the row, but a slot is capped: with twelve months they land
   near 34px, and without the cap three months of data stretched each bar to
   ~240px — wider than it is tall, which reads as a block, not a bar. Bars stay
   bar-shaped at any month count, and the row is left-aligned so a short
   history starts where a long one does. */
.dr-bars{flex:1;display:flex;align-items:flex-end;justify-content:flex-start;gap:3px;
         height:var(--dr-h)}
.dr-slot{position:relative;flex:1 1 0;max-width:40px;height:100%;display:flex;align-items:flex-end}
/* THE BAR WARMS UP TO ITS POCKET (owner, 25-Jul-26 — first "a tight gradient
   into amber, not just an immediate change in colour", then "have them all warm
   up to the color, even what we just did in drifts").

   So the bar is neutral through its first stretch, warms from `--warm-from` of
   the pocket, and is full amber AT the line and above it. Colour became a
   TEMPERATURE — how close this month ran to its line — while the dashed pocket
   line stays the exact marker and position still carries the crossing (the
   21-Jul law holds: colour underlines position, it doesn't replace it).

   THE TRICK, and the reason the stops are in px rather than %: a percentage
   stop resolves against the BAR's own box, so the colour change would slide up
   and down with each bar's height and stop meaning "the pocket". Measured from
   the bottom with `to top`, an absolute length is measured from the slot's
   floor — which every bar shares — so the boundary lands on the pocket line no
   matter how tall the bar is. `--pk` is the pocket as a unitless fraction of
   the row, set per row by the template (it isn't always the same fraction: a
   pocket of 0 falls back to the shared scale).

   A bar that never reaches the pocket simply never renders the amber half —
   the gradient continues above the bar's top, where there is nothing to paint. */
.dr-slot i{display:block;width:100%;border-radius:2px 2px 0 0;
           background:linear-gradient(to top,
             var(--dr-base) 0,
             var(--dr-base) calc(var(--warm-from) * var(--pk,1) * var(--dr-h)),
             var(--warn)    calc(var(--pk,1) * var(--dr-h)),
             var(--warn)    100%)}
.dr-slot u{position:absolute;left:0;right:0;border-top:1.5px dashed var(--acc);opacity:.7}
.dr-meta{width:170px;flex:none;text-align:right;font-size:11px;color:var(--tx3)}

/* ---------- home: pre-data (the empty base) ---------- */
.hm-empty{min-height:calc(100vh - 64px);display:flex;flex-direction:column;
          align-items:center;justify-content:center;gap:26px}
/* (.hm-empty's wordmark wears the shared .wordmark rule) */

/* ═══ THE LOWERCASE DISPLAY LAW (verdicts 43/52, amended by 76) ═══
   Display only — stored and exported wording keeps the bank's own case (hard
   rule 2); numbers and symbols are untouched by text-transform. THE CASE LAW:
   lowercase belongs to the PROWL TYPEFACE — anything mono displays lowercase,
   always; sans keeps its natural capitals, because a sentence that lowercases
   "Prowl", "PDF" and "CSV" reads careless exactly where a money app can least
   afford it. The list below IS the sans allowlist (v76: sans is rationed, so a
   selector not named here is mono by definition — borderline goes mono).
   The legal pages carry no `lc` on the body, because they are whole documents
   of SANS sentences — but their h1/h2 are mono, so `_legal_base` lowercases
   those two itself (26-Jul-26). The exemption is the prose's, not the page's;
   applied to the whole body it silently swallowed the headings. */
body.lc{text-transform:lowercase}
/* form controls don't inherit text-transform — v43 wants them lowercased
   live ("even Caps Lock shows lowercase"); display only, the VALUE keeps
   whatever case was typed or suggested */
body.lc input,body.lc select,body.lc textarea{text-transform:lowercase}
/* (The invite code had an exemption here — it was the one identifier allowed
   to keep its capitals, so the screen would match the email. The owner
   overturned that on 26-Jul-26: the code is MINTED lowercase now, so the
   screen, the email and the URL print the same characters and there is no
   exemption left to make. prowl writes in lowercase; a code is not special.) */
body.lc .prose,
body.lc .iq-warnline,
body.lc .errline,body.lc .errsum,
body.lc .ctx .ctx-hint,
body.lc .clear-opt .co-s,
body.lc .whysheet .wp-why{text-transform:none}

/* the public pages' top strip: the wordmark, and at most one door opposite it
   for a person this page can catch. Deliberately NOT the whisper line — that
   carries what ANY stranger might want, at the bottom, in a fixed set of
   three; this catches one specific reader at the moment they arrive, before a
   long page they would otherwise have to scroll. Different job, so a separate
   thing rather than a fourth whisper door (which would wrap on a phone). */
.ltop{display:flex;align-items:baseline;justify-content:space-between;gap:16px;
      flex-wrap:wrap;margin-bottom:30px}
.ltop .wordmark{display:inline-block;margin-bottom:0}
.ltop-door{font-size:12px;color:var(--tx3)}
.ltop-door a{color:var(--tx2)}
.ltop-door a:hover{background:var(--accsoft);color:var(--acc2)}

/* ═══ THE AUTH FAMILY'S SHARED ATOMS (sweeps #2/#3/#5, owner 26-Jul-26) ═══
   Each of these existed as two-to-five byte-identical copies across template
   <style> blocks — which is how /waitlist came to print a visible "email"
   (the .sr copy that two pages had and one didn't) and how every rule touching
   the auth screens had to be applied twice. One declaration each, here. */

/* the wordmark, everywhere the name is set (was five copies) */
.wordmark{font-size:14px;font-weight:600;letter-spacing:.18em;color:var(--tx2)}
.wmcaret{color:var(--tx);animation:wmblink 1.15s steps(1) infinite}
@keyframes wmblink{0%,50%{opacity:1}51%,100%{opacity:0}}
@media (prefers-reduced-motion:reduce){.wmcaret{animation:none}}

/* the message line in three moods — instruction (.au-msg), confirmation
   (.au-ok), failure (.au-err, worn WITH .errline for the attention colour).
   THE DOOR SPEAKS IN THE PROWL TYPEFACE (owner, 26-Jul-26): verdict 76 puts
   sentences in sans, but on the entry screens the one line of prose is part
   of the front door's identity rather than a paragraph to read — the owner's
   exception, all three moods moving together so a screen doesn't change voice
   depending on whether it worked. Mono means lowercase, so they sit AFTER the
   case law's exemption list and the opt-back-in wins over .errline's app-wide
   opt-out. An empty failure line takes no room — templates render it always. */
.au-msg{font:13px/1.7 var(--mono);color:var(--tx2);margin-bottom:18px}
.au-ok{font:13px/1.7 var(--mono);color:var(--tx);margin-bottom:14px}
.au-err{text-align:left;width:100%;font:13px/1.7 var(--mono)}
.au-err:empty{display:none}
body.lc .au-msg,body.lc .au-ok,body.lc .au-err{text-transform:lowercase}

/* the field box — .au-in (a plain field) and .ef-in (the enter-on-type field)
   are ONE box; .ef-in only adds room for its own ↵. `.facct` is NOT in this
   family on purpose: the settings sheet's inline fields are deliberately
   smaller and quieter (card2, r3), a recorded sibling rather than a twin. */
.au-in,.efield .ef-in{width:100%;background:var(--card);border:1px solid var(--line2);
       border-radius:var(--r2);padding:13px 15px;outline:none;color:var(--tx);font-size:14px}
.au-in::placeholder,.efield .ef-in::placeholder{color:var(--tx3)}
.au-in:focus,.efield .ef-in:focus{border-color:var(--acc)}
.au-in{margin-bottom:12px}
.efield .ef-in{padding-right:46px}

/* the brand token (v90b): where a SANS sentence names prowl or an engine, the
   name speaks the chrome voice — the wordmark carried into copy. No colour:
   azure means clickable (v75), and a name is not a button. The negative
   word-spacing pulls the token's internal space back to sans rhythm (a mono
   space is a full glyph cell — "prowl canon" read as three words apart). */
.pn{font-family:var(--mono);font-weight:600;text-transform:lowercase;letter-spacing:.02em;
    word-spacing:-.45ch}

/* per-row actions inside a why's evidence (the parked duplicate files):
   quiet text buttons, revealed at rest — a why sheet has no hover states */
.ev-acts{margin-left:auto;display:inline-flex;gap:10px;flex:none}
.ev-acts form{display:contents}
.ev-acts .btn-g{font-size:12px;cursor:pointer}

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