/* ============================================================
   app.css — all styles, moved verbatim from the inline <style>
   block of the original single-file app. Do not reformat: the
   safe-area padding + box-sizing on :root are load-bearing.
   ============================================================ */
:root{
  --bg:#000; --card-bg:#141414; --text:#eee; --sub:#999; --accent:#4ade80; --danger:#f87171;
  box-sizing:border-box;
  padding-top:env(safe-area-inset-top,0px); padding-bottom:env(safe-area-inset-bottom,0px);
}
*{box-sizing:border-box}
html{scroll-padding-top:env(safe-area-inset-top,0px)}
html,body{height:100%;margin:0}
body{background:
    radial-gradient(circle at 12% 15%, rgba(99,102,241,.16), transparent 42%),
    radial-gradient(circle at 90% 10%, rgba(244,114,182,.13), transparent 40%),
    radial-gradient(circle at 50% 105%, rgba(16,185,129,.10), transparent 50%),
    linear-gradient(180deg,#0e0e13 0%,#050506 100%);
  color:var(--text);font-family:-apple-system,Segoe UI,Roboto,sans-serif;display:flex;flex-direction:column;min-height:100%}
/* ============================================================
   ICONS — one stroke system for every glyph in the app (the sprite
   lives at the top of index.html). .ic is square and strokes with
   currentColor, so an icon is always the colour of the label beside
   it — green inside a green button, red inside a delete control — and
   nothing ever falls back to the OS emoji font again. The rules below
   only set size and optical weight per control.
   ============================================================ */
.sprite{display:none}
.ic{width:1.1em;height:1.1em;flex:none;display:block;fill:none;stroke:currentColor;
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.ic-xl{width:30px;height:30px}
/* Controls that carry an icon + label: one baseline, one gap. */
.primary,.smallBtn,.del,.fileBtn{display:inline-flex;align-items:center;justify-content:center;gap:8px}
.addRow{display:flex;width:fit-content;align-items:center;gap:7px}
/* size per control — icons are balanced against 12-15px labels */
.iconBtn .ic{width:19px;height:19px}
.menuToggle .ic{width:19px;height:19px;transition:transform .2s ease}
.menuIco .ic{width:17px;height:17px}
/* .langBtn's 15px globe and its hover state went with the pill: the drawer sizes
   its own icons where the rows are (.menuIco above), and the language value is
   a word in a chip, so it carries no glyph at all.                           */
.primary .ic{width:17px;height:17px}
.del .ic,.remove .ic{width:16px;height:16px}
.smallBtn .ic,.fileBtn .ic,.addRow .ic{width:15px;height:15px}
.swatch.customSwatch .ic{width:16px;height:16px}
.anClose .ic{width:18px;height:18px}
.anVerdict .vi .ic{width:17px;height:17px}
.anEmpty .ic{margin:0 auto 10px;opacity:.65}
/* keyboard affordance for every control that can take focus */
button:focus-visible,.fileBtn:focus-visible,.remove:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Present to a screen reader, invisible to everyone else. Used by #deckLive,
   which announces the card that just came to the front. display:none would hide
   it from assistive tech too, which defeats the point. */
.srOnly{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}


/* ---- header ----------------------------------------------------------------
   The header carries the name and one button. Six round buttons used to sit
   here: at 38px each plus 8px gaps they measured about 262px, which on a 360px
   phone left the title a stack of its own and pushed the deck below the fold.
   What is left is what a person actually reads: the name, then the two numbers
   in .summary, then the card.                                                */
header{position:relative;padding:12px clamp(14px,3.4vw,20px) 10px;
  padding-top:calc(12px + env(safe-area-inset-top,0px))}
.headRow{display:flex;align-items:center;justify-content:space-between;gap:10px}
/* One line, because it has the whole row to itself now. nowrap + ellipsis is the
   honest answer to a longer name: a wrapped title is exactly what made the old
   header read 100px tall on a phone.                                         */
header h1{font-size:clamp(17px,4.4vw,19px);margin:0;font-weight:650;color:#fff;letter-spacing:-.2px;
  min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* position:relative so the button can lift above the scrim while the drawer is
   open — and only while it is open. A permanent z-index up here would paint this
   button over every sheet, i.e. a stray circle on someone's settings screen. */
.menuToggle{position:relative;flex:none}
.menuToggle.is-open{z-index:121;background:#26262a;border-color:#4a4a52}
.menuToggle.is-open .ic{animation:menuFlip .26s ease}
@keyframes menuFlip{from{transform:rotate(-90deg) scale(.72);opacity:.35}to{transform:none;opacity:1}}
/* ---- the drawer (markup: #headMenu, behaviour: setHeadMenu in ui.js) --------
   It slides down over the deck rather than pushing it: deckMetrics() caches a
   step measured from stage.clientWidth, so a layout push would slide the card
   out from under the thumb that had just opened the panel. z-index 120 clears
   both the scrim (110) and the cards, which paint near 100 from an inline style
   in paintDeck(). visibility, not display — display would skip the transition
   outright — and the delay sits only on the way out, which is what keeps the
   panel painted long enough to be seen leaving.                             */
.headMenu{position:absolute;top:100%;left:0;right:0;z-index:120;
  padding:0 clamp(12px,3.4vw,18px) 14px;
  opacity:0;visibility:hidden;transform:translateY(-10px);pointer-events:none;
  transition:opacity .18s ease,transform .3s cubic-bezier(.2,.8,.25,1),visibility 0s linear .3s}
.headMenu.open{opacity:1;visibility:visible;transform:none;pointer-events:auto;
  transition:opacity .16s ease,transform .32s cubic-bezier(.2,.85,.3,1.05),visibility 0s}
/* Seven rows is taller than a phone held sideways, so the panel scrolls itself
   rather than clipping 使用说明 out of existence. The 78px is the header plus the
   panel's own bottom padding — the drawer never reaches the home bar.          */
.headMenuInner{width:min(100%,372px);margin-left:auto;overflow:hidden;overflow-y:auto;
  overscroll-behavior:contain;
  max-height:calc(100vh - 78px);max-height:calc(100dvh - 78px);
  padding:5px;
  background:#121215;border:1px solid #2a2a30;border-radius:16px;
  box-shadow:0 26px 54px rgba(0,0,0,.66)}
/* 48px is the smallest row a thumb should be asked to aim at. Each row uses the
   same 31px icon tile as the manual's tour, so one shape keeps meaning "this
   opens a screen" wherever it appears.                                       */
.menuRow{display:flex;align-items:center;gap:11px;width:100%;min-height:48px;padding:0 10px;
  background:none;border:none;border-radius:11px;color:#e9e9ec;font:inherit;font-size:14px;
  font-weight:550;text-align:left;cursor:pointer;transition:background .15s,color .15s,transform .1s}
.menuRow+.menuRow{margin-top:1px}
.menuRow:hover{background:#1c1c20;color:#fff}
.menuRow:active{transform:scale(.985)}
.menuRow:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.menuRow:hover .menuIco{border-color:#4a4a52;color:#fff}
.menuIco{flex:none;width:31px;height:31px;border-radius:9px;display:flex;align-items:center;justify-content:center;
  background:#1d1d21;border:1px solid #2a2a2f;color:var(--sub);transition:border-color .15s,color .15s}
.menuTxt{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* .langLbl — the value chip. Its id and class are load-bearing: renderLangButton()
   in i18n.js rewrites exactly this span when the language changes.            */
.menuVal{flex:none;min-width:38px;padding:3px 9px;text-align:center;font-size:11.5px;font-weight:700;
  letter-spacing:.4px;color:#cfcfd4;background:#1f1f23;border:1px solid #33333a;border-radius:999px}
/* The one row worth colour, because for most people it is the reason the drawer
   was opened — 快捷加减, two taps to move money. It is also the first thing a
   keyboard user lands on.                                                    */
.menuRow.menuGo{color:var(--accent)}
.menuRow.menuGo .menuIco{background:rgba(74,222,128,.12);border-color:rgba(74,222,128,.3);color:var(--accent)}
.menuRow.menuGo:hover{background:rgba(74,222,128,.07)}
.menuRow.menuGo:hover .menuIco{border-color:rgba(74,222,128,.5);color:var(--accent)}
/* Rows land one after another; by then the panel has already slid. This is the
   settle, not the entrance — 180ms end to end, so it never feels slow twice. */
.headMenu.open .menuRow{animation:menuRowIn .3s cubic-bezier(.2,.8,.25,1) backwards}
.headMenu.open .menuRow:nth-child(2){animation-delay:.03s}
.headMenu.open .menuRow:nth-child(3){animation-delay:.06s}
.headMenu.open .menuRow:nth-child(4){animation-delay:.09s}
.headMenu.open .menuRow:nth-child(5){animation-delay:.12s}
.headMenu.open .menuRow:nth-child(6){animation-delay:.15s}
.headMenu.open .menuRow:nth-child(7){animation-delay:.18s}
@keyframes menuRowIn{from{opacity:0;transform:translateY(-7px)}to{opacity:1;transform:none}}
/* Tapping outside is the fastest way out on a phone, so the tap-catcher has to
   exist — above the deck, which is why it is a body child and not a child of the
   panel (a transformed ancestor re-anchors position:fixed).                   */
.menuScrim{position:fixed;inset:0;z-index:110;background:rgba(3,3,5,.55);
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .24s ease,visibility 0s linear .24s}
.menuScrim.open{opacity:1;visibility:visible;pointer-events:auto;transition:opacity .24s ease,visibility 0s}
/* .addBtn (the green +) and .langBtn (the globe pill) went with that row: the +
   is now a drawer row, and the pill is a row whose value sits in .menuVal.
   #langBtn and its .langLbl came along for the ride, because
   renderLangButton() rewrites that span and nothing else. The .menuGo green
   hands over to 快捷加减, which has taken the + as the drawer's first row.   */
.iconBtn{background:#1e1e1e;color:#fff;border:1px solid #333;width:38px;height:38px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s,border-color .15s,transform .12s}
.iconBtn:active{transform:scale(.92)}
.iconBtn:hover{background:#26262a;border-color:#4a4a52}
.fileBtn:hover{border-color:#4a4a52;color:#fff}
/* ---- the two numbers ---------------------------------------------------------
   That used to be one grey line — 共 3 张卡片 | 总资产 RM 8,200.00 — where the total
   was 12px and the count was a whole sentence. Two labelled tiles now, on the
   same tile language as .recStats and .setTiles below, so a number with a name
   over it looks the same everywhere in the app. Both always draw: an empty wall
   reads 0 and RM 0.00 instead of losing the strip and letting the deck jump.  */
.summary{display:flex;gap:9px;padding:0 clamp(14px,3.4vw,20px) 10px}
.sumTile{flex:1;min-width:0;padding:8px 11px;border:1px solid #23232a;border-radius:13px;
  background:linear-gradient(160deg,#16161a,#101013)}
.sumTile span{display:block;font-size:9.5px;letter-spacing:.9px;text-transform:uppercase;
  color:var(--sub);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sumTile b{display:block;font-size:15px;font-weight:650;color:#fff;letter-spacing:-.2px;
  font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Only one number on this screen is money, so only one gets the accent. */
.sumTile.sumAssets{border-color:rgba(74,222,128,.24);
  background:linear-gradient(160deg,rgba(74,222,128,.08),#101013 62%)}
.sumTile.sumAssets b{color:var(--accent)}
/* ---- phone polish ------------------------------------------------------------
   The deck already owns the horizontal axis (touch-action:pan-y on .stage) and
   the page itself never scrolls, so a vertical overscroll reveals nothing — it
   only fires the browser's pull-to-refresh under a thumb that meant to swipe.
   And the grey tap flash iOS paints over every control fights the :active
   states this app already has.                                               */
html,body{overscroll-behavior-y:none}
button,.dot,.chipBtn,.menuRow,.segMini button,.qcCat,.qcBtn{-webkit-tap-highlight-color:transparent}
@media (max-width:400px){
  .summary{gap:8px}
  .sumTile{padding:8px 10px}
  .menuRow{font-size:13.5px;gap:10px}
  .headMenuInner{width:100%}
}
/* (the two rules that used to sit here — a 12px grey .summary and its bold half —
   are the tiles above. Neither number is a footnote now, so neither is styled
   like one.)                                                                 */
/* (and .summary b — the old 600-weight white — is .sumTile b now.) */
/* The pager under the deck. min-height is kept even when it is empty (one card
   needs no pager) so the stage cannot jump sideways when a second card arrives.
   Dots stay 7px wide and get their touch target from .dot::after, because the
   honest minimum for a finger is 44×28 and padding would move the dots apart —
   so the gap is 10px, which makes the pitch (17px) exactly as wide as the hit
   box and stops two neighbours from fighting over the same tap. The appearance
   reset is not decoration either: iOS paints a button it was not told to paint,
   and its own bead would show through the dot's background.                   */
.dots{display:flex;justify-content:center;align-items:center;gap:10px;min-height:34px;
  padding:0 0 calc(16px + env(safe-area-inset-bottom,0px))}
.dot{position:relative;appearance:none;-webkit-appearance:none;display:block;
  width:7px;height:7px;padding:0;border:0;background:#3a3a3a;border-radius:50%;
  cursor:pointer;transition:background .25s ease,width .25s ease,border-radius .25s ease}
.dot::after{content:'';position:absolute;top:-12px;bottom:-12px;left:-5px;right:-5px}
.dot.active{background:var(--accent);width:22px;border-radius:4px}
/* .stage is the drag surface. touch-action:pan-y hands the browser the vertical
   axis and reserves the horizontal one for ui.js, which is also what stops iOS
   reading a swipe starting near the left edge as "go back". */
.stage{position:relative;flex:1;display:flex;align-items:center;justify-content:center;perspective:1400px;overflow:hidden;touch-action:pan-y}
/* Held: the deck must be welded to the finger. Easing here puts a card a frame
   behind the touch, and that lag is exactly what makes a carousel feel cheap. */
.stage.drag{cursor:grabbing}
.stage.drag .card{transition:none}
/* The card is as wide as the screen allows, so on a phone the balance reads
   without squinting; 300px is the ceiling it has always had on a desktop. */
.card{position:absolute;left:50%;top:50%;width:min(300px,86vw);aspect-ratio:1.586/1;cursor:pointer;border-radius:20px;padding:20px 22px;
  display:flex;flex-direction:column;justify-content:space-between;overflow:hidden;
  box-shadow:0 14px 32px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.08);
  transition:transform .5s cubic-bezier(.22,.68,0,1.05), opacity .4s ease, filter .4s ease, box-shadow .4s ease;
  will-change:transform,opacity;user-select:none;-webkit-user-select:none;
  -webkit-touch-callout:none}
.card.active{box-shadow:0 28px 60px rgba(0,0,0,.72), inset 0 1px 0 rgba(255,255,255,.16)}
.card::before{content:'';position:absolute;inset:0;background:linear-gradient(115deg,rgba(255,255,255,.16) 0%,rgba(255,255,255,0) 30%,rgba(255,255,255,0) 55%,rgba(255,255,255,.1) 80%,rgba(255,255,255,0) 100%);background-size:100% 100%;pointer-events:none}
.card.active::before{background-size:260% 260%;animation:sheen 6s ease-in-out infinite}
@keyframes sheen{0%,100%{background-position:0% 20%}50%{background-position:100% 80%}}
.card::after{content:'';position:absolute;width:220px;height:220px;border-radius:50%;background:radial-gradient(circle,rgba(255,255,255,.10),transparent 70%);top:-80px;right:-60px;pointer-events:none}
.card .top-row{display:flex;justify-content:space-between;align-items:flex-start;position:relative;z-index:1}
.top-row .right-col{display:flex;flex-direction:column;align-items:flex-end;gap:7px}
.tier{font-size:9px;letter-spacing:2px;opacity:.7}
.chip{width:38px;height:28px;border-radius:6px;
  background:repeating-linear-gradient(0deg,rgba(0,0,0,.16) 0 1px,transparent 1px 6px),linear-gradient(135deg,#f4e5a1,#d8b34a);
  position:relative}
.chip::before{content:'';position:absolute;inset:4px;border:1px solid rgba(0,0,0,.25);border-radius:3px}
.chip::after{content:'';position:absolute;left:50%;top:0;bottom:0;width:1px;background:rgba(0,0,0,.25)}
.network{display:flex}
.network span{width:20px;height:20px;border-radius:50%;background:rgba(255,255,255,.55);display:block}
.network span:last-child{margin-left:-8px;background:rgba(255,255,255,.85)}
.card .number{font-family:'Courier New',monospace;letter-spacing:3px;font-size:16px;opacity:.92;position:relative;z-index:1}
.card .bottom-row{display:flex;justify-content:space-between;align-items:flex-end;position:relative;z-index:1}
.card .holder{font-size:10px;letter-spacing:1.2px;text-transform:uppercase;opacity:.85}
.card .holder b{display:block;font-size:14px;letter-spacing:.5px;margin-top:3px;text-transform:none}
.card .balance-label{font-size:9px;opacity:.75;text-transform:uppercase;letter-spacing:1px;text-align:right}
.card .balance{font-size:19px;font-weight:700;text-align:right}
.card.light .network span{background:rgba(0,0,0,.28)}
.card.light .network span:last-child{background:rgba(0,0,0,.5)}
.card .petals{position:absolute;inset:0;pointer-events:none;opacity:.9}
/* The wall with nothing on it. It used to be a sentence that pointed at a + in
   the header; it is now a sentence and the button it points at, stacked, which
   is why this is a column rather than a centred line of grey. .primary is flex:1
   everywhere else — it is a footer button — so this one row takes that back. */
.empty{color:var(--sub);text-align:center;margin:auto;font-size:13.5px;line-height:1.7;
  display:flex;flex-direction:column;align-items:center;gap:15px;padding:0 26px}
.empty p{margin:0}
.emptyAdd{flex:none;padding:11px 20px;font-size:14px}
.themeGrid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:6px}
.swatch{border-radius:12px;height:56px;border:2px solid transparent;cursor:pointer;position:relative;display:flex;align-items:flex-end;padding:6px;font-size:10px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.6)}
.swatch.active{border-color:var(--accent)}
.swatch.customSwatch{background:#1e1e1e;color:#ccc;align-items:center;justify-content:center;gap:7px;text-shadow:none}
.imgPreview{width:100%;aspect-ratio:1.586/1;border-radius:14px;background:#1e1e1e center/cover no-repeat;display:none;margin-top:8px;border:1px solid #333}
.fileBtn{display:inline-flex;align-items:center;justify-content:center;gap:8px;background:#1e1e1e;border:1px solid #333;color:#ccc;padding:9px 14px;border-radius:10px;font-size:13px;cursor:pointer;margin-top:6px;transition:border-color .15s,color .15s}
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.75);display:none;align-items:flex-end;justify-content:center;z-index:10}
.overlay.show{display:flex}
.sheet{background:#111;width:100%;max-width:480px;border-radius:20px 20px 0 0;padding:20px;padding-bottom:calc(24px + env(safe-area-inset-bottom,0px));
  max-height:85vh;overflow-y:auto}
.sheet::before{content:'';display:block;width:40px;height:4px;background:#333;border-radius:2px;margin:0 auto 16px}
/* Scoped away from .anSheet deliberately. .sheet h2 (0,1,1) beats .anH2
   (0,1,0), so left unscoped it overrode the margin and font-size of every
   .anBlkHead heading inside the workspace shells — all four of them render at
   17px instead of the intended 11.5px small caps. Now it only styles the title
   of a legacy plain .sheet, of which there are none left.                   */
.sheet:not(.anSheet) h2{margin:0 0 14px;font-size:17px}
label{font-size:12px;color:var(--sub);display:block;margin:12px 0 4px}
input,select{width:100%;background:#1e1e1e;border:1px solid #333;color:#fff;padding:10px;border-radius:10px;font-size:14px;transition:border-color .15s,box-shadow .15s}
input:focus,select:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(74,222,128,.15)}
.row{display:flex;gap:8px;align-items:center;margin-bottom:6px}
.row input{flex:1}
.smallBtn{background:#2a2a2a;color:#fff;border:1px solid #444;border-radius:8px;padding:6px 10px;font-size:12px;cursor:pointer;transition:background .15s}
.smallBtn:hover{background:#333}
.addRow{color:var(--accent);font-size:13px;cursor:pointer;margin-top:4px}
.allocSummary{font-size:12px;margin-top:8px;display:flex;justify-content:space-between;font-weight:600}
.allocSummary.ok{color:var(--accent)}
.allocSummary.warn{color:var(--danger)}
.stackBar{display:flex;height:14px;border-radius:8px;overflow:hidden;margin:4px 0 14px;background:#222}
.jarBarRow{margin:10px 0}
.jarBarRow .jarTop{display:flex;justify-content:space-between;font-size:13px;margin-bottom:4px}
.jarBarRow .jarTop span:last-child{color:var(--sub)}
.jarBar{height:8px;border-radius:5px;background:#222;overflow:hidden}
.jarBar div{height:100%;border-radius:5px}
/* .btns went with the add / edit form: its actions live in a .sheetFoot now,
   where .primary and .ghostBtn are sized as a pair (see the footer block). */
.primary{flex:1;background:var(--accent);color:#000;border:none;border-radius:12px;padding:12px;font-weight:600;cursor:pointer;transition:filter .15s}
.primary:hover{filter:brightness(1.08)}
.del{background:transparent;color:var(--danger);border:1px solid var(--danger);border-radius:12px;padding:12px;cursor:pointer}
/* .miniCard / .miniChip / .detail went when the detail sheet moved onto the
   workspace shell — .cardFace and .detPanel replaced them (see below). */
/* .section / .section h3 went the same way: the form's groups are .wsBlk blocks
   inside the shell, so their headings are the shared .anBlkHead + .anH2 pair. */
.remove{flex:none;width:32px;height:32px;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;
  color:var(--danger);background:rgba(248,113,113,.07);border:1px solid rgba(248,113,113,.22);border-radius:9px;
  transition:background .15s,border-color .15s,transform .12s}
.remove:hover{background:rgba(248,113,113,.16);border-color:rgba(248,113,113,.45)}
.remove:active{transform:scale(.92)}
.dedRow{background:#181818;border:1px solid #2a2a2a;border-radius:12px;padding:10px;margin-bottom:8px}
.dedRow select{margin-top:0}

/* ============================================================
   Records, statements, analysis — added with the transaction-ledger,
   interest-projection and financial-analysis features.
   ============================================================ */
#printArea{display:none}
/* Capture stage for the jsPDF path. html2canvas refuses display:none, so this
   is laid out for real and pushed off-viewport instead of being hidden.
   position (not static) so a 700px-tall document cannot create scrollbars. */
#pdfStage{position:fixed;top:0;left:-100000px;width:703px;background:#fff;z-index:-1;pointer-events:none}
.pdfBtns{display:flex;gap:10px;margin-top:10px}
.pdfBtns .smallBtn{align-self:stretch;padding:6px 16px;white-space:nowrap}
.pdfBtns .primary:disabled{opacity:.6;cursor:progress}
/* Which month the document covers. It sits under the two export buttons rather
   than in the filter bar above because it belongs to the paper, not to the list:
   the screen shows whatever the ledger filters ask for, the statement prints
   this month. A stepper, not a <select> — iOS has no month picker, and the
   month someone wants is nearly always this one or the one before it.          */
.stScope{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:10px}
.stScopeK{font-size:10px;letter-spacing:.8px;text-transform:uppercase;color:#6c6c72}
.stScopeNav{display:inline-flex;align-items:center;gap:2px;background:#171719;
  border:1px solid #2c2c31;border-radius:11px;padding:2px}
.stScopeM{min-width:112px;padding:0 6px;text-align:center;font-size:13px;font-weight:700;
  color:#e8e8e8;white-space:nowrap;font-variant-numeric:tabular-nums}
.stNavBtn{width:38px;height:38px;display:inline-flex;align-items:center;justify-content:center;
  background:none;border:none;border-radius:9px;color:var(--sub);cursor:pointer;font:inherit;font-size:15px;
  transition:background .15s,color .15s}
.stNavBtn:hover{background:#232327;color:#fff}
.stNavBtn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.stNavBtn:disabled{opacity:.3;cursor:not-allowed}
/* .headTitle, .headActions and .menuBtn went with the six-button header row:
   .headRow / .menuToggle / .headMenu above replace them, and the ledger
   shortcut that used to sit beside the title is a drawer row like any other. */
.hint{font-size:12px;color:var(--sub);line-height:1.65;margin:0 0 8px}
.amtIn{color:var(--accent)}
.amtOut{color:var(--danger)}

/* ---- records ------------------------------------------------------------
   Filters are chips and segments instead of <select>s: a native dropdown shows
   one option at a time, renders in the OS style on every platform, and never
   tells you that a filter is switched on. The list groups by month, because
   that is the unit a person actually reads a statement in.                  */
.recBar{flex:none;padding:11px clamp(14px,3vw,26px) 12px;background:#0d0d10;border-bottom:1px solid #232327}
.recSearch{display:flex;align-items:center;gap:8px;background:#171719;border:1px solid #2c2c31;border-radius:11px;padding:0 9px;color:var(--sub);transition:border-color .15s,box-shadow .15s,color .15s}
.recSearch:focus-within{border-color:rgba(74,222,128,.5);box-shadow:0 0 0 3px rgba(74,222,128,.12);color:#e8e8e8}
.recSearch .ic{width:15px;height:15px;flex:none}
.recSearch input{flex:1;min-width:0;background:none;border:none;padding:10px 0;font-size:13.5px}
.recSearch input:focus{outline:none;box-shadow:none;border:none}
.recClear{flex:none;width:26px;height:26px;display:flex;align-items:center;justify-content:center;background:#222226;border:none;border-radius:50%;color:var(--sub);cursor:pointer;transition:background .15s,color .15s}
.recClear:hover{background:rgba(248,113,113,.18);color:var(--danger)}
.recClear .ic{width:11px;height:11px}
/* display:flex above would otherwise beat the hidden attribute */
.recClear[hidden]{display:none}
.chipRow{display:flex;align-items:center;gap:6px;margin-top:9px;overflow-x:auto;scrollbar-width:none}
.chipRow::-webkit-scrollbar{display:none}
.chipBtn{flex:none;min-height:32px;padding:0 12px;border-radius:999px;background:#171719;border:1px solid #2c2c31;color:var(--sub);font:inherit;font-size:12px;font-weight:600;white-space:nowrap;cursor:pointer;transition:background .15s,border-color .15s,color .15s}
.chipBtn:hover{border-color:#4a4a52;color:#fff}
.chipBtn.on{background:rgba(74,222,128,.13);border-color:rgba(74,222,128,.5);color:var(--accent)}
.chipBtn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.chipTag{flex:none;font-size:10px;letter-spacing:.8px;text-transform:uppercase;color:#6c6c72;padding-right:2px}
.recNote{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:10px;font-size:11px;color:var(--sub);font-variant-numeric:tabular-nums}
.recNote:empty{display:none}
.recReset{display:inline-flex;align-items:center;gap:5px;background:none;border:none;padding:4px 7px;border-radius:8px;color:var(--sub);font:inherit;font-size:11.5px;cursor:pointer;transition:background .15s,color .15s}
.recReset:hover{background:rgba(248,113,113,.12);color:var(--danger)}
.recReset .ic{width:12px;height:12px}
.recStats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:9px}
.recStats>div{background:linear-gradient(160deg,#17171a,#111113);border:1px solid #26262a;border-radius:13px;padding:10px 7px;text-align:center;min-width:0}
.recStats>div.net{border-color:rgba(74,222,128,.26);background:linear-gradient(160deg,rgba(74,222,128,.09),#111113 65%)}
.recStats>div.net.down{border-color:rgba(248,113,113,.28);background:linear-gradient(160deg,rgba(248,113,113,.09),#111113 65%)}
.recStats span{display:block;font-size:9.5px;letter-spacing:.9px;text-transform:uppercase;color:var(--sub);margin-bottom:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.recStats b{display:block;font-size:12.5px;font-weight:650;font-variant-numeric:tabular-nums;letter-spacing:-.2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.recStats>div.net b{font-size:14.5px}
/* One scroll area for the whole screen: the old 46vh list inside the 85vh sheet
   produced two nested scrollbars that fought each other. */
.txList{padding:1px 0 4px}
.txGroup{margin-bottom:10px}
.txGroupHead{position:sticky;top:-1px;z-index:2;display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  padding:9px 0 7px;background:#0d0d10;border-bottom:1px solid #1d1d21;
  font-size:10.5px;letter-spacing:1px;text-transform:uppercase;color:var(--sub);font-variant-numeric:tabular-nums}
.txGroupHead b{font-weight:650;letter-spacing:-.2px;font-size:11.5px;text-transform:none}
.txRow{display:flex;align-items:flex-start;gap:11px;padding:11px 0;border-bottom:1px solid #1e1e22}
.txRow:last-child{border-bottom:none}
/* the direction badge carries the meaning the old rows asked colour alone to
   carry: green in, red out, blue across two cards, grey for something typed in
   by hand. */
.txIco{width:29px;height:29px;flex:none;border-radius:10px;display:flex;align-items:center;justify-content:center;background:#1c1c20;color:var(--sub)}
.txIco .ic{width:15px;height:15px}
.txIco.in{background:rgba(74,222,128,.13);color:var(--accent)}
.txIco.out{background:rgba(248,113,113,.13);color:var(--danger)}
.txIco.tr{background:rgba(96,165,250,.13);color:#60a5fa}
.txMain{flex:1;min-width:0}
.txTitle{font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.txMeta{font-size:11px;color:var(--sub);margin-top:3px}
.txCard{background:#242424;border-radius:4px;padding:1px 5px;margin-left:4px}
.txAmt{flex-shrink:0;text-align:right}
.txAmt .amtIn,.txAmt .amtOut{font-size:13.5px;font-weight:600;font-variant-numeric:tabular-nums}
.txBal{font-size:10.5px;color:var(--sub);margin-top:3px;font-variant-numeric:tabular-nums}
.txEmpty{padding:30px 10px;text-align:center;color:var(--sub);font-size:12.5px;line-height:1.9}
.txEmpty .ic{margin:0 auto 10px;opacity:.5}
.txEmpty .recReset{margin-top:6px}
/* Manual entry sits behind its own bar, so the list is not pushed below a form
   that is open all the time and used once a month. */
.recAddBar{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;min-height:44px;margin-top:13px;
  background:#141417;border:1px dashed #34343b;border-radius:13px;color:var(--accent);font:inherit;font-size:13px;font-weight:600;cursor:pointer;transition:background .15s,border-color .15s}
.recAddBar:hover{background:rgba(74,222,128,.07);border-color:rgba(74,222,128,.42)}
.recAddBar .ic{width:15px;height:15px}
.recForm{display:none;margin-top:10px;padding:13px;background:#131315;border:1px solid #26262a;border-radius:14px}
.recForm.show{display:block}
.recForm .row{margin-top:10px}
.recForm .row select{min-width:0}
#m_amt{max-width:112px}
.recForm .recSave{flex:none;padding:10px 16px}
.segMini{display:flex;gap:6px}
.segMini button{flex:1;min-height:38px;background:#171719;border:1px solid #2c2c31;color:var(--sub);border-radius:10px;font:inherit;font-size:12.5px;font-weight:600;cursor:pointer;transition:background .15s,border-color .15s,color .15s}
.segMini button:hover{border-color:#4a4a52;color:#fff}
.segMini button.on{background:rgba(74,222,128,.13);border-color:rgba(74,222,128,.5);color:var(--accent)}
.segMini button.on.out{background:rgba(248,113,113,.13);border-color:rgba(248,113,113,.45);color:var(--danger)}

/* ---- the workspace shell: analysis, records and settings -----------------
   These three screens are workspaces rather than forms, so they share one
   container: .anOverlay (darker backdrop) + .anSheet (fixed header, its own
   scroll area, no grab handle). Sizes use clamp() so one rule serves a 320px
   phone and a 1400px desktop without a breakpoint per step. .wsNarrow is the
   phone-sized version — only the analysis grid is wide enough to be worth
   1080px; records and settings stay at reading width and centre themselves.  */
.anOverlay{align-items:flex-end;background:rgba(0,0,0,.82)}
.anSheet{width:min(100%,1080px);max-width:1080px;padding:0;overflow:hidden;
  border-radius:22px 22px 0 0;background:#0d0d10;
  display:flex;flex-direction:column;
  height:min(94vh,940px);max-height:94vh;
  height:min(94dvh,940px);max-height:94dvh}          /* dvh where supported */
.anSheet.wsNarrow{width:min(100%,560px);max-width:560px}
.anSheet::before{display:none}                        /* the grab handle is replaced by a real header */
.wsBlk{margin-top:clamp(18px,3vw,26px)}
.wsBlk:first-child{margin-top:0}
@media (min-width:860px){
  .anOverlay{align-items:center;padding:clamp(16px,4vw,44px)}
  .anSheet{height:auto;max-height:min(880px,92vh);border-radius:20px}
}
@media (max-width:640px){
  .anSheet{height:100vh;max-height:100vh;height:100dvh;max-height:100dvh;border-radius:0}
}

/* fixed header — title, context line, close */
.anHead{display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;
  padding:calc(15px + env(safe-area-inset-top,0px)) clamp(14px,3vw,26px) 14px;
  background:#0d0d10;border-bottom:1px solid #232327;flex:none}
.anHeadMain{flex:1;min-width:170px}
.anEyebrow{font-size:10px;letter-spacing:1.6px;text-transform:uppercase;color:var(--sub);margin:0 0 5px}
.anH1{margin:0;font-size:clamp(19px,3.2vw,25px);font-weight:650;letter-spacing:-.2px;color:#fff;line-height:1.2}
.anScope{font-size:11.5px;color:var(--sub);margin:6px 0 0;font-variant-numeric:tabular-nums}
.anClose{width:38px;height:38px;flex:none;border-radius:50%;background:#1e1e1e;border:1px solid #333;
  color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:border-color .15s,color .15s,transform .12s}
.anClose:hover{border-color:var(--danger);color:var(--danger)}
.anClose:active{transform:scale(.92)}

/* horizon / language / period pickers: real tap targets, not a tiny <select> */
.anSeg{display:flex;gap:6px;flex-wrap:wrap;padding:12px clamp(14px,3vw,26px) 0;flex:none}
.anSeg button,.segLang button{flex:1 1 74px;min-width:70px;min-height:42px;background:#171719;border:1px solid #2c2c31;
  color:var(--sub);border-radius:11px;font:inherit;font-size:12.5px;font-weight:600;padding:0 12px;
  cursor:pointer;transition:background .15s,border-color .15s,color .15s}
.anSeg button:hover,.segLang button:hover{border-color:#4a4a52;color:#fff}
.anSeg button.on,.segLang button.on{background:rgba(74,222,128,.13);border-color:rgba(74,222,128,.5);color:var(--accent)}
.anSeg button:focus-visible,.segLang button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* The custom horizon field takes a line of its own rather than being a sixth
   chip: five 70px chips plus gaps do not fit 320px (that is the same arithmetic
   .anPeriod works around), and a number box squeezed into the row would be about
   50px wide. It only exists while the Custom chip is open, so the fixed head
   costs nothing until it is asked for. The 16px is the iOS rule — a smaller
   focused field zooms the page and leaves you panning around half an app.     */
.anSegNum{flex:1 1 100%;display:flex;align-items:center;gap:8px;min-height:42px;margin-top:6px;
  background:#171719;border:1px solid #2c2c31;border-radius:11px;padding:0 12px}
.anSegNum:focus-within{border-color:rgba(74,222,128,.5)}
.anMonthsIn{flex:1;min-width:0;padding:0;background:transparent;border:0;outline:none;color:#fff;
  font-family:inherit;font-size:16px;font-weight:600;text-align:right;font-variant-numeric:tabular-nums}
.anSegUnit{font-size:11.5px;color:var(--sub);white-space:nowrap}

.anScroll{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  padding:clamp(14px,3vw,24px) clamp(14px,3vw,26px) calc(clamp(18px,3vw,26px) + env(safe-area-inset-bottom,0px))}
.anBlk{margin-top:clamp(18px,3vw,26px)}
.anBlk:first-child{margin-top:0}
.anBlkHead{display:flex;align-items:baseline;justify-content:space-between;gap:10px 14px;flex-wrap:wrap;margin-bottom:11px}
.anH2{margin:0;font-size:11.5px;color:var(--sub);text-transform:uppercase;letter-spacing:1.1px;font-weight:600}
.anCap{font-size:11px;color:var(--sub);font-variant-numeric:tabular-nums}
.anCap b{color:#e2e2e2;font-weight:600}
/* the storage figure carries its own glyph, so the caption is a flex row */
.anCapIco{display:inline-flex;align-items:center;gap:5px}
.anCapIco .ic{width:12px;height:12px;flex:none;opacity:.8}
/* hero: the two numbers that matter, then the supporting figures */
.anHero{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,250px),1fr));gap:12px}
.anHeroCard{padding:clamp(14px,2.3vw,20px);border-radius:16px;min-width:0;
  background:linear-gradient(160deg,#17171a,#111113);border:1px solid #26262a}
.anHeroCard.fc{background:linear-gradient(160deg,rgba(74,222,128,.10),#111113 60%);border-color:rgba(74,222,128,.26)}
.anHeroCard.fc.down{background:linear-gradient(160deg,rgba(248,113,113,.10),#111113 60%);border-color:rgba(248,113,113,.28)}
/* Second card in the hero row: the same money with one round of every deduction
   rule taken off. It borrows the red family the falling forecast already uses —
   the third colour is spoken for by spending categories — and only the figure
   turns red when the deductions are more than the total they came out of.     */
.anHeroCard.ded{background:linear-gradient(160deg,rgba(248,113,113,.10),#111113 60%);border-color:rgba(248,113,113,.26)}
.anHeroCard.ded.neg .anHeroVal{color:#f87171}
.anHeroLbl{display:block;font-size:10.5px;letter-spacing:1px;text-transform:uppercase;color:var(--sub)}
.anHeroVal{font-size:clamp(26px,4.4vw,36px);font-weight:700;line-height:1.15;margin:9px 0 0;
  letter-spacing:-.6px;color:#fff;font-variant-numeric:tabular-nums;word-break:break-word}
.anHeroVal .cur{font-size:.52em;font-weight:600;color:var(--sub);letter-spacing:0;margin-right:5px}
.anHeroNote{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin-top:11px;font-size:11.5px;color:var(--sub)}

.anChip{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:999px;font-size:11.5px;
  font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap;border:1px solid #2c2c31;color:var(--sub);background:#1b1b1e}
.anChip.up{color:var(--accent);background:rgba(74,222,128,.12);border-color:rgba(74,222,128,.3)}
.anChip.down{color:var(--danger);background:rgba(248,113,113,.12);border-color:rgba(248,113,113,.3)}

.anVerdict{display:flex;gap:12px;align-items:flex-start;margin-top:14px;padding:14px 15px;border-radius:14px;
  font-size:13.5px;line-height:1.7;background:rgba(74,222,128,.08);border:1px solid rgba(74,222,128,.25)}
.anVerdict .vi{width:27px;height:27px;flex:none;margin-top:1px;border-radius:50%;display:flex;align-items:center;
  justify-content:center;background:rgba(74,222,128,.15);color:var(--accent)}
.anVerdict.down .vi{background:rgba(248,113,113,.15);color:var(--danger)}
.anVerdict.down{background:rgba(248,113,113,.08);border-color:rgba(248,113,113,.28)}
.anVerdict b{color:#fff}

/* forecast chart */
.anChart{display:flex;align-items:flex-end;gap:clamp(2px,.55vw,5px);position:relative;overflow:hidden;
  height:clamp(140px,22vh,220px);padding:12px 12px 0;border-radius:14px;
  background:linear-gradient(#151517,#101012);border:1px solid #26262a}
.anChart::before{content:'';position:absolute;left:12px;right:12px;top:calc(50% - 6px);
  border-top:1px dashed #2a2a2f;pointer-events:none}
.barCol{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%}
.bar{width:100%;min-height:3px;border-radius:4px 4px 1px 1px;opacity:.92;transition:filter .15s,opacity .15s}
.barCol:hover .bar{opacity:1;filter:brightness(1.4)}
.barLbl{font-size:9.5px;color:var(--sub);margin-top:7px;height:11px;line-height:11px;
  font-variant-numeric:tabular-nums;overflow:hidden}
.anEmpty{padding:26px 16px;text-align:center;font-size:13px;line-height:1.9;color:var(--sub);
  background:#131315;border:1px dashed #2e2e33;border-radius:14px}

/* Monthly in vs out. The old "monthly net" chart was 12 identical bars: the
   projected flow is constant, so repeating it carried no information.       */
.anFlow{display:grid;gap:14px}
.anFlowTop{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:7px}
.anFlowLbl{font-size:11px;letter-spacing:.7px;text-transform:uppercase;color:var(--sub)}
.anFlowVal{font-size:clamp(14px,2.2vw,16px);font-weight:650;font-variant-numeric:tabular-nums}
.anFlowBar{position:relative;height:14px;border-radius:8px;background:#17171a;border:1px solid #26262a;overflow:hidden}
.anFlowBar i{position:absolute;left:0;top:0;bottom:0;border-radius:7px;transition:width .35s ease}
.anFlowBar.in i{background:linear-gradient(90deg,rgba(74,222,128,.45),rgba(74,222,128,.9))}
.anFlowBar.out i{background:linear-gradient(90deg,rgba(248,113,113,.45),rgba(248,113,113,.9))}
.anFlowNet{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding-top:13px;border-top:1px solid #232327}
/* ---- the entry-driven blocks (2026-09-29) ---------------------------------
   The period picker, the category list and the two note lines. Everything else
   they are built from already existed: .segMini, .anFlow/.anFlowTop/.anFlowBar,
   .anChip, .anChart/.barCol, .anVerdict, .anNone, .anEmpty, .recAddBar. Only
   what has no existing equivalent gets a rule, so this screen cannot drift into
   owning a second version of a control the rest of the app already has.     */
.anPeriod{margin-bottom:14px}
/* four period chips have to survive 320px on one row: the UA padding a plain
   button brings would wrap "All time" onto two lines and grow the row. */
.anPeriod .segMini button{padding:0 6px;font-size:12px;white-space:nowrap}
.anGap{margin-top:clamp(16px,2.6vw,22px)}
.anMoveNote{display:flex;align-items:flex-start;gap:8px;margin-top:12px;
  font-size:12px;line-height:1.65;color:var(--sub)}
.anMoveNote .ic{width:14px;height:14px;flex:none;margin-top:2px;opacity:.7}
.anMoveNote b{color:#e8e8e8;font-weight:600;font-variant-numeric:tabular-nums}
.anCatList{display:grid;gap:15px}
.anCatList .anVerdict{margin-top:4px}
/* a group name is the user's own wording most of the time, so it is not
   uppercased the way a section label is, and it ellipsises instead of wrapping
   the money value onto a second line. */
.anCatName{display:block;min-width:0;text-transform:none;letter-spacing:0;
  font-size:13px;color:#e8e8e8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.anCatPct{margin-left:8px;font-size:11.5px;font-weight:600;color:var(--sub)}
/* amber: the app's third colour (see .popCard.warn), so a spending bar is never
   mistaken for the green/red in-and-out bars above it. */
.anFlowBar.cat i{background:linear-gradient(90deg,rgba(250,204,21,.40),rgba(250,204,21,.86))}
.anCatMeta{font-size:11px;color:var(--sub);margin-top:6px;font-variant-numeric:tabular-nums}
@media (max-width:400px){
  .anCatMeta{font-size:10.5px}
  .anCatPct{margin-left:6px}
}
.anCard{background:#131315;border:1px solid #26262a;border-radius:16px;padding:clamp(13px,1.8vw,17px);min-width:0}
/* per-card panels — the editable half of the screen. They flow into columns
   only when there is genuinely room for a 330px panel.                     */
.anCards{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,330px),1fr));gap:12px;align-items:start}
.anCardHead{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}
.anCardId{display:flex;align-items:center;gap:10px;min-width:0;flex:1}
.anCardChip{width:40px;height:27px;flex:none;border-radius:6px;background-size:cover;background-position:center;
  border:1px solid rgba(255,255,255,.16);box-shadow:inset 0 1px 0 rgba(255,255,255,.18)}
.anCardName{font-size:15px;font-weight:600;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.anCardMeta{font-size:11.5px;color:var(--sub);margin-top:4px;font-variant-numeric:tabular-nums}
.anCardProj{flex:none;text-align:right}
.anCardProj > span{display:block;font-size:9.5px;color:var(--sub);letter-spacing:.6px;text-transform:uppercase}
.anCardProj b{font-size:clamp(16px,2.1vw,19px);color:var(--accent);font-variant-numeric:tabular-nums;white-space:nowrap}
.anCardProj .anChip{margin-top:7px}
.anInt{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:14px 0 2px;padding:11px 12px;
  background:#19191b;border:1px solid #26262a;border-radius:12px}
.anIntToggle{flex:1;min-width:126px;display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;margin:0}
.anIntToggle input{width:auto;height:16px;flex:none;accent-color:var(--accent)}
.anRate{width:84px !important;flex:none;text-align:right;font-size:16px;font-variant-numeric:tabular-nums}
.anPct{font-size:12.5px;color:var(--sub)}
.anInt select{width:auto;flex:none;font-size:13px;padding:9px 10px}
.anRate:disabled,.anInt select:disabled{opacity:.45}
.anIntOut{flex-basis:100%;font-size:12px;color:var(--sub);margin-top:5px;font-variant-numeric:tabular-nums}
.anIntOut b{color:#fff;font-weight:600}
.anRule{display:flex;align-items:center;gap:10px;min-height:46px;padding:5px 0;border-bottom:1px solid #1f1f22}
.anRule:last-child{border-bottom:none}
.anRuleName{flex:1;min-width:0;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.anRuleFreq{flex:none;max-width:40%;font-size:10.5px;color:var(--sub);background:#1a1a1c;border:1px solid #26262a;
  border-radius:999px;padding:3px 9px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* 16px on purpose: iOS zooms the page when a focused input is smaller. */
.anRuleAmt{width:112px !important;flex:none;text-align:right;font-size:16px;font-variant-numeric:tabular-nums;padding:9px 10px}
@media (max-width:400px){ .anRuleAmt{width:94px !important} .anRate{width:70px !important} }
.intPreview{margin-top:10px;font-size:12px;color:var(--sub);line-height:1.7;background:#1b1b1b;
  border-left:2px solid var(--accent);padding:9px 11px;border-radius:0 8px 8px 0}
.intPreview b{color:#fff}

.anGroupLbl{display:block;font-size:10px;color:var(--sub);letter-spacing:1px;text-transform:uppercase;
  margin:16px 0 3px;padding-top:12px;border-top:1px solid #232327}
.anNone{font-size:12px;color:var(--sub);padding:11px 10px;text-align:center;background:#151517;
  border:1px dashed #2a2a2f;border-radius:10px}
/* ---- the card detail sheet (2026-09-27) ---------------------------------
   .anOverlay + .anSheet already give this screen the fixed header and its own
   scroll area that records and settings use; what it adds on top is a footer,
   so 交易记录 / 编辑 / 删除 can never fall below the fold again. Three pieces
   of vocabulary: .cardFace (a card drawn outside the carousel), .detPanel with
   .detLine rows (a labelled money row), and .detInt (the interest forecast).
   The old .detail / .miniCard rules this screen used are gone.             */

/* A card face anywhere — the detail sheet, the add/edit preview. .card owns the
   carousel's 3D wall (absolute positioning, transform, sheen), so this repeats
   only the typography and lets the caller choose the width. */
.cardFace{width:100%;max-width:340px;aspect-ratio:1.586/1;border-radius:18px;margin:0 auto;
  padding:clamp(14px,4vw,20px) clamp(15px,4.4vw,22px);position:relative;overflow:hidden;
  display:flex;flex-direction:column;justify-content:space-between;
  box-shadow:0 14px 32px rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.08)}
.cardFace .top-row{display:flex;justify-content:space-between;align-items:flex-start;position:relative;z-index:1}
.cardFace .number{font-family:'Courier New',monospace;letter-spacing:3px;font-size:clamp(12px,3.6vw,16px);opacity:.92;position:relative;z-index:1}
.cardFace .bottom-row{display:flex;justify-content:space-between;align-items:flex-end;position:relative;z-index:1}
.cardFace .holder{font-size:9.5px;letter-spacing:1.2px;text-transform:uppercase;opacity:.85}
.cardFace .holder b{display:block;font-size:13px;letter-spacing:.5px;margin-top:3px;text-transform:none}
.cardFace .balance-label{font-size:9px;opacity:.75;text-transform:uppercase;letter-spacing:1px;text-align:right}
.cardFace .balance{font-size:clamp(15px,4.6vw,19px);font-weight:700;text-align:right;font-variant-numeric:tabular-nums}
.cardFace.light .network span{background:rgba(0,0,0,.28)}
.cardFace.light .network span:last-child{background:rgba(0,0,0,.5)}

/* the figure the whole sheet is about */
.detHero{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  margin:16px 0 12px;padding:12px 15px;background:linear-gradient(160deg,#17171a,#111113);
  border:1px solid #26262a;border-radius:14px}
.detHero span{font-size:10px;letter-spacing:1.1px;text-transform:uppercase;color:var(--sub);white-space:nowrap}
.detHero b{font-size:clamp(20px,5.4vw,27px);font-weight:700;letter-spacing:-.4px;font-variant-numeric:tabular-nums}

/* one bordered panel per group; .detLine is one rule inside it */
.detPanel{background:#131315;border:1px solid #232327;border-radius:14px;padding:3px 13px}
.detPanel .anNone{margin:10px 0}
.detLine{display:flex;align-items:center;justify-content:space-between;gap:12px;
  min-height:50px;padding:9px 0;border-bottom:1px solid #1f1f22}
.detLine:last-child{border-bottom:none}
.detLine .dl{display:flex;align-items:center;gap:10px;min-width:0}
.detLine .dic{flex:none;width:29px;height:29px;border-radius:9px;background:#1d1d20;border:1px solid #2a2a2f;
  display:flex;align-items:center;justify-content:center;color:var(--sub)}
.detLine .dic .ic{width:15px;height:15px}
.detLine .dt{font-size:13.5px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.detLine .dt em{display:block;font-style:normal;font-size:10.5px;color:var(--sub);margin-top:2px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.detLine .dm{flex:none;font-size:13.5px;font-weight:650;font-variant-numeric:tabular-nums;white-space:nowrap}
.detLine .dm.tr{color:#60a5fa;font-weight:600}

/* interest forecast — only drawn when the card actually earns */
.detInt{margin-top:12px;padding:13px 15px;border-radius:14px;
  background:linear-gradient(160deg,rgba(74,222,128,.07),#111113 62%);border:1px solid rgba(74,222,128,.22)}
.detIntHead{display:flex;align-items:center;gap:9px;font-size:11px;letter-spacing:1.1px;
  text-transform:uppercase;color:var(--sub);font-weight:600}
.detIntHead .ic{width:15px;height:15px;color:var(--accent)}
.detIntHead em{margin-left:auto;font-style:normal;letter-spacing:0;text-transform:none;
  font-size:11px;color:#cfe9d8;white-space:nowrap}
.detIntGrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:11px}
.detIntGrid>div{background:#151517;border:1px solid #26262a;border-radius:11px;padding:9px 11px;min-width:0}
.detIntGrid span{display:block;font-size:9.5px;letter-spacing:.9px;text-transform:uppercase;color:var(--sub);margin-bottom:5px}
.detIntGrid b{display:block;font-size:14.5px;font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.detNote{margin:10px 0 0;font-size:11px;line-height:1.55;color:var(--sub)}

/* Actions that stay put. The sheet is a flex column, so the footer is never
   something you have to scroll to reach — the reason this screen was rebuilt. */
.sheetFoot{flex:none;display:flex;align-items:center;gap:9px;
  padding:12px clamp(14px,3vw,26px) calc(13px + env(safe-area-inset-bottom,0px));
  background:#0d0d10;border-top:1px solid #232327}
.ghostBtn{flex:1;min-width:0;min-height:46px;display:inline-flex;align-items:center;justify-content:center;gap:8px;
  background:#1a1a1d;border:1px solid #2e2e33;border-radius:12px;color:#e6e6e6;
  font:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:10px 12px;
  transition:background .15s,border-color .15s,transform .12s}
.ghostBtn:hover{background:#232327;border-color:#45454d;color:#fff}
.ghostBtn:active{transform:scale(.97)}
.ghostBtn .ic{width:15px;height:15px}
.iconDanger{flex:none;width:46px;height:46px;display:inline-flex;align-items:center;justify-content:center;
  background:rgba(248,113,113,.07);border:1px solid rgba(248,113,113,.32);border-radius:12px;
  color:var(--danger);cursor:pointer;transition:background .15s,border-color .15s,transform .12s}
.iconDanger:hover{background:rgba(248,113,113,.16);border-color:rgba(248,113,113,.55)}
.iconDanger:active{transform:scale(.94)}
.iconDanger .ic{width:17px;height:17px}
.sheetFoot .primary{margin:0;padding:10px 12px;min-height:46px}
@media (max-width:420px){
  .detIntGrid{grid-template-columns:minmax(0,1fr)}
  .ghostBtn{font-size:12px;letter-spacing:-.2px}
}

/* ---- quick cash (markup: quickCashHTML in ui.js) --------------------------
   The sheet a held card or a right-clicked card brings up. Bottom-anchored on a
   phone — the thumb is already down there — and a centred 540px panel from 760px
   up, where a bottom sheet would sit three inches from the pointer.

   Four things the sizes are chosen for:
   - the amount field is the largest text on the sheet. It is what you are here
     to say, and on a phone it is typed with a numeric keypad that hides half of
     the sheet anyway;
   - the six quick amounts are six equal keys under that field, and the four
     category tiles are one row of four at every width, so neither the amount nor
     the choice ever becomes a scroll;
   - the two action buttons are 60px tall, carry the resulting balance, and close
     the sheet: they are sticky and they are the last thing in the panel, so with
     the keypad open they are the only part still reachable, and with it shut they
     are where the sheet ends;
   - .cashCard scrolls itself rather than the page, so a transfer with a note
     still gets to both buttons.                                          */
.cashOverlay{z-index:15;align-items:flex-end;background:rgba(3,3,5,.74)}
.cashCard{width:min(100%,540px);max-height:min(92vh,92dvh);overflow:auto;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
  display:flex;flex-direction:column;
  background:#111;border:1px solid #26262a;border-bottom:none;border-radius:22px 22px 0 0;
  padding:14px clamp(14px,4vw,20px) 0;
  box-shadow:0 -16px 46px rgba(0,0,0,.62);
  animation:cashUp .26s cubic-bezier(.22,.8,.28,1)}
@keyframes cashUp{from{opacity:0;transform:translateY(26px)}to{opacity:1;transform:none}}
@media (min-width:760px){
  .cashOverlay{align-items:center;padding:clamp(16px,4vw,40px)}
  .cashCard{border-radius:20px;border-bottom:1px solid #26262a;max-height:min(88vh,780px)}
}
.qcHead{display:flex;align-items:flex-start;gap:12px;padding-top:2px}
.qcHeadMain{flex:1;min-width:0}
.qcWho{margin:0;font-size:clamp(17px,4.6vw,21px);font-weight:650;letter-spacing:-.2px;color:#fff;
  line-height:1.2;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.qcBal{display:flex;align-items:baseline;gap:9px;margin:7px 0 0}
.qcBal span{font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--sub);white-space:nowrap}
.qcBal b{font-size:clamp(16px,4.2vw,19px);font-weight:700;font-variant-numeric:tabular-nums;color:var(--accent)}
.qcLbl{margin:16px 0 0;font-size:10.5px;letter-spacing:.9px;text-transform:uppercase;
  color:var(--sub);font-weight:600}
/* the amount, and nothing else, gets the big type */
.qcAmtWrap{display:flex;align-items:center;gap:10px;margin-top:8px;padding:9px 14px;
  background:#17171a;border:1px solid #2c2c31;border-radius:14px;
  transition:border-color .15s,background .15s}
.qcAmtWrap:focus-within{border-color:rgba(74,222,128,.55);background:#191c1a}
.qcRM{flex:none;font-size:15px;font-weight:700;letter-spacing:.4px;color:var(--sub)}
.qcAmt{flex:1;min-width:0;background:transparent;border:none;border-radius:0;color:#fff;
  font:inherit;font-size:clamp(23px,7vw,30px);font-weight:700;letter-spacing:-.5px;
  font-variant-numeric:tabular-nums;padding:2px 0}
.qcAmt::placeholder{color:#55555c;font-weight:600}
.qcAmt:focus{outline:none;border:none;background:transparent;box-shadow:none}
/* Chips inside the sheet are 40px rather than the ledger's 32: these are tapped
   one-handed on a phone, and the +500 next to a 44px button would look small. */
.cashCard .chipRow{margin-top:9px}
.cashCard .chipBtn{min-height:40px;font-size:12.5px}
/* The six quick amounts are six equal columns, not the ledger's scrolling
   .chipRow: the row is a fixed length, so the chips run edge to edge with the
   amount field above them instead of trailing off into a third of the sheet, and
   a row that cannot overflow sideways cannot steal a vertical swipe from a sheet
   that already scrolls. min-width:0 (which beats .qcStepC's 46px on specificity)
   is what keeps the sixth key inside a 320px phone.                        */
.cashCard .qcSteps{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-top:9px}
.cashCard .qcSteps .chipBtn{min-width:0;padding:0 4px;text-align:center}
.qcStepC{min-width:46px;font-weight:700;letter-spacing:.5px}
.qcNone{margin:10px 0 0;font-size:12px;line-height:1.5;color:var(--sub)}
.qcCats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-top:8px}
.qcCat{min-height:66px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;
  background:#171719;border:1px solid #2c2c31;border-radius:13px;color:var(--sub);
  font:inherit;font-size:11.5px;font-weight:650;cursor:pointer;padding:8px 3px;
  transition:background .15s,border-color .15s,color .15s,transform .12s}
.qcCat .ic{width:20px;height:20px}
.qcCat span{max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.qcCat:hover{border-color:#4a4a52;color:#fff}
.qcCat.on{background:rgba(74,222,128,.12);border-color:rgba(74,222,128,.5);color:var(--accent)}
.qcCat:active{transform:scale(.97)}
.qcCat:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* The two decisions. Sticky so the open keypad cannot bury them, and each one
   states the balance it produces — the answer, not just the ask. The negative
   margins pull the bar across the sheet's own side padding, so a chip scrolling
   behind it is covered rather than peeking through the gutter (same trick as
   .sheetFoot, which is why its padding repeats the sheet's).
   Two things it also has to be, because it is now the LAST thing in the sheet
   (.qcHint was moved above it — see ui.js):
   - flush with the bottom edge, so the band reads as the panel's footer rather
     than as a stripe with a line of grey text dangling under it;
   - the keeper of env(safe-area-inset-bottom). That padding had lived on the
     hint, which is exactly the line a pinned sticky bar covers — so on an iPhone
     the two money buttons, not a footnote, were what sat under the home
     indicator. Nothing else in this sheet can carry it.                    */
.qcBtns{position:sticky;bottom:0;z-index:1;display:flex;gap:10px;margin:8px calc(-1 * clamp(14px,4vw,20px)) 0;
  padding:12px clamp(14px,4vw,20px) calc(12px + env(safe-area-inset-bottom,0px));
  background:#111;border-top:1px solid #1f1f22}
.qcBtn{flex:1;min-width:0;min-height:60px;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:3px;border-radius:14px;font:inherit;cursor:pointer;padding:8px 6px;
  transition:background .15s,border-color .15s,transform .12s}
.qcBtnMain{display:flex;align-items:center;gap:7px;font-size:14.5px;font-weight:700}
.qcBtnMain .ic{width:16px;height:16px;flex:none}
.qcBtnSub{max-width:100%;font-size:12px;font-weight:650;opacity:.82;font-variant-numeric:tabular-nums;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.qcBtn.out{background:rgba(248,113,113,.10);border:1px solid rgba(248,113,113,.42);color:#fca5a5}
.qcBtn.out:hover{background:rgba(248,113,113,.18);border-color:rgba(248,113,113,.62)}
.qcBtn.in{background:rgba(74,222,128,.12);border:1px solid rgba(74,222,128,.5);color:var(--accent)}
.qcBtn.in:hover{background:rgba(74,222,128,.2);border-color:rgba(74,222,128,.7)}
.qcBtn:active{transform:scale(.97)}
.qcBtn:focus-visible{outline:2px solid #fff;outline-offset:2px}
/* Sits above the action bar (see .qcBtns) and is the dialog's aria-describedby,
   so it has to describe the sheet — not explain the gesture that opened it. */
.qcHint{margin:16px 0 0;font-size:11px;line-height:1.5;color:#6c6c72;text-align:center}
@media (max-width:400px){
  .qcCat{gap:5px;font-size:11px}
  .qcBtnMain{font-size:13.5px}
}
@media (max-width:340px){
  .qcCats{gap:6px}
  .qcCat .ic{width:18px;height:18px}
}

/* ---- the add / edit form on the workspace shell (2026-09-27) ------------
   Same .anOverlay + .anSheet as detail / records / settings, plus the two
   things a form needs and a read-only workspace does not: .wsForm, a width
   wide enough to hold the fields and the card face side by side, and
   .formWrap, the grid that puts 卡片预览 beside the fields instead of above
   them. The live preview is the reason this sheet was rebuilt — it redraws on
   every keystroke, so 新增罐子 has nowhere left to push it.                  */
.anSheet.wsForm{width:min(100%,880px);max-width:880px}
.formWrap{display:grid;grid-template-columns:minmax(0,1fr);gap:clamp(14px,3vw,26px);align-items:start}
.formMain{min-width:0}
.formMain > label:first-child{margin-top:0}
.formPreview{margin:0}
.formPreview .cardFace{max-width:260px}
/* the two jar actions read as one group of choices, not two stacked links */
.addRowGrp{display:flex;flex-wrap:wrap;gap:6px 18px;margin-top:6px}
/* Phone: the card opens the form with a rule under it, so the fields below
   still read as a list of their own instead of a continuation of the preview. */
@media (max-width:859px){
  .formPreview{padding-bottom:14px;border-bottom:1px solid #232327}
}
/* Wide: a real second column, and the preview pins to the top of the scroller.
   The panel is painted in the sheet colour, so the fields slide behind it
   quietly instead of under the card. top:0 is the scrollport edge, which is why
   the panel carries its own padding.                                         */
@media (min-width:860px){
  .formWrap{grid-template-columns:272px minmax(0,1fr)}
  .formAside{position:sticky;top:0;z-index:1;background:#0d0d10;
    padding:clamp(10px,1.6vw,18px) 12px calc(clamp(10px,1.6vw,18px) + 2px)}
  .formPreview .cardFace{max-width:100%}
}

@media (max-width:420px){
  .recStats{grid-template-columns:repeat(2,minmax(0,1fr))}
  .recStats>div.net{grid-column:1 / -1}
  .setTiles{gap:8px}
}

/* ---- settings -----------------------------------------------------------
   Same shell as the other two workspaces, so only the content blocks need
   rules: an overview strip, the backup column, a language segment that shows
   which language is live, and an erase block that cannot be mistaken for the
   harmless actions above it.                                                */
.setTiles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
.setTile{background:linear-gradient(160deg,#17171a,#111113);border:1px solid #26262a;border-radius:14px;padding:13px 8px 11px;text-align:center;min-width:0}
.setTile .ic{width:16px;height:16px;color:var(--sub);margin:0 auto}
.setTile b{display:block;font-size:15.5px;font-weight:700;margin-top:8px;font-variant-numeric:tabular-nums;letter-spacing:-.3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.setTile span{display:block;font-size:9.5px;letter-spacing:.9px;text-transform:uppercase;color:var(--sub);margin-top:4px;white-space:nowrap;overflow:hidden}
.wideBtn{width:100%;margin-top:10px;justify-content:center}
.setNote{font-size:11.5px;color:var(--sub);line-height:1.6;margin:9px 0 0}
.setNote b{color:#e2e2e2;font-weight:600;font-variant-numeric:tabular-nums}
.hideFile{display:none}
.fallback{display:none;margin-top:13px;padding:12px;background:#131315;border:1px solid #26262a;border-radius:14px}
.fallback.show{display:block}
.fallback .smallBtn{margin-top:9px}
.jsonBox{width:100%;height:120px;background:#1e1e1e;border:1px solid #333;color:#fff;border-radius:10px;padding:10px;font-size:11px;font-family:monospace;resize:vertical}
.dangerBtn{display:inline-flex;align-items:center;justify-content:center;gap:8px;width:100%;min-height:46px;
  background:rgba(248,113,113,.07);border:1px solid rgba(248,113,113,.32);border-radius:13px;color:var(--danger);
  font:inherit;font-size:13.5px;font-weight:600;cursor:pointer;transition:background .15s,border-color .15s,transform .12s}
.dangerBtn:hover{background:rgba(248,113,113,.15);border-color:rgba(248,113,113,.55)}
.dangerBtn:active{transform:scale(.99)}
.dangerBtn .ic{width:16px;height:16px}
.aboutNote{display:flex;gap:9px;font-size:12px;color:var(--sub);line-height:1.75}
.aboutNote .ic{width:15px;height:15px;flex:none;margin-top:3px;color:#5f5f66}

/* ============================================================
   THE USER MANUAL (#helpOverlay)

   The shell is borrowed wholesale — .anOverlay, .anSheet.wsNarrow, .anHead,
   .anScroll, .wsBlk, .anBlkHead/.anH2, .anCap, .sheetFoot — so this block
   only styles what is new to it: a brand plate, a numbered walk-through,
   an icon-to-purpose table, a list of caveats, and the author card.
   Nothing here redeclares a shell class; the panel is meant to look like
   its siblings came off the same press.
   The logo artwork ships on a solid #000 field, which is why .manBrand is
   flat #000: the plate and the artwork are the same black, so the image has
   no visible edge and reads as a mark resting on the plate rather than a
   picture in a frame. The accent gradient on .manCrest is the logo's own
   green→blue, reused once, so the mark and the author read as one thing.
   ============================================================ */
.manBrand{padding:clamp(18px,4vw,26px) 16px clamp(16px,3.4vw,20px);border-radius:18px;
  background:#000;border:1px solid #232327;text-align:center;
  box-shadow:0 0 0 1px rgba(74,222,128,.05), 0 14px 34px -16px rgba(74,222,128,.18)}
/* The file is far wider than it is tall AND heavily letterboxed — the lockup
   only occupies a band across the vertical middle — so height:auto alone would
   paint a tall black slab with a small mark stranded in the centre of it. The
   box declares an aspect ratio wider than the artwork and covers it: cover
   scales by width, so the crop is always vertical, the full wordmark always
   survives, and what gets cut is the artwork's own dead margin. Re-exporting
   the file at a different ratio moves the crop, never the mark.            */
.manLogo{display:block;width:100%;max-width:300px;aspect-ratio:4.4/1;
  object-fit:cover;object-position:center;margin:0 auto}
.manTag{margin:13px 0 0;font-size:12.5px;line-height:1.6;color:var(--sub);letter-spacing:.2px}

.manSteps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:9px}
.manStep{display:flex;align-items:flex-start;gap:12px;padding:12px 13px;
  background:#131315;border:1px solid #232327;border-radius:14px}
.manNum{flex:none;width:23px;height:23px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  background:rgba(74,222,128,.12);border:1px solid rgba(74,222,128,.3);color:var(--accent);
  font-size:11.5px;font-weight:700;font-variant-numeric:tabular-nums}
.manStepTxt{margin:0;font-size:13px;line-height:1.68;color:#d6d6da}

/* icon → what that button opens, so the manual names the screen the same way
   the tooltip does instead of inventing a second vocabulary for it */
.manTour{background:#131315;border:1px solid #232327;border-radius:14px;padding:2px 13px}
.manRow{display:flex;align-items:flex-start;gap:12px;padding:12px 0;border-bottom:1px solid #1f1f22}
.manRow:last-child{border-bottom:none}
.manIco{flex:none;width:30px;height:30px;border-radius:9px;display:flex;align-items:center;justify-content:center;
  background:#1d1d20;border:1px solid #2a2a2f;color:var(--sub)}
.manIco .ic{width:15px;height:15px}
.manRowMain{flex:1;min-width:0}
.manRowT{margin:0;font-size:13px;font-weight:600;color:#fff}
.manRowD{margin:3px 0 0;font-size:11.5px;line-height:1.62;color:var(--sub)}

.manTips{display:flex;flex-direction:column;gap:9px}
.manTip{display:flex;gap:10px;margin:0;font-size:12px;line-height:1.72;color:var(--sub)}
.manTip::before{content:'';flex:none;width:5px;height:5px;margin-top:7px;border-radius:50%;background:#3d3d44}

.manCreator{display:flex;align-items:center;gap:13px;padding:14px;border-radius:16px;
  background:linear-gradient(160deg,#17171a,#111113);border:1px solid #26262a}
.manCrest{flex:none;width:44px;height:44px;border-radius:13px;display:flex;align-items:center;justify-content:center;
  background:linear-gradient(135deg,#4ade80,#38bdf8 55%,#3b82f6);color:#05140d;
  font-size:15px;font-weight:800;letter-spacing:.4px}
.manCMain{min-width:0}
.manCName{margin:0;font-size:16px;font-weight:700;letter-spacing:-.2px;color:#fff}
.manCNote{margin:5px 0 0;font-size:11.5px;line-height:1.62;color:var(--sub)}
.manMail{display:flex;align-items:center;justify-content:center;gap:9px;width:100%;min-height:46px;margin-top:10px;
  background:rgba(74,222,128,.07);border:1px solid rgba(74,222,128,.3);border-radius:13px;
  color:var(--accent);font-size:13px;font-weight:600;text-decoration:none;
  transition:background .15s,border-color .15s,transform .12s}
.manMail:hover{background:rgba(74,222,128,.15);border-color:rgba(74,222,128,.55);color:#6ee7a0}
.manMail:active{transform:scale(.99)}
.manMail:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.manMail .ic{width:16px;height:16px}
/* the address is one long unbroken token — it has to be allowed to wrap rather
   than push the button wider than the sheet */
.manMail span{min-width:0;overflow-wrap:anywhere}
.manMailCap{margin:9px 0 0;font-size:11px;line-height:1.6;color:var(--sub);text-align:center}
.manVer{margin:clamp(18px,3vw,24px) 0 0;font-size:10.5px;letter-spacing:.4px;color:#5f5f66;
  text-align:center;font-variant-numeric:tabular-nums}
@media (max-width:420px){
  .manRowT{font-size:12.5px}
  .manStepTxt{font-size:12.5px}
  .manMail{font-size:12px}
}

/* ============================================================
   POP-OUTS — the confirm / notice card and the toast strip.

   These exist because alert() and confirm() were the only surfaces in
   the app the browser draws: a flat system strip headed with the origin
   ("127.0.0.1 says"), no control over the button labels, and a page that
   freezes until it is dismissed. .popCard is built from the same parts
   as every other surface here — #121215 panels, 1px #2a2a30 edges, the
   accent/danger pair from :root — and sits at z-index 40, above the
   .overlay sheets at 10, because it is asked on top of them.
   Markup: #popOverlay / #toasts in index.html. Logic: assets/js/dialog.js.
   ============================================================ */
.popOverlay{position:fixed;inset:0;z-index:40;display:none;align-items:center;justify-content:center;
  padding:22px;background:rgba(3,3,5,.74);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}
.popOverlay.show{display:flex}
.popCard{width:min(100%,392px);max-height:min(78vh,560px);overflow-y:auto;
  background:#121215;border:1px solid #2a2a30;border-radius:18px;padding:22px 20px 18px;
  box-shadow:0 26px 64px rgba(0,0,0,.62);animation:popIn .18s ease-out}
@keyframes popIn{from{opacity:0;transform:translateY(10px) scale(.97)}to{opacity:1;transform:none}}
.popIcon{width:38px;height:38px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  background:rgba(148,163,184,.10);color:#cbd5e1;margin-bottom:13px}
.popIcon .ic{width:19px;height:19px}
.popCard.danger .popIcon{background:rgba(248,113,113,.13);color:var(--danger)}
.popCard.err .popIcon{background:rgba(248,113,113,.13);color:var(--danger)}
.popCard.warn .popIcon{background:rgba(250,204,21,.12);color:#facc15}
.popCard.ok .popIcon{background:rgba(74,222,128,.12);color:var(--accent)}
.popTitle{margin:0 0 6px;font-size:16px;font-weight:650;letter-spacing:-.1px}
/* pre-line is what lets an existing message keep its \n breaks — c_jar_sum and
   the PDF fallback are two-line strings, and the old alert() rendered them so. */
.popMsg{margin:0;font-size:13.5px;line-height:1.72;color:#c9c9cf;white-space:pre-line;overflow-wrap:anywhere}
.popBtns{display:flex;gap:9px;margin-top:20px}
.popBtn{flex:1;min-width:0;min-height:44px;display:inline-flex;align-items:center;justify-content:center;gap:7px;
  background:#1a1a1d;border:1px solid #2e2e33;border-radius:12px;color:#e6e6e6;
  font:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:10px 12px;
  transition:background .15s,border-color .15s,transform .12s}
.popBtn:hover{background:#232327;border-color:#45454d;color:#fff}
.popBtn:active{transform:scale(.97)}
.popBtn .ic{width:15px;height:15px}
/* :focus and not :focus-visible — popDrain() focuses the default button
   programmatically, and the ring is the only cue that Enter is armed. */
.popBtn:focus{outline:2px solid var(--accent);outline-offset:2px}
/* the button that actually answers the question — green to proceed, red when
   proceeding is the thing that cannot be undone */
.popBtn.go{background:var(--accent);border-color:var(--accent);color:#04140a;font-weight:700}
.popBtn.go:hover{background:#5ee894;border-color:#5ee894;color:#04140a;filter:none}
.popCard.danger .popBtn.go,.popCard.err .popBtn.go{background:var(--danger);border-color:var(--danger);color:#1b0505}
.popCard.danger .popBtn.go:hover,.popCard.err .popBtn.go:hover{background:#fb9a9a;border-color:#fb9a9a;color:#1b0505}
@media (max-width:420px){ .popBtn{font-size:12px;letter-spacing:-.2px;padding:10px 9px} }

/* Toasts — the notices nobody has to acknowledge. Anchored above the safe-area
   inset so they clear the home bar on a phone, and click-to-dismiss because a
   toast that hides the row you are reading is the reason people dislike them. */
.toasts{position:fixed;left:50%;transform:translateX(-50%);z-index:50;
  bottom:calc(18px + env(safe-area-inset-bottom,0px));
  width:min(calc(100% - 28px),420px);display:flex;flex-direction:column;gap:8px;pointer-events:none}
.toast{display:flex;align-items:flex-start;gap:9px;pointer-events:auto;cursor:pointer;
  background:rgba(20,20,25,.96);border:1px solid #2e2e33;border-radius:13px;padding:11px 13px;
  font-size:13px;line-height:1.6;color:#eaeaea;white-space:pre-line;overflow-wrap:anywhere;
  box-shadow:0 14px 34px rgba(0,0,0,.45);opacity:0;transform:translateY(10px);
  transition:opacity .22s ease,transform .22s ease}
.toast.in{opacity:1;transform:none}
.toast.out{opacity:0;transform:translateY(6px)}
.toast .ic{width:16px;height:16px;flex:none;margin-top:2px;color:var(--accent)}
.toast.err{border-color:rgba(248,113,113,.42)}
.toast.err .ic{color:var(--danger)}
@media (prefers-reduced-motion:reduce){
  /* The deck speaks entirely in movement, so under reduce it stops easing
     rather than stopping moving — a swipe still pages cards, they simply
     arrive on the frame they are committed. The sheen sweep is pure
     decoration and goes with it.                                            */
  .card{transition:none}
  .card.active::before{animation:none}
  .popCard{animation:none}
  .toast{transition:none}
  .manMail{transition:none}
  /* The drawer still opens and closes — it simply arrives already there. The
     rows keep their order, they just stop shuffling into it one by one.     */
  .headMenu,.menuScrim,.menuToggle .ic,.menuRow{transition:none}
  .headMenu.open .menuRow,.menuToggle.is-open .ic{animation:none}
  /* Quick cash keeps its positions and loses its slide, in line with the drawer
     and the pop-outs above it.                                               */
  .cashCard{animation:none}
}

/* ============================================================
   PHONE + APPLE PASS (2026-09-27)

   Every rule in here exists because of how iOS, Android and a laptop
   trackpad actually behave — not because of how the page looks. They
   are grouped under a comment each so any one of them can be lifted
   out on its own if it ever misbehaves on a real device.
   ============================================================ */

/* Safari re-sizes text on its own in landscape (it "improves" readability by
   inflating body copy), which pushes the deck below the fold on a phone turned
   sideways. Pinning the adjustment makes clamp() the only thing that decides a
   size anywhere in the app. */
html{ -webkit-text-size-adjust:100%; text-size-adjust:100% }

/* A tap on a control is never a request to zoom. Without this iOS waits out its
   double-tap window on every button — the ~300ms that makes 存入 feel slow — and
   if a second tap lands inside that window it magnifies the whole app into the
   button that was just pressed, which the deck cannot be read through. */
button,.dot,.chipBtn,.menuRow,.fileBtn,.anClose,.iconBtn,.recClear,.recReset,
.qcCat,.qcBtn,.ghostBtn,.primary,.dangerBtn,.del,.manMail,.recAddBar,
.segMini button,.segLang button,.anSeg button,.stNavBtn,.popBtn{ touch-action:manipulation }

/* Safari still needs the -webkit- spelling of backdrop-filter; both are on
   .popOverlay where it is declared (see the pop-out block above), so the blur
   survives on every iOS version rather than going flat. */

/* macOS Safari paints an autofilled field pale yellow and sets its own dark
   text — on this app that is #eee on cream, i.e. a card name that is there but
   cannot be read. box-shadow is how the fill is forced back, text-fill-color is
   the only property that beats autofill's colour, caret keeps the cursor visible. */
input:-webkit-autofill,select:-webkit-autofill,textarea:-webkit-autofill,
input:-webkit-autofill:hover,input:-webkit-autofill:focus{
  -webkit-box-shadow:0 0 0 1000px #1e1e1e inset, 0 0 0 3px rgba(74,222,128,.15);
  -webkit-text-fill-color:#fff; caret-color:#fff; border-color:#333 }
#qcAmt:-webkit-autofill{ -webkit-box-shadow:none; border-color:transparent }

/* Momentum scrolling. iOS needs the prefix to hand a scroll area its flick;
   without it a 400-row ledger moves one row per drag instead of gliding. */
.anScroll,.popCard,.sheet,.jsonBox,.cashCard{ -webkit-overflow-scrolling:touch }

/* The warning colour a number field wears while its contents cannot be read —
   see junkNumber in ui.js. Red rather than a toast: the field is the problem and
   the red goes away the moment it is fixed. */
.badNum,.badNum:focus{ border-color:var(--danger) !important;
  box-shadow:0 0 0 3px rgba(248,113,113,.18) !important }

/* ---- the viewport kit's two variables (viewportKit in ui.js) --------------
   --vvh is the height genuinely visible; --kb is how much of the bottom the
   on-screen keyboard has taken. Both default to "nothing to do", so before any
   script runs — and on every browser that already shrinks its layout viewport —
   the sheets below are exactly the sizes they always were. */
:root{ --vvh:100vh; --kb:0px }
/* A bottom sheet is lifted by the whole keyboard; a centred one by half of it,
   because flex centres the margin box. One property, both layouts. */
.anSheet,.cashCard,.popCard{ margin-bottom:var(--kb,0px) }
/* ---- phone and small tablet (the same 859px line the deck and the form
   already break on, so no new breakpoint is introduced) ------------------- */
@media (max-width:859px){
  /* iOS zooms the PAGE when a focused control's text is smaller than 16px, and it
     does not undo that zoom when the field loses focus — you are left panning
     around half an app, which is how this app felt broken to more than one
     reviewer. 16px is the entire fix. .anRuleAmt already carried it for exactly
     this reason; no other field did. */
  input,select,textarea{font-size:16px}
  .recSearch input{font-size:16px}
  .jsonBox{font-size:16px}   /* monospace at 11px still triggers the iOS zoom */
  /* 44pt is Apple's minimum and a thumb misses anything under it. What the app
     already sized for a finger (.qcBtn 60, .qcCat 66, .sheetFoot 46, .anSeg 42,
     .dot's 44×28 target) is untouched; these are the ones measured with a mouse:
     the form's fields, the ledger's filter chips, the ✕ that clears a search. */
  input:not([type=checkbox]):not([type=color]):not([type=range]),select{min-height:44px}
  .chipBtn{min-height:40px}
  .stNavBtn{width:44px;height:44px}
  .recClear{width:32px;height:32px}
  .recClear .ic{width:13px;height:13px}
  /* Cap the sheets to what is genuinely visible. 100dvh / 94dvh is the tallest
     the viewport ever gets, so on an iPhone with Safari's bar on screen the
     sheet's last row — usually 保存, sometimes 取消 — sits behind the toolbar and
     no amount of scrolling reaches it. --vvh is the honest number (viewportKit,
     ui.js); with the keyboard away it equals the visible height, with the keypad
     up it shrinks the sheet to the band above it, which is what --kb then lifts.
     The 940px / 560px caps are kept so nothing grows on a tall narrow window. */
  .anSheet{ height:min(94dvh,940px,var(--vvh)); max-height:min(94dvh,940px,var(--vvh)) }
  .popCard{ max-height:min(78dvh,560px,var(--vvh)) }
}
/* Quick cash only below 760px, because from 760px up it is a centred dialog with
   its own max-height:min(88vh,780px) — overriding that here would make it taller,
   not better, on an iPad held upright. */
@media (max-width:759px){
  .cashCard{ max-height:min(92dvh,var(--vvh)) }
}
@media (max-width:640px){
  /* The full-bleed version keeps its full-bleed look, bounded by the same truth:
     a sheet taller than the screen hides its own footer behind the browser's
     toolbar, and that footer is the close button. */
  .anSheet{ height:min(100dvh,var(--vvh)); max-height:min(100dvh,var(--vvh)) }
}


