  /* Guard: the browser's default [hidden]{display:none} loses to any later
     class rule of equal specificity that sets its own `display` (e.g.
     `.import-dup-confirm{display:flex}`), so an element can end up stuck
     visible even after JS sets el.hidden = true. Force hidden to win. */
  [hidden]{display:none !important;}

  :root{
    --ink:#1B2A3D;
    --ink-soft:#57687C;
    --ink-faint:#8A97A6;
    --paper:#EEF1EA;
    --surface:#FFFFFF;
    --surface-2:#F6F7F1;
    --line:#DBDECF;
    --line-strong:#C4C9B7;
    --accent:#1F6F5C;
    --accent-strong:#164F41;
    --accent-tint:#E1EEE8;
    --brass:#B98A3D;
    --brass-text:#8C6526;
    --brass-tint:#F2E7D0;
    --danger:#B4483A;
    --danger-tint:#F5E1DD;
    --tag-husband:#2E5E86;
    --tag-wife:#A14E70;
    --tag-travel:#A2650E;
    --tag-insurance:#5C4B87;
    --tag-on-fill:#FFFFFF;
    --on-accent:#FFFFFF;
    --hero-bg:#164F41;
    --hero-fg:#FFFFFF;
    --hero-fg-dim:rgba(255,255,255,.72);
    --shadow:rgba(27,42,61,0.10);
    --font-display:'Noto Serif TC','Songti TC','PMingLiU',serif;
    --font-body:'Noto Sans TC','PingFang TC','Microsoft JhengHei',sans-serif;
    --font-mono:'IBM Plex Mono','SFMono-Regular',Consolas,monospace;
  }
  @media (prefers-color-scheme: dark){
    :root:not([data-theme="light"]){
      --ink:#ECEFE9;
      --ink-soft:#AEB8AC;
      --ink-faint:#7C877C;
      --paper:#12181A;
      --surface:#1A2224;
      --surface-2:#20292B;
      --line:#33403F;
      --line-strong:#455251;
      --accent:#57C7A6;
      --accent-strong:#8DDCC4;
      --accent-tint:#1D3A32;
      --brass:#D9AC5F;
      --brass-text:#E6C583;
      --brass-tint:#3A2E18;
      --danger:#E28C7B;
      --danger-tint:#3B2521;
      --tag-husband:#82B6E2;
      --tag-wife:#E39BB4;
      --tag-travel:#E8AE5C;
      --tag-insurance:#B39EDD;
      --tag-on-fill:#12181A;
      --on-accent:#0F1A17;
      --hero-bg:#1D3A32;
      --hero-fg:#EAF7F1;
      --hero-fg-dim:rgba(234,247,241,.7);
      --shadow:rgba(0,0,0,0.45);
    }
  }
  :root[data-theme="dark"]{
    --ink:#ECEFE9;
    --ink-soft:#AEB8AC;
    --ink-faint:#7C877C;
    --paper:#12181A;
    --surface:#1A2224;
    --surface-2:#20292B;
    --line:#33403F;
    --line-strong:#455251;
    --accent:#57C7A6;
    --accent-strong:#8DDCC4;
    --accent-tint:#1D3A32;
    --brass:#D9AC5F;
    --brass-text:#E6C583;
    --brass-tint:#3A2E18;
    --danger:#E28C7B;
    --danger-tint:#3B2521;
    --tag-husband:#82B6E2;
    --tag-wife:#E39BB4;
    --tag-travel:#E8AE5C;
    --tag-insurance:#B39EDD;
    --tag-on-fill:#12181A;
    --on-accent:#0F1A17;
    --hero-bg:#1D3A32;
    --hero-fg:#EAF7F1;
    --hero-fg-dim:rgba(234,247,241,.7);
    --shadow:rgba(0,0,0,0.45);
  }

  *{box-sizing:border-box;}
  @media (prefers-reduced-motion: reduce){
    *{animation:none !important; transition:none !important;}
  }
  html,body{margin:0;padding:0;}
  body{
    background:var(--paper);
    color:var(--ink);
    font-family:var(--font-body);
    font-size:15px;
    line-height:1.55;
    -webkit-font-smoothing:antialiased;
  }
  a{color:var(--accent-strong);}
  /* Longhand (not shorthand) so each side can add the notch/home-indicator
     safe-area inset on top of its normal spacing — a bare shorthand value
     would leave content sitting flush against, or under, a notch/rounded
     corner on iPhones in landscape, or right above the home-indicator bar. */
  .wrap{
    max-width:1180px;margin:0 auto;
    padding-top:28px;
    padding-right:max(20px, env(safe-area-inset-right));
    padding-bottom:calc(80px + env(safe-area-inset-bottom));
    padding-left:max(20px, env(safe-area-inset-left));
  }
  @media (max-width:600px){
    .wrap{
      padding-top:18px;
      padding-right:max(12px, env(safe-area-inset-right));
      padding-bottom:calc(60px + env(safe-area-inset-bottom));
      padding-left:max(12px, env(safe-area-inset-left));
    }
  }

  /* ---------- Header ---------- */
  .masthead{
    display:flex;
    align-items:flex-start;
    justify-content:space-between;
    gap:24px;
    flex-wrap:wrap;
    padding-bottom:20px;
    border-bottom:1px solid var(--line-strong);
    margin-bottom:24px;
  }
  .masthead-title{display:flex;align-items:center;gap:16px;}
  .card-mark{
    width:auto;height:38px;flex:none;display:block;
  }
  h1{
    font-family:var(--font-display);
    font-weight:700;
    font-size:clamp(24px,3.4vw,32px);
    margin:0;
    letter-spacing:.01em;
    text-wrap:balance;
  }
  .tagline{margin:4px 0 0;color:var(--ink-soft);font-size:13.5px;}
  .sync-pill{
    display:inline-flex;align-items:center;gap:7px;
    padding:6px 12px;border-radius:999px;
    background:var(--surface);border:1px solid var(--line);
    font-size:12.5px;color:var(--ink-soft);
    white-space:nowrap;
  }
  .sync-dot{width:7px;height:7px;border-radius:50%;background:var(--ink-faint);flex:none;}
  .sync-pill.is-live .sync-dot{background:var(--accent);box-shadow:0 0 0 3px var(--accent-tint);}
  .sync-pill.is-local .sync-dot{background:var(--brass);box-shadow:0 0 0 3px var(--brass-tint);}
  .masthead-right{display:flex;align-items:center;gap:14px;flex-wrap:wrap;}

  /* ---------- Stat row ---------- */
  .stats-period{
    display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap;
    font-size:12.5px;color:var(--ink-soft);
  }
  .stats-period label{font-weight:600;}
  #period-select{
    font:inherit;font-family:var(--font-body);font-size:13px;color:var(--ink);
    background:var(--surface);border:1px solid var(--line-strong);border-radius:999px;
    padding:6px 30px 6px 14px;cursor:pointer;
  }
  @media (max-width:480px){
    #period-select{flex:1 1 auto;min-width:0;}
  }
  .stats{
    display:grid;
    grid-template-columns:1.4fr repeat(5,1fr);
    gap:12px;
    margin-bottom:28px;
  }
  .stat{
    background:var(--surface);
    border:1px solid var(--line);
    border-radius:12px;
    padding:14px 16px;
    display:flex;
    flex-direction:column;
    gap:6px;
    min-width:0;
  }
  .stat-lead{background:var(--hero-bg);border-color:var(--hero-bg);color:var(--hero-fg);}
  .stat-lead .stat-label{color:var(--hero-fg-dim);}
  .stat-lead .stat-value{color:var(--hero-fg);}
  .stat-label{
    font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;
    color:var(--ink-faint);display:flex;align-items:center;gap:6px;
  }
  .stat-dot{width:8px;height:8px;border-radius:50%;flex:none;}
  .stat-value{
    font-family:var(--font-mono);font-weight:600;
    font-size:20px;font-variant-numeric:tabular-nums;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }
  .stat-lead .stat-value{font-size:24px;}
  .stat-sub{font-size:12px;color:var(--ink-faint);}
  .stat-lead .stat-sub{color:var(--hero-fg-dim);}
  .stat-fx-list{
    font-family:var(--font-mono);font-size:11px;color:var(--ink-faint);
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }
  .stat-fx.is-toggleable{cursor:pointer;user-select:none;}
  .stat-fx.is-toggleable:hover{border-color:var(--accent);}
  .stat-fx-mode{
    font-family:var(--font-body);font-size:10.5px;font-weight:700;letter-spacing:0;
    color:var(--accent-strong);background:var(--accent-tint);
    padding:1px 6px;border-radius:999px;margin-left:4px;text-transform:none;
  }

  /* ---------- Main layout ---------- */
  @media (max-width:860px){
    .stats{grid-template-columns:1fr 1fr;}
    .stat-lead{grid-column:1 / -1;}
  }
  @media (max-width:460px){
    /* Narrow phones: two columns starts truncating stat values and the
       外幣消費 mode badge, so stack every stat tile full-width instead.
       Raised from 420 to 460 — the largest current iPhones (Pro Max/Plus
       models) report a 430px CSS width, which was still inside the old
       420px cutoff's 2-column zone and could clip a 7-digit NT$ total. */
    .stats{grid-template-columns:1fr;gap:8px;}
    .stat{padding:12px 14px;}
  }

  /* ---------- Entry slip (form) ---------- */
  .slip{
    background:var(--surface);
    border:1px solid var(--line);
    border-radius:14px;
    padding:20px 22px 18px;
    box-shadow:0 2px 10px var(--shadow);
    margin-bottom:24px;
  }
  .slip.is-collapsed{padding:0;box-shadow:0 1px 5px var(--shadow);}
  .slip-compact{
    display:flex;align-items:center;gap:10px;width:100%;
    background:none;border:none;border-radius:14px;
    padding:14px 20px;cursor:pointer;
    font:inherit;font-family:var(--font-body);font-size:14.5px;font-weight:700;color:var(--ink-soft);
    text-align:left;
  }
  .slip-compact:hover{background:var(--surface-2);color:var(--accent-strong);}
  .slip-compact-icon{
    width:28px;height:28px;border-radius:50%;flex:none;
    background:var(--accent-tint);color:var(--accent-strong);
    display:flex;align-items:center;justify-content:center;font-size:17px;font-weight:700;
  }
  .slip-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;}
  .slip-head #slip-collapse-btn,.slip-head #rcpt-slip-collapse-btn{flex:none;white-space:nowrap;margin-top:2px;}
  .slip h2{
    font-family:var(--font-display);
    font-size:17px;margin:0 0 2px;font-weight:700;
  }
  .slip-hint{font-size:12.5px;color:var(--ink-faint);margin:0 0 16px;}
  .slip-fields{
    display:grid;
    grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
    gap:14px 16px;
    margin-bottom:14px;
  }
  .slip-fields .field{margin-bottom:0;}
  .slip-fields .field.field-billing{min-width:150px;}
  .field{margin-bottom:14px;}
  .field label{
    display:block;font-size:12.5px;font-weight:600;color:var(--ink-soft);
    margin-bottom:6px;letter-spacing:.01em;
  }
  .field input[type="text"],
  .field input[type="date"],
  .field input[type="month"],
  .field input[type="number"],
  .field input[type="search"],
  .field select{
    width:100%;
    font:inherit;
    font-family:var(--font-body);
    color:var(--ink);
    background:var(--surface-2);
    border:1px solid var(--line);
    border-radius:8px;
    padding:9px 11px;
  }
  .field input[type="number"]{font-family:var(--font-mono);}
  .field select{cursor:pointer;}
  /* Pairs a currency picker with an amount field on one row (收據明細's
     幣別 + 總金額) — the currency select stays a fixed, compact width while
     the amount field takes the rest, wrapping to its own line only when
     the row is too narrow for both. */
  .field-row-currency{display:flex;gap:14px;flex-wrap:wrap;}
  .field-row-currency .field-currency{flex:0 0 120px;}
  .field-row-currency .field-amount{flex:1 1 200px;}
  .field textarea{
    width:100%;
    font:inherit;
    font-family:var(--font-body);
    color:var(--ink);
    background:var(--surface-2);
    border:1px solid var(--line);
    border-radius:8px;
    padding:9px 11px;
    resize:vertical;
    min-height:52px;
  }
  .field input:focus-visible,
  button:focus-visible,
  .tag-toggle input:focus-visible + span{
    outline:2px solid var(--accent);outline-offset:2px;
  }
  .field-hint{font-size:11px;color:var(--ink-faint);margin:6px 0 0;}
  .amount-wrap{position:relative;}
  .amount-wrap::before{
    content:"NT$";position:absolute;left:11px;top:50%;transform:translateY(-50%);
    font-family:var(--font-mono);font-size:13px;color:var(--ink-faint);pointer-events:none;
  }
  .amount-wrap input{padding-left:42px !important;}

  .foreign-toggle{
    display:inline-flex;align-items:center;gap:8px;cursor:pointer;user-select:none;
    font-size:13px;font-weight:600;color:var(--ink-soft);
  }
  .foreign-toggle input{width:16px;height:16px;accent-color:var(--accent);cursor:pointer;margin:0;}
  .foreign-amount-field{margin:10px 0 0;max-width:280px;}
  .foreign-amount-field input{font-family:var(--font-mono);}

  .tag-toggles{display:flex;flex-wrap:wrap;gap:8px;}
  .tag-toggle{position:relative;cursor:pointer;}
  .tag-toggle input{position:absolute;opacity:0;width:0;height:0;}
  .tag-toggle span{
    display:inline-flex;align-items:center;gap:6px;
    padding:7px 13px;border-radius:999px;
    border:1.5px solid var(--tc);
    color:var(--tc);
    font-size:13px;font-weight:600;
    background:transparent;
    transition:background .12s ease,color .12s ease;
  }
  .tag-toggle span::before{
    content:"";width:7px;height:7px;border-radius:50%;background:var(--tc);flex:none;
  }
  .tag-toggle input:checked + span{background:var(--tc);color:var(--tag-on-fill);}
  .tag-toggle input:checked + span::before{background:var(--tag-on-fill);}
  .tag-husband{--tc:var(--tag-husband);}
  .tag-wife{--tc:var(--tag-wife);}
  .tag-travel{--tc:var(--tag-travel);}
  .tag-insurance{--tc:var(--tag-insurance);}
  .tag-toggle.compact span{padding:3px 9px;font-size:11.5px;gap:4px;}
  .tag-toggle.compact span::before{width:6px;height:6px;}

  /* ---------- Sub-items ---------- */
  .subitems-field .subitems-head{
    display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px 14px;margin-bottom:6px;
  }
  .subitems-field label{margin-bottom:0;}
  .subitems-head-actions{display:flex;align-items:center;gap:14px;}
  .link-btn{
    background:none;border:none;padding:0;color:var(--accent-strong);
    font-size:12.5px;font-weight:700;cursor:pointer;
  }
  .link-btn:hover{text-decoration:underline;}
  .link-btn:disabled{color:var(--ink-faint);cursor:default;text-decoration:none;}
  .receipt-scan-status{font-size:11.5px;color:var(--brass-text);margin:0 0 6px;}
  .receipt-scan-status.is-error{color:var(--danger);}
  .receipt-text-panel{margin:0 0 8px;padding:10px;border:1px solid var(--line);border-radius:8px;background:var(--surface-2);}
  .receipt-text-panel textarea{
    width:100%;font:inherit;font-family:var(--font-body);font-size:12.5px;color:var(--ink);
    background:var(--surface);border:1px solid var(--line);border-radius:8px;padding:8px 10px;
    resize:vertical;min-height:70px;
  }
  .receipt-text-actions{display:flex;justify-content:flex-end;align-items:center;gap:12px;margin-top:8px;}
  #receipt-text-submit{
    background:var(--accent);color:var(--on-accent);border:none;border-radius:7px;
    padding:6px 14px;font:inherit;font-family:var(--font-body);font-size:12.5px;font-weight:700;cursor:pointer;
  }
  #receipt-text-submit:hover{background:var(--accent-strong);}
  #receipt-text-submit:disabled{opacity:.5;cursor:not-allowed;}
  #subitem-rows{display:flex;flex-direction:column;gap:6px;}
  #subitem-rows:not(:empty){margin-bottom:6px;}
  .subitem-row{display:grid;grid-template-columns:1fr 52px 84px 22px;gap:6px;align-items:start;}
  .subitem-row input{
    font:inherit;font-family:var(--font-body);font-size:12.5px;color:var(--ink);
    background:var(--surface-2);border:1px solid var(--line);border-radius:6px;padding:6px 8px;width:100%;
  }
  .subitem-row .si-amount{font-family:var(--font-mono);text-align:right;margin-top:0;}
  .subitem-row .si-qty{font-family:var(--font-mono);text-align:center;margin-top:0;}
  .si-name-wrap{display:flex;flex-direction:column;gap:4px;min-width:0;}
  .subitem-row .si-zh{color:var(--ink-soft);font-size:11.5px;}
  .subitem-row .si-pid{color:var(--ink-faint);font-size:11px;font-family:var(--font-mono);}
  .si-remove{
    width:22px;height:22px;border:none;background:transparent;color:var(--ink-faint);
    border-radius:6px;cursor:pointer;font-size:12px;padding:0;
  }
  .si-remove:hover{background:var(--danger-tint);color:var(--danger);}
  .subitem-hint{font-size:11.5px;color:var(--brass-text);margin:2px 0 0;}
  .si-zh-label{font-weight:400;color:var(--ink-faint);}
  .si-qty-label{font-weight:400;color:var(--ink-faint);font-family:var(--font-mono);margin:0 2px;}
  /* Search-hit highlight: a plain gray box around the matched characters,
     not a colored highlighter — keeps it readable inside item names, tags
     or the mono-space card/date text without fighting the page's palette. */
  mark.search-hit{
    background:var(--line-strong);color:inherit;font:inherit;
    border-radius:3px;padding:0 2px;margin:0 -2px;
  }

  .tear{
    height:1px;margin:18px 0 16px;
    background-image:linear-gradient(to right,var(--line) 60%,transparent 0%);
    background-size:9px 1px;background-repeat:repeat-x;
  }
  .form-actions{display:flex;gap:10px;justify-content:flex-end;}
  button{
    font:inherit;font-family:var(--font-body);cursor:pointer;
    border-radius:8px;border:1px solid transparent;padding:10px 16px;
    font-weight:600;font-size:14px;
  }
  #submit-btn{background:var(--accent);color:var(--on-accent);min-width:170px;box-shadow:0 2px 6px var(--shadow);}
  #submit-btn:hover{background:var(--accent-strong);}
  #cancel-edit-btn{background:transparent;border-color:var(--line-strong);color:var(--ink-soft);}
  .form-msg{margin-top:10px;font-size:12.5px;color:var(--danger);min-height:1em;}

  /* ---------- Toolbar ---------- */
  .toolbar{
    display:flex;align-items:center;gap:10px;flex-wrap:wrap;
    margin-bottom:14px;
  }
  .chip-filters{display:flex;gap:8px;flex-wrap:wrap;flex:1;min-width:0;}
  .chip{
    padding:6px 13px;border-radius:999px;border:1.5px solid var(--line-strong);
    background:var(--surface);color:var(--ink-soft);font-size:13px;font-weight:600;
    cursor:pointer;display:inline-flex;align-items:center;gap:6px;
  }
  .chip::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--tc,var(--ink-faint));}
  .chip.is-active{background:var(--tc,var(--ink));border-color:var(--tc,var(--ink));color:var(--tag-on-fill,#fff);}
  .chip.chip-all.is-active{background:var(--ink);border-color:var(--ink);color:var(--paper);}
  .chip[data-tag="老公"]{--tc:var(--tag-husband);}
  .chip[data-tag="老婆"]{--tc:var(--tag-wife);}
  .chip[data-tag="旅遊"]{--tc:var(--tag-travel);}
  .chip[data-tag="保險"]{--tc:var(--tag-insurance);}
  #search-input{
    width:190px;max-width:100%;
    background:var(--surface);border:1px solid var(--line-strong);
    border-radius:999px;padding:7px 14px;font-size:13px;color:var(--ink);
  }
  /* 依日期查詢: an exact-date filter alongside the free-text search box —
     kept as its own control (rather than folded into the text search)
     because a native date picker is the easier way to pick "one specific
     day" than typing a YYYY-MM-DD string by hand. */
  .date-filter-wrap{display:inline-flex;align-items:center;gap:4px;}
  #date-filter{
    background:var(--surface);border:1px solid var(--line-strong);
    border-radius:999px;padding:6px 12px;font:inherit;font-size:13px;color:var(--ink);
  }
  #date-filter-clear{
    background:none;border:none;color:var(--ink-faint);
    font:inherit;font-size:14px;font-weight:700;cursor:pointer;
    width:26px;height:26px;border-radius:6px;padding:0;
    display:flex;align-items:center;justify-content:center;flex:none;
  }
  #date-filter-clear:hover{color:var(--danger);background:var(--surface-2);}
  #batch-btn,#import-btn,#export-btn{background:var(--surface);border:1px solid var(--line-strong);color:var(--ink-soft);}
  #batch-btn:hover,#import-btn:hover,#export-btn:hover{border-color:var(--accent);color:var(--accent-strong);}
  @media (max-width:640px){
    /* Chips and search take a full row each so they're not squeezed; the
       three action buttons then wrap onto their own row and share it evenly
       instead of trailing off one at a time. */
    .chip-filters{flex:1 1 100%;}
    #search-input{flex:1 1 100%;width:100%;}
    .date-filter-wrap{flex:1 1 100%;}
    #date-filter{flex:1 1 auto;width:100%;}
    #batch-btn,#import-btn,#export-btn{flex:1 1 auto;white-space:nowrap;}
  }

  /* ---------- Ledger list ---------- */
  .ledger{
    background:var(--surface);
    border:1px solid var(--line);
    border-radius:14px;
    overflow:hidden;
    box-shadow:0 2px 10px var(--shadow);
  }
  .ledger-head{
    display:grid;
    grid-template-columns:28px 100px 1fr 150px 190px 120px 40px;
    gap:10px;
    padding:11px 18px;
    font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;
    color:var(--ink-faint);
    background:var(--surface-2);
    border-bottom:1px solid var(--line);
  }
  .ledger-head .num{text-align:right;}
  .cell-check{display:flex;align-items:center;}
  .cell-check input[type="checkbox"]{
    width:16px;height:16px;accent-color:var(--accent);cursor:pointer;margin:0;
  }
  .sort-toggle{
    display:inline-flex;align-items:center;gap:4px;
    background:none;border:none;padding:0;margin:0;cursor:pointer;
    font:inherit;font-family:inherit;font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;
    color:var(--ink-faint);
  }
  .sort-toggle:hover{color:var(--accent-strong);}
  .sort-toggle.is-active{color:var(--ink-soft);}
  .sort-toggle.num{width:100%;justify-content:flex-end;}
  .sort-arrow{font-size:9px;}
  /* Compact sort control shown only under the 900px breakpoint, where
     .ledger-head (and its five column sort-toggle buttons) is hidden for
     space — without this, phones/iPad-portrait would lose sorting
     entirely rather than just losing the column-header layout. */
  .ledger-sort-select{
    display:none;
    width:100%;
    margin:0;
    padding:10px 14px;
    font:inherit;font-size:13.5px;color:var(--ink);
    background:var(--surface-2);
    border:none;border-bottom:1px solid var(--line);
    border-radius:0;
  }
  /* Stands in for .ledger-head's "全選" checkbox under the 900px
     breakpoint, where that whole header row (and the checkbox it lives
     in) is hidden — without this, phone/iPad-portrait users have no way
     to select every row at once and can only bulk-tag/bulk-delete by
     tapping each row's own checkbox one at a time. */
  .ledger-mobile-bar{
    display:none;
    align-items:center;
    padding:9px 14px;
    background:var(--surface-2);
    border-bottom:1px solid var(--line);
  }
  .mobile-select-all{
    display:inline-flex;align-items:center;gap:7px;
    font-size:13px;font-weight:600;color:var(--ink-soft);cursor:pointer;
  }
  .mobile-select-all input[type="checkbox"]{
    width:16px;height:16px;accent-color:var(--accent);cursor:pointer;margin:0;
  }
  .month-group-head{
    display:flex;align-items:baseline;justify-content:space-between;
    padding:10px 18px;
    background:var(--brass-tint);
    color:var(--brass-text);
    font-family:var(--font-display);font-weight:700;font-size:14px;
    border-bottom:1px solid var(--line);
  }
  .month-group-head .msub{font-family:var(--font-mono);font-weight:600;font-size:13px;}
  .month-group-head.is-toggleable{cursor:pointer;user-select:none;}
  .month-group-head.is-toggleable:hover{background:var(--brass);color:#fff;}
  .month-group-head.is-toggleable:hover .msub,
  .month-group-head.is-toggleable:hover .group-count{color:#fff;}
  .month-group-head .group-arrow{display:inline-block;width:12px;font-size:11px;}
  .month-group-head .group-count{font-family:var(--font-mono);font-weight:400;font-size:12px;color:var(--brass-text);opacity:.8;}
  .month-group-head.is-collapsed{opacity:.85;}
  .row{
    display:grid;
    grid-template-columns:28px 100px 1fr 150px 190px 120px 40px;
    gap:10px;
    padding:12px 18px;
    align-items:center;
    border-bottom:1px solid var(--line);
    position:relative;
  }
  .row:last-child{border-bottom:none;}
  .row:hover{background:var(--surface-2);}
  .row.is-example{opacity:.55;}
  .cell-date{font-family:var(--font-mono);font-size:13px;color:var(--ink-soft);}
  .cell-item{font-weight:600;min-width:0;}
  .cell-item .item-name{
    display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  }
  .cell-item .item-note{
    display:block;font-weight:400;font-size:11.5px;color:var(--ink-faint);
    white-space:normal;word-break:break-word;margin-top:2px;
  }
  .cell-item .item-note.is-editable{cursor:pointer;border-radius:6px;padding:1px 5px;margin:2px -5px -1px;}
  .cell-item .item-note.is-editable:hover{background:var(--surface-2);color:var(--ink-soft);}
  .note-empty{color:var(--ink-faint);font-style:italic;}
  .expand-btn{
    background:none;border:none;padding:0;margin-right:5px;color:var(--ink-faint);
    font-size:10px;cursor:pointer;vertical-align:middle;width:14px;display:inline-block;
  }
  .expand-btn:hover{color:var(--accent-strong);}
  .row-detail{
    padding:8px 18px 14px 46px;background:var(--surface-2);border-bottom:1px solid var(--line);
    display:flex;flex-wrap:wrap;gap:6px 18px;
  }
  .row-detail .si{font-size:12.5px;color:var(--ink-soft);font-family:var(--font-mono);white-space:nowrap;}
  .row-detail .si b{font-family:var(--font-body);font-weight:600;color:var(--ink);margin-right:5px;}
  .cell-card{
    display:inline-flex;align-items:center;gap:7px;font-size:12.5px;color:var(--ink-soft);
    min-width:0;
  }
  .card-swatch{width:8px;height:8px;border-radius:2px;flex:none;}
  .cell-tags{display:flex;gap:5px;flex-wrap:wrap;}
  .cell-tags.is-editable{cursor:pointer;border-radius:8px;padding:3px;margin:-3px;}
  .cell-tags.is-editable:hover{background:var(--surface-2);}
  .tag-chip{
    font-size:11px;font-weight:700;padding:3px 8px;border-radius:999px;
    background:var(--tc);color:var(--tag-on-fill);white-space:nowrap;
  }
  .tag-chip.husband{--tc:var(--tag-husband);}
  .tag-chip.wife{--tc:var(--tag-wife);}
  .tag-chip.travel{--tc:var(--tag-travel);}
  .tag-chip.insurance{--tc:var(--tag-insurance);}
  .tag-empty{
    font-size:11px;color:var(--ink-faint);border:1px dashed var(--line-strong);
    border-radius:999px;padding:3px 9px;white-space:nowrap;
  }

  /* ---------- Quick tag popover (click a row's tags to reclassify) ---------- */
  .tag-popover{
    position:fixed;z-index:60;min-width:150px;
    background:var(--surface);border:1px solid var(--line-strong);border-radius:10px;
    box-shadow:0 10px 28px var(--shadow);padding:6px;
  }
  .tp-opt{
    display:flex;align-items:center;gap:8px;width:100%;text-align:left;
    background:none;border:none;border-radius:6px;padding:7px 9px;
    font:inherit;font-family:var(--font-body);font-size:13px;font-weight:600;color:var(--ink);
    cursor:pointer;
  }
  .tp-opt:hover{background:var(--surface-2);}
  .tp-opt .tp-dot{width:8px;height:8px;border-radius:50%;background:var(--tc);flex:none;}
  .tp-opt.is-current{background:var(--surface-2);}
  .tp-opt.tp-clear{color:var(--ink-faint);font-weight:600;border-top:1px solid var(--line);margin-top:4px;padding-top:8px;border-radius:0 0 6px 6px;}

  /* ---------- Quick note popover (click a row's note to edit) ---------- */
  .note-popover{
    position:fixed;z-index:60;width:240px;
    background:var(--surface);border:1px solid var(--line-strong);border-radius:10px;
    box-shadow:0 10px 28px var(--shadow);padding:10px;
  }
  .note-popover textarea{
    width:100%;font:inherit;font-family:var(--font-body);font-size:13px;color:var(--ink);
    background:var(--surface-2);border:1px solid var(--line);border-radius:8px;
    padding:8px 10px;resize:vertical;min-height:60px;
  }
  .note-popover-actions{display:flex;align-items:center;justify-content:flex-end;gap:12px;margin-top:8px;}
  #note-popover-save{
    background:var(--accent);color:var(--on-accent);border:none;border-radius:7px;
    padding:6px 14px;font:inherit;font-family:var(--font-body);font-size:12.5px;font-weight:700;cursor:pointer;
  }
  #note-popover-save:hover{background:var(--accent-strong);}

  /* ---------- Quick 外幣 popover (click a row's 外幣 line to set/edit) ---------- */
  .fx-popover{
    position:fixed;z-index:60;width:240px;
    background:var(--surface);border:1px solid var(--line-strong);border-radius:10px;
    box-shadow:0 10px 28px var(--shadow);padding:10px;
  }
  .fx-popover-label{
    display:block;margin-bottom:6px;
    font-size:12.5px;font-weight:600;color:var(--ink-soft);
  }
  .fx-popover input[type="text"]{
    width:100%;font:inherit;font-family:var(--font-mono);font-size:13px;color:var(--ink);
    background:var(--surface-2);border:1px solid var(--line);border-radius:8px;
    padding:8px 10px;
  }
  .fx-popover-actions{display:flex;align-items:center;justify-content:flex-end;gap:12px;margin-top:8px;}
  #fx-popover-save{
    background:var(--accent);color:var(--on-accent);border:none;border-radius:7px;
    padding:6px 14px;font:inherit;font-family:var(--font-body);font-size:12.5px;font-weight:700;cursor:pointer;
  }
  #fx-popover-save:hover{background:var(--accent-strong);}
  .cell-amount{
    font-family:var(--font-mono);font-weight:600;text-align:right;
    font-variant-numeric:tabular-nums;
  }
  .neg-amount{color:var(--danger);}
  .stat-lead .neg-amount{color:#FFB4A8;}
  .cell-actions{display:flex;gap:4px;justify-content:flex-end;}
  .icon-btn{
    width:26px;height:26px;border-radius:6px;border:1px solid transparent;
    background:transparent;color:var(--ink-faint);display:flex;align-items:center;justify-content:center;
    padding:0;font-size:14px;line-height:1;
  }
  .icon-btn:hover{background:var(--surface-2);color:var(--ink);}
  .icon-btn.danger-armed{background:var(--danger-tint);color:var(--danger);border-color:var(--danger);}
  .example-badge{
    font-size:10px;font-weight:700;color:var(--brass-text);background:var(--brass-tint);
    padding:2px 7px;border-radius:999px;margin-left:8px;letter-spacing:.03em;
  }
  /* Rows that share date + card + amount + item with another record —
     likely an accidental double entry (e.g. imported twice). Flagged with
     a caution color rather than the danger red used for errors/deletes. */
  .row.is-duplicate{box-shadow:inset 3px 0 0 var(--brass);}
  .dup-badge{
    font-size:10px;font-weight:700;color:var(--brass-text);background:var(--brass-tint);
    padding:2px 7px;border-radius:999px;margin-left:8px;letter-spacing:.02em;white-space:nowrap;
  }
  .item-fx{
    display:block;font-weight:400;font-size:11.5px;color:var(--accent-strong);
    font-family:var(--font-mono);white-space:normal;word-break:break-word;margin-top:2px;
  }
  .item-fx .note-empty{font-family:var(--font-body);}
  .item-fx.is-editable{cursor:pointer;border-radius:6px;padding:1px 5px;margin:2px -5px -1px;}
  .item-fx.is-editable:hover{background:var(--surface-2);color:var(--accent-strong);}
  .example-note{
    padding:10px 18px;font-size:12.5px;color:var(--ink-faint);
    background:var(--surface-2);border-bottom:1px solid var(--line);
  }
  .empty-state{padding:48px 20px;text-align:center;color:var(--ink-faint);font-size:13.5px;}
  .ledger-foot{
    display:grid;grid-template-columns:28px 100px 1fr 150px 190px 120px 40px;gap:10px;
    padding:13px 18px;background:var(--surface-2);border-top:1px solid var(--line-strong);
    font-weight:700;
  }
  .ledger-foot .cell-amount{font-size:15px;color:var(--accent-strong);}

  /* ---------- Bulk selection bar ---------- */
  .bulk-bar{
    display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
    padding:10px 16px;margin-bottom:10px;border-radius:10px;
    background:var(--surface-2);border:1px solid var(--line-strong);color:var(--ink);font-size:13px;font-weight:600;
  }
  .bulk-actions{display:flex;align-items:center;gap:10px;}
  .bulk-clear-btn{background:none;border:none;padding:0;color:var(--ink-soft);font-size:12.5px;font-weight:700;cursor:pointer;text-decoration:underline;}
  .bulk-tag-btn{
    border:1px solid var(--line-strong);border-radius:8px;padding:7px 14px;
    font:inherit;font-family:var(--font-body);font-size:12.5px;font-weight:700;
    background:var(--surface);color:var(--ink-soft);cursor:pointer;white-space:nowrap;
  }
  .bulk-tag-btn:hover{border-color:var(--accent);color:var(--accent-strong);}
  .bulk-delete-btn{
    border:none;border-radius:8px;padding:7px 14px;
    font:inherit;font-family:var(--font-body);font-size:12.5px;font-weight:700;
    background:var(--danger);color:#fff;cursor:pointer;white-space:nowrap;
  }
  .bulk-delete-btn:hover{opacity:.88;}
  .bulk-delete-btn.is-armed{box-shadow:0 0 0 3px rgba(180,72,58,.3);}

  @media (max-width:900px){
    /* The 7-column ledger grid needs ~900px to fit without crushing the
       item-name column to nothing — that range (roughly 721-900px) is
       exactly where an iPad in portrait sits (768-834px), not just phones,
       so this now covers tablets too, not only phones under 720px. */
    .ledger-head{display:none;}
    .ledger-mobile-bar{display:flex;}
    .ledger-sort-select{display:block;}
    .row,.ledger-foot{
      display:flex;flex-wrap:wrap;align-items:center;
      row-gap:6px;column-gap:10px;padding:13px 16px;
    }
    .row .cell-check{order:0;flex:0 0 auto;}
    .row .cell-item{order:1;flex:1 1 55%;white-space:normal;}
    .row .cell-amount{order:2;flex:0 0 auto;margin-left:auto;}
    .row .cell-date{order:3;flex:0 0 auto;font-size:12px;}
    .row .cell-card{order:4;flex:0 0 auto;font-size:12px;}
    .row .cell-date::after{content:" · ";color:var(--ink-faint);}
    .row .cell-tags{order:5;flex:1 1 100%;}
    .row .cell-actions{order:6;flex:0 0 auto;margin-left:auto;}
    .ledger-foot .cell-item{flex:1 1 auto;}
    .ledger-foot .cell-amount{margin-left:auto;}
    .ledger-foot .cell-date,.ledger-foot .cell-card,.ledger-foot .cell-tags,.ledger-foot .cell-actions,.ledger-foot .cell-check{display:none;}
    /* Was 2px — the edit/delete icon buttons in each row sat close enough
       together that a finger reaching for one on a phone risked landing on
       the other (arming a delete by mistake), so give them real breathing
       room now that the row wraps onto multiple lines anyway. */
    .cell-actions{gap:8px;}
  }

  @media (pointer:coarse){
    /* Bigger tap targets for any touch device — a finger is far less
       precise than a mouse cursor, and this matters just as much on a
       landscape iPad (plenty of screen width, still a touchscreen) as on
       a phone, so it's keyed on touch capability rather than viewport
       width the way the layout reflow above is. */
    .icon-btn{width:34px;height:34px;font-size:15px;}
    .expand-btn{width:22px;font-size:13px;}
    .cell-check input[type="checkbox"],.mobile-select-all input[type="checkbox"],.rcpt-card-check input{width:20px;height:20px;}
    .chip{padding:8px 15px;}
    .tag-toggle span{padding:9px 15px;}
    .si-remove{width:30px;height:30px;font-size:13px;}
    .tp-opt{padding:10px 11px;}
    #date-filter-clear{width:34px;height:34px;font-size:16px;}
    #period-select{padding:10px 32px 10px 16px;}
    /* These keep padding:0 (or none) on pointer devices because they sit
       inline in a tight row where real padding would shift the layout —
       grow the tap target with padding offset by an equal negative margin
       instead, so the visible size and surrounding gaps are unchanged but
       the hit area is bigger. Covers the row-level note/FX quick-edit
       labels and the multi-select checkbox too — previously only the
       action icons/toggles got this treatment, leaving these (the only
       way to add a note/FX line or multi-select a row on touch) as
       small text or a bare checkbox. */
    .link-btn{padding:9px 6px;margin:-9px -6px;}
    .sort-toggle{padding:9px 6px;margin:-9px -6px;}
    .bulk-tag-btn,.bulk-delete-btn{padding:11px 16px;}
    .cell-item .item-note.is-editable,.item-fx.is-editable{padding:7px 5px;margin:-4px -5px -7px;}
    .mobile-select-all{padding:8px 4px;margin:-8px -4px;}
    /* iOS Safari zooms the whole page in when a focused text input's
       computed font-size is under 16px — every one of this app's text
       entry fields (amount, item, card, note, batch rows, Costco entry
       rows…) was smaller than that, so every tap into a field on an
       iPhone/iPad visibly zoomed the viewport. Bump only on touch, so the
       tighter desktop sizing is untouched. */
    input[type="text"],input[type="number"],input[type="date"],input[type="month"],input[type="search"],input[type="email"],input[type="tel"],textarea{
      font-size:16px;
    }
  }

  @media (max-width:600px){
    /* These are real transaction data (a note, a foreign-currency amount,
       a currency-conversion total, a purchase line item) rather than
       decorative labels, so on a phone-sized screen bump them up from
       11-12.5px to a size that's comfortable to actually read — tablets
       and laptops keep the tighter sizing since they're viewed from
       further away with more room to spare. */
    .cell-item .item-note,.item-fx,.stat-fx-list,.row-detail .si{font-size:13px;}
  }

  .toast{
    position:fixed;left:50%;bottom:calc(22px + env(safe-area-inset-bottom));transform:translateX(-50%) translateY(12px);
    background:var(--ink);color:var(--paper);padding:10px 18px;border-radius:999px;
    font-size:13px;box-shadow:0 6px 20px var(--shadow);opacity:0;pointer-events:none;
    transition:opacity .18s ease,transform .18s ease;z-index:50;
  }
  .toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
  .footnote{margin-top:18px;font-size:12px;color:var(--ink-faint);text-align:center;}

  /* ---------- Batch-add modal ---------- */
  .modal-overlay{
    position:fixed;inset:0;background:rgba(10,14,12,.5);
    display:flex;align-items:center;justify-content:center;padding:24px;z-index:70;
  }
  .modal-panel{
    background:var(--surface);border-radius:14px;max-width:960px;width:100%;
    max-height:86vh;display:flex;flex-direction:column;overflow:hidden;
    box-shadow:0 16px 44px var(--shadow);
  }
  .modal-head{display:flex;align-items:center;justify-content:space-between;padding:20px 22px 2px;}
  .modal-head h2{font-family:var(--font-display);font-size:18px;margin:0;font-weight:700;}
  .modal-hint{padding:0 22px;margin:4px 0 14px;font-size:12.5px;color:var(--ink-faint);}
  .batch-table-wrap{overflow:auto;padding:0 22px;flex:1;}
  .batch-table-head,.batch-row{
    display:grid;grid-template-columns:138px 1fr 150px 100px 118px 268px 30px;gap:8px;
    min-width:950px;align-items:center;
  }
  .batch-table-head{
    font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-faint);
    padding:6px 0;position:sticky;top:0;background:var(--surface);border-bottom:1px solid var(--line);
  }
  .batch-row{padding:7px 0;border-bottom:1px solid var(--line);}
  .batch-row input[type="text"],
  .batch-row input[type="date"],
  .batch-row input[type="month"],
  .batch-row input[type="number"]{
    font:inherit;font-family:var(--font-body);font-size:12.5px;color:var(--ink);
    background:var(--surface-2);border:1px solid var(--line);border-radius:6px;padding:7px 9px;width:100%;
  }
  .batch-row input[type="number"]{font-family:var(--font-mono);}
  .batch-row.has-error input{border-color:var(--danger);}
  .br-tags{display:flex;flex-wrap:wrap;gap:4px;}
  #batch-add-row-btn{margin:12px 22px 0;}
  .modal-foot{
    display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
    padding:16px 22px 6px;border-top:1px solid var(--line);margin-top:14px;
  }
  .batch-summary{font-size:13px;color:var(--ink-soft);font-family:var(--font-mono);}
  .modal-foot-actions{display:flex;gap:10px;}
  #batch-cancel-btn{background:transparent;border-color:var(--line-strong);color:var(--ink-soft);}
  #batch-submit-btn{background:var(--accent);color:var(--on-accent);}
  #batch-submit-btn:hover{background:var(--accent-strong);}
  .modal-panel .form-msg{padding:0 22px 16px;margin-top:0;}

  /* ---------- Import CSV modal ---------- */
  .import-controls{display:flex;align-items:center;gap:12px;padding:0 22px;margin-bottom:10px;flex-wrap:wrap;}
  .file-btn{
    display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:8px;
    border:1px solid var(--line-strong);background:var(--surface);color:var(--ink-soft);
    font-size:13px;font-weight:600;cursor:pointer;
  }
  .file-btn:hover{border-color:var(--accent);color:var(--accent-strong);}
  .import-filename{font-size:12.5px;color:var(--ink-faint);}
  .import-billing-field{
    display:flex;align-items:center;gap:6px;margin-left:auto;
    font-size:12.5px;color:var(--ink-soft);font-weight:600;white-space:nowrap;
  }
  .import-billing-field input{
    font:inherit;font-family:var(--font-body);font-size:12.5px;color:var(--ink);
    background:var(--surface-2);border:1px solid var(--line);border-radius:6px;padding:6px 8px;
  }
  .import-billing-field input.has-error{border-color:var(--danger);}
  .import-textarea{
    display:block;margin:0 22px 10px;width:calc(100% - 44px);min-height:80px;resize:vertical;
    font-family:var(--font-mono);font-size:12.5px;color:var(--ink);
    background:var(--surface-2);border:1px solid var(--line);border-radius:8px;padding:10px 12px;
  }
  .import-actions{display:flex;align-items:center;gap:12px;padding:0 22px 12px;flex-wrap:wrap;}
  #import-parse-btn{background:var(--surface);border:1px solid var(--line-strong);color:var(--ink-soft);}
  #import-parse-btn:hover{border-color:var(--accent);color:var(--accent-strong);}
  .import-status{font-size:12.5px;color:var(--ink-faint);}
  .import-preview-wrap{overflow:auto;padding:0 22px;flex:1;border-top:1px solid var(--line);}
  .import-preview-head,.import-preview-row{
    display:grid;grid-template-columns:106px 1fr 130px 100px 90px 170px 150px;gap:8px;
    min-width:860px;align-items:center;padding:7px 0;border-bottom:1px solid var(--line);
  }
  .import-preview-head{
    font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-faint);
    position:sticky;top:0;background:var(--surface);
  }
  .import-preview-row{font-size:12.5px;color:var(--ink);}
  .import-preview-row.is-invalid{color:var(--ink-faint);}
  .import-preview-row.is-dup{box-shadow:inset 3px 0 0 var(--brass);}
  .import-preview-row .status-ok{color:var(--accent-strong);font-weight:700;}
  .import-preview-row .status-bad{color:var(--danger);font-weight:700;}
  .import-preview-row .status-dup{color:var(--brass-text);font-weight:700;}
  #import-submit-btn{background:var(--accent);color:var(--on-accent);}
  #import-submit-btn:hover{background:var(--accent-strong);}
  #import-submit-btn:disabled{opacity:.5;cursor:not-allowed;}
  #import-cancel-btn{background:transparent;border-color:var(--line-strong);color:var(--ink-soft);}
  .import-dup-confirm{
    margin:0 22px 14px;padding:12px 14px;border-radius:10px;
    background:var(--brass-tint);border:1px solid var(--brass);
    display:flex;flex-direction:column;gap:10px;
  }
  .import-dup-confirm p{margin:0;font-size:12.5px;color:var(--brass-text);line-height:1.6;}
  .import-dup-confirm-actions{display:flex;gap:10px;flex-wrap:wrap;}
  .import-dup-confirm-actions button{
    font:inherit;font-family:var(--font-body);font-size:12.5px;font-weight:700;
    padding:8px 14px;border-radius:8px;border:1px solid var(--brass);cursor:pointer;
  }
  .import-dup-confirm-actions button:disabled{opacity:.45;cursor:not-allowed;}
  #import-dup-proceed-btn{background:var(--brass);color:#fff;}
  #import-dup-skip-btn{background:var(--surface);color:var(--brass-text);}
  #import-dup-cancel-btn{background:transparent;border-color:transparent;color:var(--ink-faint);}

  /* ---------- Costco: CSV import/export ---------- */
  .costco-io-row{display:flex;gap:10px;flex-wrap:wrap;margin:0 0 16px;}
  #cc-import-btn,#cc-export-btn{background:var(--surface);border:1px solid var(--line-strong);color:var(--ink-soft);}
  #cc-import-btn:hover,#cc-export-btn:hover{border-color:var(--accent);color:var(--accent-strong);}
  .import-preview-head.cc-cols,.import-preview-row.cc-cols{
    grid-template-columns:106px 100px 1fr 70px 90px 90px 130px;min-width:700px;
  }
  #cc-import-parse-btn{background:var(--surface);border:1px solid var(--line-strong);color:var(--ink-soft);}
  #cc-import-parse-btn:hover{border-color:var(--accent);color:var(--accent-strong);}
  #cc-import-submit-btn{background:var(--accent);color:var(--on-accent);}
  #cc-import-submit-btn:hover{background:var(--accent-strong);}
  #cc-import-submit-btn:disabled{opacity:.5;cursor:not-allowed;}
  #cc-import-cancel-btn{background:transparent;border-color:var(--line-strong);color:var(--ink-soft);}
  #cc-import-dup-proceed-btn{background:var(--brass);color:#fff;}
  #cc-import-dup-skip-btn{background:var(--surface);color:var(--brass-text);}
  #cc-import-dup-cancel-btn{background:transparent;border-color:transparent;color:var(--ink-faint);}

  /* Floating popovers (分類／備註／外幣) are a fixed pixel width — safe on
     any normal phone, but cap it so an unusually narrow viewport can't push
     them past the screen edge. */
  .tag-popover,.note-popover,.fx-popover{max-width:calc(100vw - 24px);}

  @media (max-width:640px){
    /* The batch/import modals hold wide, spreadsheet-like grids that need
       to scroll horizontally regardless of screen size — that's expected.
       What mobile doesn't need is 22px of dead padding on every side eating
       into the little width there is, so it shrinks down here. */
    /* Anchor modals to the top instead of centering them. Centering math is
       done against the full layout viewport, which on phones does NOT
       shrink when the on-screen keyboard opens — so a centered modal can
       end up straddling the keyboard, with its bottom button rendered
       underneath it and unreachable except by nudging the page around.
       Starting from the top and letting the panel scroll internally if
       it's still taller than what's left above the keyboard keeps the
       buttons reachable. */
    .modal-overlay{
      padding:max(10px, env(safe-area-inset-top)) max(10px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left));
      align-items:flex-start;
    }
    .modal-panel{
      max-height:92vh;max-height:92dvh; /* dvh tracks the keyboard where supported; vh is the fallback */
      overflow-y:auto;overflow-x:hidden;border-radius:12px;
    }
    .modal-head{padding:16px 16px 2px;}
    .modal-hint{padding:0 16px;}
    .batch-table-wrap,.import-preview-wrap,.import-controls,.import-actions{padding-left:16px;padding-right:16px;}
    .import-textarea{margin:0 16px 10px;width:calc(100% - 32px);}
    .import-dup-confirm{margin:0 16px 14px;}
    #batch-add-row-btn{margin:12px 16px 0;}
    .modal-foot{padding:14px 16px 6px;}
    .modal-panel .form-msg{padding:0 16px 16px;}
  }

  /* ---------- Costco purchase tracker ---------- */
  .view-toggle-row{margin-bottom:20px;}
  .view-toggle-row button{
    background:var(--surface);border:1px solid var(--line-strong);border-radius:999px;
    padding:8px 16px;font:inherit;font-size:13.5px;font-weight:600;cursor:pointer;color:var(--ink);
  }
  .view-toggle-row button:hover{background:var(--surface-2);}
  #costco-view{--cc-red:#E31837;--cc-blue:#005A9C;--cc-blue-tint:#DCEAF5;}
  /* --cc-blue is used as text color both for .costco-total-line (the
     期間內消費金額/折扣金額 total line) and for 特價 row text/badge below —
     always directly on the page background (or on --cc-blue-tint), never
     as a button/tab fill like --cc-red — so unlike --cc-red it needs a
     lighter, dark-mode-specific value, or its ~3:1 contrast against the
     dark theme's near-black surface is hard to read. --cc-blue-tint is
     the soft badge background paired with it (same light-tint/dark-tint
     pattern as --brass-tint and --accent-tint elsewhere in the file). */
  @media (prefers-color-scheme: dark){
    :root:not([data-theme="light"]) #costco-view{--cc-blue:#6FB3EA;--cc-blue-tint:#16324A;}
  }
  :root[data-theme="dark"] #costco-view{--cc-blue:#6FB3EA;--cc-blue-tint:#16324A;}
  .costco-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:18px;}
  .costco-head h2{font-family:var(--font-display);font-size:22px;margin:0 0 4px;}
  .costco-head .slip-hint{margin:0;color:var(--ink-soft);font-size:13.5px;}
  .costco-tabs,.costco-subtabs{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px;}
  .costco-tab,.costco-subtab{
    background:var(--surface);border:1px solid var(--line-strong);border-radius:999px;
    padding:7px 16px;font:inherit;font-size:13.5px;font-weight:600;color:var(--ink-soft);cursor:pointer;
  }
  .costco-tab.is-active,.costco-subtab.is-active{background:var(--cc-red);border-color:var(--cc-red);color:#fff;}
  .costco-card{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:18px;margin-bottom:18px;}
  .costco-form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:14px;margin-bottom:6px;}
  .costco-form-grid .field{margin:0;}
  .costco-total-line{font-weight:700;font-size:15px;margin:10px 0 14px;color:var(--cc-blue);}
  .costco-actions{display:flex;justify-content:flex-end;gap:10px;flex-wrap:wrap;}
  .costco-actions button{background:var(--cc-red);color:#fff;border:none;border-radius:8px;padding:9px 20px;font:inherit;font-weight:600;cursor:pointer;}
  .costco-actions button:hover{opacity:.88;}
  .costco-actions button.link-btn{background:none;color:var(--ink-soft);padding:9px 8px;}
  .costco-table-wrap{overflow-x:auto;margin-bottom:4px;}
  /* min-width keeps every column at a readable size on a phone-width
     screen — the table scrolls horizontally inside .costco-table-wrap
     instead of squeezing every column down to unreadable/untappable
     slivers to fit width:100%. */
  .costco-table{width:100%;min-width:760px;border-collapse:collapse;font-size:13.5px;table-layout:fixed;}
  .costco-table th,.costco-table td{padding:8px 10px;border-bottom:1px solid var(--line);text-align:left;white-space:nowrap;vertical-align:middle;}
  .costco-table th{color:var(--ink-soft);font-weight:600;background:var(--surface-2);position:sticky;top:0;}
  /* Fixed per-column widths (table-layout:fixed) so every list — 特價／
     退貨／紅利折抵／依日期查詢／新增整批 — renders the exact same column
     layout no matter which rows happen to be in it. Without this, the
     browser's default per-table auto-sizing looks at every row's content
     to size each column, so 品名 (and everything after it) ends up a
     different width depending on the longest product name in THAT
     particular filtered list — which is exactly why 全部年份 (309 rows,
     more likely to contain one unusually long name) rendered its columns
     differently from a single year (29 rows): 小計 even ended up pushed
     out past the visible edge. 品名 is the one column deliberately left
     without a set width — it absorbs whatever space is left over, and
     wraps (see the nth-child(4) rule below) instead of stretching the
     table to fit a long name. */
  .costco-table th:nth-child(2),.costco-table td:nth-child(2){width:104px;}
  .costco-table th:nth-child(3),.costco-table td:nth-child(3){width:92px;}
  .costco-table th:nth-child(4),.costco-table td:nth-child(4){white-space:normal;word-break:break-word;}
  .costco-table th:nth-child(5),.costco-table td:nth-child(5){width:64px;}
  .costco-table th:nth-child(6),.costco-table td:nth-child(6){width:92px;}
  .costco-table th:nth-child(7),.costco-table td:nth-child(7){width:92px;}
  .costco-table th:nth-child(8),.costco-table td:nth-child(8){width:104px;}
  /* The leftmost column holds the ✕ delete button. On a wide table (this
     one has 8 columns) the far side can scroll out of view inside
     .costco-table-wrap's horizontal scroll, making delete look missing —
     pin it in place so it's always reachable without scrolling.
     Deliberately the FIRST column, pinned to the LEFT, not the last column
     pinned to the right: a right-pinned trailing column sits on top of
     whichever real column would otherwise render there the moment the
     table overflows at all — even unscrolled — silently hiding it (this
     is how an earlier version of this fix ended up hiding 小計 behind the
     delete button on 全部年份's wider table). A left-pinned FIRST column
     has no such problem, because unscrolled it's already sitting exactly
     where it would be anyway — it only visibly "sticks" once you actually
     scroll, which is the behavior sticky is meant for. */
  .costco-table th:first-child,.costco-table td:first-child{
    position:sticky;left:0;background:var(--surface);
    border-right:1px solid var(--line);text-align:center;width:44px;
  }
  .costco-table th:first-child{background:var(--surface-2);z-index:2;}
  .costco-table td:first-child{z-index:1;}
  .costco-table tr.is-best td{background:var(--accent-tint);}
  .costco-table tr.is-sale td:nth-child(2){box-shadow:inset 3px 0 0 var(--cc-blue);}
  .costco-table tr.is-sale td{color:var(--cc-blue);}
  .costco-table tr.is-return td{color:var(--danger);}
  .costco-table tr.is-bonus td{color:var(--accent-strong);}
  .costco-stat-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-bottom:16px;}
  .costco-stat-card{background:var(--surface-2);border:1px solid var(--line);border-radius:10px;padding:10px 14px;}
  .costco-stat-card .num{font-family:var(--font-mono);font-weight:600;font-size:17px;}
  .costco-stat-card .lbl{font-size:11.5px;color:var(--ink-soft);}
  /* Same treatment as the main ledger's headline "本期刷卡總額" stat tile
     (.stat-lead), reused here for the Costco view's own headline number. */
  .costco-stat-card.is-lead{background:var(--hero-bg);border-color:var(--hero-bg);}
  .costco-stat-card.is-lead .num{color:var(--hero-fg);}
  .costco-stat-card.is-lead .lbl{color:var(--hero-fg-dim);}
  .costco-batch-scan-row{display:flex;align-items:center;gap:14px;margin-bottom:10px;flex-wrap:wrap;}
  .costco-filter-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:14px;}
  .costco-filter-row select,.costco-filter-row input[type=text],.costco-filter-row input[type=date]{
    font:inherit;font-size:13px;padding:7px 12px;border-radius:8px;border:1px solid var(--line-strong);background:var(--surface);color:var(--ink);
  }
  .costco-filter-row label{font-size:12.5px;font-weight:600;color:var(--ink-soft);}
  .costco-batch-row input{width:100%;font:inherit;font-size:13px;padding:5px 7px;border-radius:6px;border:1px solid var(--line-strong);background:var(--surface);color:var(--ink);}
  .costco-batch-row td{vertical-align:top;padding:4px 6px;}
  /* These two have no fixed width like qty/price/discount do (they're
     free-text), so without a floor they're what actually gets crushed when
     the table is squeezed on a narrow screen. */
  .cc-b-pid{min-width:80px;}
  .cc-b-name{min-width:150px;}
  .costco-del-btn{background:none;border:none;color:var(--danger);cursor:pointer;font-size:15px;padding:2px 8px;font-weight:700;}
  .costco-del-btn.is-armed{background:var(--danger-tint);border-radius:6px;}
  .costco-empty{color:var(--ink-faint);font-size:13px;padding:20px 0;text-align:center;}
  /* Computed "on sale" badge (discount>0) — independent of the freeform
     備註 note, which can happen to contain unrelated text like "特價" of
     its own and shouldn't be mistaken for this indicator. */
  .costco-sale-badge{
    font-size:10px;font-weight:700;color:var(--cc-blue);background:var(--cc-blue-tint);
    padding:2px 7px;border-radius:999px;margin-left:6px;letter-spacing:.03em;white-space:nowrap;
  }
  /* Same idea as .costco-sale-badge, computed from price<0 rather than the
     備註 note — 退貨 when the line still has a product number (an actual
     returned item), 紅利折抵 when it doesn't (a redemption credit, not a
     product at all, so it's kept out of the returns list). */
  .costco-return-badge{
    font-size:10px;font-weight:700;color:#fff;background:var(--danger);
    padding:2px 7px;border-radius:999px;margin-left:6px;letter-spacing:.03em;white-space:nowrap;
  }
  .costco-bonus-badge{
    font-size:10px;font-weight:700;color:var(--accent-strong);background:var(--accent-tint);
    padding:2px 7px;border-radius:999px;margin-left:6px;letter-spacing:.03em;white-space:nowrap;
  }
  #costco-add-batch-row-btn{margin-bottom:12px;}
  @media (max-width:640px){
    .costco-tabs,.costco-subtabs{gap:6px;}
    .costco-tab,.costco-subtab{padding:6px 12px;font-size:12.5px;}
    .costco-card{padding:14px;}
  }
  @media (max-width:420px){
    /* Same reasoning as the main ledger's own .stats breakpoint here: two
       narrow columns start crowding a long "NT$ 265,090"-style value on
       the smallest iPhones, so stack the summary cards full-width instead. */
    .costco-stat-cards{grid-template-columns:1fr;}
  }
  @media (pointer:coarse){
    /* Costco's tab/delete/filter controls use their own classes (not the
       main ledger's .icon-btn/.chip/etc, already covered by the earlier
       pointer:coarse block), so they need their own touch-target bump here
       — matters on iPad and iPhone the same way it does for the ledger. */
    .costco-tab,.costco-subtab{padding:10px 16px;}
    .costco-del-btn{padding:7px 11px;font-size:16px;}
    .costco-filter-row select,.costco-filter-row input[type=text],.costco-filter-row input[type=date]{padding:10px 12px;}
    .costco-batch-row input{padding:8px 9px;}
    .costco-batch-row .cc-b-sale{transform:scale(1.3);}
  }

  /* ---------- 收據明細 (general, non-Costco receipt log) ---------- */
  #rcpt-item-rows{display:flex;flex-direction:column;gap:6px;}
  #rcpt-item-rows:not(:empty){margin-bottom:6px;}
  #rcpt-submit-btn{background:var(--accent);color:var(--on-accent);}
  #rcpt-submit-btn:hover{background:var(--accent-strong);}
  .rcpt-card{border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin-bottom:10px;background:var(--surface-2);}
  .rcpt-card:last-child{margin-bottom:0;}
  /* flex-wrap so the two action buttons drop to their own row instead of
     squeezing the meta line (date/time/merchant/invoice/payment) down to a
     sliver on an iPhone-width card — a Mac window or landscape iPad has
     room for everything on one line and never wraps. */
  .rcpt-card-head{display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:6px 10px;margin-bottom:6px;}
  .rcpt-card-check{display:flex;align-items:center;flex:none;padding-top:2px;}
  .rcpt-card-check input{width:16px;height:16px;accent-color:var(--accent);cursor:pointer;margin:0;}
  .rcpt-card-actions{display:flex;align-items:center;gap:10px;flex:none;margin-left:auto;}
  .rcpt-list-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;}
  .rcpt-move-btn{white-space:nowrap;}
  .rcpt-move-btn.is-armed{color:var(--accent-strong);}
  .rcpt-ledger-btn{white-space:nowrap;}
  .rcpt-ledger-btn.is-armed{color:var(--accent-strong);}
  .rcpt-card-meta{font-size:13px;color:var(--ink);min-width:0;overflow-wrap:anywhere;flex:1 1 190px;}
  .rcpt-card-date{font-family:var(--font-mono);font-weight:600;}
  .rcpt-card-time{font-family:var(--font-mono);font-weight:600;color:var(--ink-soft);margin-left:4px;}
  .rcpt-card-time::before{content:"⏰ ";}
  .rcpt-card-merchant{color:var(--ink-soft);margin-left:6px;}
  .rcpt-card-invoice{color:var(--ink-faint);font-family:var(--font-mono);font-size:11.5px;margin-left:8px;}
  .rcpt-card-payment{color:var(--ink-faint);font-size:11.5px;margin-left:8px;padding:1px 7px;border:1px solid var(--line);border-radius:20px;}
  /* Only shown for a non-TWD receipt (rare — most are plain NT$), so it
     uses the brass/caution color to flag "this one's not NT$" at a glance
     rather than blending in like the other neutral meta badges. */
  .rcpt-card-currency{color:var(--brass-text);background:var(--brass-tint);font-size:11px;font-weight:700;margin-left:8px;padding:1px 7px;border-radius:20px;letter-spacing:.02em;}
  .rcpt-moved-badge{color:var(--ink-faint);font-size:12px;font-weight:600;white-space:nowrap;}
  .rcpt-card-items{display:flex;flex-direction:column;gap:5px;margin-bottom:6px;}
  /* Item lines wrap instead of truncating — unlike the Costco table (a
     dense grid meant for scanning many rows), each receipt card only shows
     a handful of lines, so it's worth showing the full name (plus its
     Chinese translation) rather than clipping it with an ellipsis, which on
     a narrow iPhone card was cutting the translation off entirely. Qty and
     amount ride along on the name's row when there's room (a wide Mac/iPad
     card) and drop to their own right-aligned row when there isn't (a
     one-column iPhone card). */
  .rcpt-item-line{display:flex;align-items:flex-start;flex-wrap:wrap;gap:2px 8px;font-size:12.5px;color:var(--ink-soft);}
  .rcpt-item-line .rcpt-item-name{flex:1 1 auto;min-width:120px;overflow-wrap:anywhere;color:var(--ink);}
  .rcpt-item-pid{font-family:var(--font-mono);font-size:11px;color:var(--ink-faint);}
  .rcpt-item-line .rcpt-item-qty{font-family:var(--font-mono);color:var(--ink-faint);white-space:nowrap;}
  .rcpt-item-line .rcpt-item-amount{font-family:var(--font-mono);white-space:nowrap;margin-left:auto;}
  .rcpt-card-note{color:var(--ink-faint);font-size:11.5px;margin-top:4px;overflow-wrap:anywhere;}
  .rcpt-card-total{font-weight:700;font-size:14px;color:var(--accent-strong);text-align:right;}
  /* 版次 badge (2026-09-12): plain text at v1 (never edited); once there's
     an edit history it becomes a small link that opens the rollback list
     below the card — kept visually minor (same style family as the other
     meta badges) since most receipts will never need it. */
  .rcpt-version-plain{color:var(--ink-faint);font-size:11px;font-family:var(--font-mono);margin-left:8px;}
  .rcpt-version-badge{font-size:11px;font-family:var(--font-mono);margin-left:8px;white-space:nowrap;}
  .rcpt-history-panel{margin-top:8px;padding-top:8px;border-top:1px dashed var(--line);display:flex;flex-direction:column;gap:6px;}
  .rcpt-history-hint{margin:0 0 2px;font-size:11px;color:var(--ink-faint);}
  .rcpt-history-row{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:12px;}
  .rcpt-history-meta{color:var(--ink-soft);font-family:var(--font-mono);}
  .rcpt-history-restore{white-space:nowrap;}
  /* On a Mac window or an iPad in landscape there's enough width that a
     single column of small receipt cards looks sparse (each card stretches
     edge-to-edge with mostly empty space to the right of its 2-3 lines of
     content). Lay them out as a multi-column grid instead once there's room
     for at least two ~300px cards side by side; below that (iPad portrait,
     iPhone) it stays the original single stacked column. */
  @media (min-width:700px){
    /* Bumped from 300px to 340px — each card now carries more on its head
       line (a checkbox, plus date/time/merchant/invoice/payment badges),
       and 300px was tuned before those existed; 340px keeps that line from
       wrapping needlessly on a two-up Mac/iPad layout. */
    #rcpt-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));align-items:start;gap:12px;}
    #rcpt-list .rcpt-card{margin-bottom:0;}
    #rcpt-list .costco-empty{grid-column:1/-1;}
  }
  /* The date/time/merchant row also stretches its date and time boxes as
     wide as the merchant field on a full Mac window (same auto-fit grid
     used by every other form in this app) — harmless, but a fixed-width
     lane for the two shortest fields reads more intentionally once there's
     desktop-width room to spare, leaving merchant free to take the rest. */
  @media (min-width:640px){
    #receipt-entry-card .costco-form-grid{grid-template-columns:150px 130px 1fr 170px 150px;}
  }

/* ---------- Additions for the Cloudflare/D1 self-hosted rewrite ---------- */
/* The original masthead had a decorative card-logo image (a large embedded
   base64 PNG) to the left of the title; dropped here as purely decorative
   (see API_DESIGN.md / migration notes for why) — h1 carries the same text. */
.sync-pill.is-error .sync-dot{background:var(--danger);box-shadow:0 0 0 3px var(--danger-tint);}
.scan-note{font-size:11px;color:var(--ink-faint);margin:4px 0 0;}
