/* Self-hosted so that no request — and no visitor IP — reaches Google. One variable file per
   family and subset covers every weight the app uses. */
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:100 1000; font-display:swap; src:url('fonts/dm-sans-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:100 1000; font-display:swap; src:url('fonts/dm-sans-latin.woff2') format('woff2'); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family:'Space Grotesk'; font-style:normal; font-weight:300 700; font-display:swap; src:url('fonts/space-grotesk-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:'Space Grotesk'; font-style:normal; font-weight:300 700; font-display:swap; src:url('fonts/space-grotesk-latin.woff2') format('woff2'); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
/* SPAR's red, sampled from its logo, and a green a shade darker than SPAR's own so that amounts in
   it stay readable as small text on white — Libri is the bound book at night,
   Tracktivity the cool green, TicketWatch the plum, Gustus the tomato and Horizon the dawn. --red is
   the colour anything interactive is drawn in. --green is money left on a voucher and is used for
   nothing else, so a figure in green is always something that can still be spent; --amber marks a
   balance nobody has checked yet, which is the face value and may well be out of date. */
:root { --ink:#24201f; --muted:#7a716d; --paper:#f8f5f3; --card:#fffefd; --line:#ebe3df; --red:#e40613; --red-soft:#fbe4e2; --green:#0b7a3a; --green-soft:#def2e6; --alert:#b3261e; --alert-soft:#fbe3e1; --amber:#a86412; --amber-soft:#fbefd9; --gold:#f5c64a; }
* { box-sizing:border-box; }
/* The app's own text is not selectable. Chrome on Android answers a tap on selectable text by
   selecting the word and opening a Google search over it ("Touch to Search"), which at the till —
   tapping round a balance or a card number — is a search nobody asked for. It only fires on text that
   can be selected, so this is what stops it; the card number and PIN have Copy buttons for the one
   reason to select them. Text fields, the privacy policy and the bookmarklet's code stay selectable.
   user-select inherits, which is also why those have to say so: without it, a none on an ancestor
   would silently take away the long-press "paste" menu inside text fields. */
body { user-select:none; -webkit-user-select:none; }
input,textarea,.legal-block,.legal-body { user-select:text; -webkit-user-select:text; }
/* Beats any display rule below: .login-shell sets display:flex, which otherwise outranks the
   browser's own [hidden] { display:none } and leaves the signed-out panel showing under every tab. */
[hidden] { display:none !important; }
/* Android browsers "boost" text they take for body copy, which silently overrides small sizes. */
html { -webkit-text-size-adjust:100%; text-size-adjust:100%; }
/* A sideways drag would otherwise engage the browser's own swipe-to-go-back gesture, which nudges the
   whole page and drops fixed elements like the tab bar for a few frames. */
html,body { overscroll-behavior-x:none; }
/* The document does not scroll; the body inside it does. Browsers hang their own furniture — Samsung
   Internet's floating "scroll to top" button among it — off whatever the root scroller is, and in an
   installed app that button lands on top of the tab bar. See Libri's commit of the same name. */
html { height:100%; overflow:hidden; }
body { height:100%; overflow-x:hidden; overflow-y:auto; }
.shell { overflow-x:clip; }
/* Only transform and opacity are animated: those two the compositor handles on its own. */
@keyframes card-in-right { from { opacity:0; transform:translate3d(22%,0,0); } to { opacity:1; transform:none; } }
@keyframes card-in-left { from { opacity:0; transform:translate3d(-22%,0,0); } to { opacity:1; transform:none; } }
.view.card-right,.view.card-left { backface-visibility:hidden; will-change:transform, opacity; }
.view.card-right { animation:card-in-right .24s cubic-bezier(.16,.84,.36,1); }
.view.card-left { animation:card-in-left .24s cubic-bezier(.16,.84,.36,1); }
@media (prefers-reduced-motion: reduce) { .view.card-right,.view.card-left { animation:none !important; } }
body { margin:0; background:radial-gradient(circle at 100% 0%, #fbdcd9 0, transparent 38%), radial-gradient(circle at 0% 100%, #dcefe3 0, transparent 34%), var(--paper); background-attachment:fixed; color:var(--ink); font-family:'DM Sans', sans-serif; }
button,input,select,textarea { font:inherit; }
button { cursor:pointer; }
.shell,.login-shell { width:min(100% - 32px, 720px); margin:0 auto; }
.topbar { display:flex; align-items:center; justify-content:space-between; padding:26px 0 14px; gap:10px; }
.brand { align-items:center; display:flex; gap:12px; }
.brand img { border-radius:10px; box-shadow:0 4px 14px rgba(120,20,24,.22); flex:none; }
h1,h2 { font-family:'Space Grotesk', sans-serif; margin:0; letter-spacing:0; }
h1 { font-size:clamp(1.6rem, 6vw, 2.2rem); line-height:1.05; }
h2 { font-size:1.45rem; }
.kicker,.eyebrow { color:var(--red); font-size:.68rem; font-weight:700; letter-spacing:.14em; margin:0 0 5px; text-transform:uppercase; }
.muted { color:var(--muted); }
.icon-button { border:0; background:transparent; color:var(--muted); font-size:1.35rem; padding:8px; }
.topbar-actions { align-items:center; display:flex; gap:2px; position:relative; }
.menu { background:var(--card); border:1px solid var(--line); border-radius:6px; box-shadow:0 12px 28px rgba(36,32,31,.16); display:flex; flex-direction:column; overflow:hidden; padding:5px; position:absolute; right:0; top:calc(100% + 6px); width:max-content; min-width:190px; z-index:40; }
/* nowrap plus a max-content menu keeps every label on one line, whatever the phone's width.
   Absolute px, not rem: immune to a scaled root font size on the phone. */
.menu-item { background:transparent; border:0; border-radius:4px; color:var(--ink); font-size:11px; letter-spacing:.02em; padding:8px 11px; text-align:left; white-space:nowrap; width:100%; }
.menu-item:hover { background:var(--paper); }
.menu-item.danger { color:var(--alert); }
.menu-caption { color:var(--muted); display:block; font-size:.85em; letter-spacing:.08em; text-transform:uppercase; }
.menu-value { display:block; font-weight:600; margin-top:2px; }
.menu-value:after { content:' ▸'; color:var(--muted); }
.menu-divider { background:var(--line); height:1px; margin:4px 6px; }
.view { padding-bottom:100px; }
.view-title { align-items:end; display:flex; gap:12px; justify-content:space-between; margin:2px 0 16px; }
#view-legal { padding-bottom:40px; }
.section-heading { align-items:end; display:flex; gap:12px; justify-content:space-between; margin:24px 0 10px; }
.section-heading h3 { font-family:'Space Grotesk', sans-serif; font-size:1.02rem; margin:0; }
.section-heading span { color:var(--muted); font-size:.78rem; }

/* ---- panels and figures ---- */
.panel { background:var(--card); border:1px solid var(--line); border-radius:12px; padding:16px; }
.panel + .panel { margin-top:12px; }
.panel h3 { font-family:'Space Grotesk', sans-serif; font-size:1rem; margin:0 0 10px; }
.figure-label { color:var(--muted); font-size:.68rem; font-weight:700; letter-spacing:.12em; text-transform:uppercase; }
.amount { font-family:'Space Grotesk', sans-serif; font-variant-numeric:tabular-nums; font-weight:700; white-space:nowrap; }


/* ---- imprint and privacy ---- */
.back-button { background:transparent; border:0; color:inherit; font:inherit; letter-spacing:inherit; margin:0; padding:0; text-transform:inherit; }
.imprint-button { background:transparent; border:0; color:var(--muted); font-size:.78rem; padding:8px 4px; white-space:nowrap; }
.legal-block { background:var(--card); border:1px solid var(--line); border-radius:8px; padding:16px 18px; }
.legal-facts { display:grid; gap:2px 16px; grid-template-columns:auto 1fr; margin:0 0 14px; }
.legal-facts dt { color:var(--muted); font-size:.76rem; letter-spacing:.06em; text-transform:uppercase; }
.legal-facts dd { margin:0 0 8px; }
.legal-block a,.legal-body a { color:var(--red); }
.legal-details { background:var(--card); border:1px solid var(--line); border-radius:8px; margin-top:12px; }
.legal-details summary { cursor:pointer; font-family:'Space Grotesk', sans-serif; font-size:1.05rem; font-weight:600; list-style:none; padding:15px 18px; }
.legal-details summary::-webkit-details-marker { display:none; }
.legal-details summary:after { color:var(--muted); content:'▾'; float:right; transition:transform .2s; }
.legal-details[open] summary:after { transform:rotate(180deg); }
.legal-body { border-top:1px solid var(--line); padding:4px 18px 18px; }
.legal-body h3 { font-family:'Space Grotesk', sans-serif; font-size:.95rem; margin:18px 0 6px; }
.legal-body p,.legal-body li { color:#4a4442; font-size:.88rem; line-height:1.55; margin:6px 0; }
.legal-body ul { margin:6px 0; padding-left:20px; }

/* ---- tab bar ----
   Opaque, not translucent: translate3d and will-change keep it on its own compositor layer, so it is
   not repainted — and briefly dropped — while a card animates past. */
.tabbar { background:var(--paper); border-top:1px solid var(--line); bottom:0; display:flex; justify-content:space-around; left:50%; max-width:720px; padding:11px 0 max(11px, env(safe-area-inset-bottom)); position:fixed; transform:translate3d(-50%,0,0); width:100%; will-change:transform; z-index:3; }
.tabbar button { align-items:center; background:none; border:0; color:var(--muted); display:flex; flex-direction:column; font-size:.72rem; gap:4px; padding:3px 18px; }
.tabbar button svg { display:block; height:22px; width:22px; }
.tabbar button.active { color:var(--red); font-weight:700; }
.tabbar button em { font-style:normal; }

/* ---- buttons, dialogs, the rest of the furniture ---- */
.primary-button { background:var(--red); border:0; border-radius:5px; color:#fff; font-weight:700; padding:10px 18px; white-space:nowrap; }
.primary-button:disabled,.update-button:disabled { opacity:.55; cursor:default; }
.ghost-button { background:var(--card); border:1px solid var(--line); border-radius:5px; color:var(--ink); font-weight:500; padding:9px 14px; }
.ghost-button.on { background:var(--green-soft); border-color:var(--green); color:var(--green); font-weight:700; }
.danger-button { background:var(--alert); }
.ghost-button.danger { color:var(--alert); }
.link-button { background:none; border:0; color:var(--red); font-weight:700; padding:0; text-decoration:underline; }
.empty-state { color:var(--muted); padding:30px 10px; text-align:center; }
.empty-state .primary-button { margin-top:14px; }
.loading { color:var(--muted); font-size:.88rem; padding:24px 4px; text-align:center; }
.offline-note { background:var(--amber-soft); border-radius:6px; color:var(--ink); font-size:.82rem; margin:0 0 12px; padding:8px 12px; }
.storage-status { border-left:3px solid var(--amber); color:var(--ink); font-size:.86rem; line-height:1.45; margin:0 0 12px; padding:2px 0 2px 10px; }
.storage-status.kept { border-left-color:var(--green); }
.segmented { background:var(--card); border:1px solid var(--line); border-radius:6px; display:flex; overflow:hidden; }
.segmented button { background:transparent; border:0; color:var(--muted); flex:1; font-size:.84rem; padding:10px 6px; }
.segmented button.active { background:var(--green); color:#fff; font-weight:700; }
.modal-overlay { align-items:center; background:rgba(36,32,31,.46); bottom:0; display:flex; justify-content:center; left:0; opacity:0; padding:20px; position:fixed; right:0; top:0; transition:opacity .16s; z-index:10; }
.modal-overlay.open { opacity:1; }
.modal { background:var(--paper); border-radius:12px; box-shadow:0 18px 44px rgba(36,32,31,.26); max-height:calc(100vh - 40px); max-width:480px; overflow-y:auto; padding:22px; transform:translateY(8px); transition:transform .16s; width:100%; }
/* vh on a phone is measured with the browser's toolbars hidden, so a dialog sized in it is taller
   than what can be seen and its last row — the buttons — sits under the address bar. dvh is the
   height there really is. */
@supports (max-height: 100dvh) { .modal { max-height:calc(100dvh - 40px); } }
.modal-overlay.open .modal { transform:none; }
.modal h3 { font-family:'Space Grotesk', sans-serif; font-size:1.2rem; margin:0 0 10px; }
.modal-copy { color:var(--muted); font-size:.9rem; line-height:1.45; margin:0 0 18px; }
/* Wrapping is not cosmetic: a flex row that justifies to the end overflows towards the start, and
   overflow in that direction cannot be scrolled to — on a narrow phone the leftmost button, which is
   Delete, would simply be gone. */
.modal-actions { display:flex; flex-wrap:wrap; gap:9px; justify-content:flex-end; margin-top:20px; }
.modal-actions .spacer { flex:1 1 0; min-width:0; }
.modal-error { background:var(--alert-soft); border-radius:6px; color:#8c1c17; font-size:.86rem; margin:4px 0 0; padding:9px 11px; }
@media (max-width: 460px) {
  .modal-overlay { padding:12px; }
  .modal { padding:18px; }
  .modal-actions { flex-direction:column-reverse; }
  .modal-actions .spacer { display:none; }
  .modal-actions > button, .modal-actions > a { padding:12px 14px; text-align:center; width:100%; }
}
.field { display:flex; flex-direction:column; gap:8px; margin-bottom:16px; }
.field > span { color:var(--muted); font-size:.72rem; font-weight:700; letter-spacing:.09em; text-transform:uppercase; }
.field input,.field select { background:var(--card); border:1px solid var(--line); border-radius:5px; color:var(--ink); min-width:0; padding:11px; width:100%; }
.field input:disabled { background:#f0ebe8; color:var(--muted); }
.field select { appearance:none; background-image:linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%); background-position:calc(100% - 17px) 50%, calc(100% - 12px) 50%; background-repeat:no-repeat; background-size:5px 5px, 5px 5px; padding-right:34px; }
.field small { color:var(--muted); font-size:.74rem; }
.field-row { display:flex; gap:9px; }
.field-row > .field { flex:1; min-width:0; }
@media (max-width: 360px) { .field-row { flex-direction:column; gap:0; } }
.row-list { display:flex; flex-direction:column; gap:8px; }
.row { align-items:center; background:var(--card); border:1px solid var(--line); border-radius:8px; color:var(--ink); display:flex; gap:12px; padding:10px 12px; text-align:left; width:100%; }
.row-copy { display:flex; flex:1; flex-direction:column; gap:3px; min-width:0; }
.row-copy small { color:var(--muted); font-size:.77rem; }
.row .arrow { color:var(--muted); margin-left:auto; }

/* ---- signed out, and the two banners ---- */
.login-shell { align-items:flex-start; display:flex; flex-direction:column; justify-content:center; min-height:100vh; padding:30px 0; }
.login-mark { border-radius:16px; box-shadow:0 8px 24px rgba(120,20,24,.25); margin-bottom:26px; }
.login-shell h1 { font-size:clamp(2.2rem, 11vw, 4.3rem); max-width:560px; }
.login-shell > .muted { margin:10px 0 30px; }
.login-actions { display:flex; gap:9px; margin-top:4px; width:100%; max-width:420px; }
.login-actions .primary-button { align-items:center; display:flex; flex:1; justify-content:center; min-height:48px; text-decoration:none; }
.login-switch { color:var(--muted); font-size:.85rem; margin:14px 0 0; }
.build-badge { color:var(--muted); font-size:.6rem; letter-spacing:.04em; margin:0; opacity:.7; pointer-events:none; position:fixed; right:12px; top:max(calc(env(safe-area-inset-top) + 6px), 6px); z-index:2; }
.update-banner { align-items:center; background:var(--ink); border-radius:6px; color:var(--paper); display:flex; font-size:.85rem; gap:12px; left:50%; max-width:calc(100% - 32px); padding:10px 12px; position:fixed; top:12px; transform:translateX(-50%); width:720px; z-index:6; }
.update-banner span { flex:1; }
.update-button { background:var(--gold); border:0; border-radius:5px; color:var(--ink); font-weight:700; padding:8px 14px; }
#toast { background:var(--ink); border-radius:5px; bottom:85px; color:#fff; left:50%; max-width:calc(100% - 32px); opacity:0; padding:12px 16px; pointer-events:none; position:fixed; transform:translate(-50%, 15px); transition:.2s; z-index:20; }
#toast.show { opacity:1; transform:translate(-50%,0); }

@media (max-width:520px) {
  .shell,.login-shell { width:min(100% - 24px, 720px); }
  .primary-button { min-height:46px; }
  .brand img { height:36px; width:36px; }
  .tabbar button { padding:3px 12px; }
}

/* ---- the active voucher ----
   The screen that is held up at the till. The barcode sits on white whatever the page does around it,
   with the quiet zone Code 128 needs on either side, and is as wide as the screen allows: a scanner
   reads wide bars from further away and at a worse angle than narrow ones. */
.active-card { background:var(--card); border:1px solid var(--line); border-radius:14px; box-shadow:0 10px 30px rgba(120,20,24,.10); overflow:hidden; }
.active-head { align-items:center; background:var(--red); color:#fff; display:flex; gap:10px; justify-content:space-between; padding:12px 16px; }
.active-head .kicker { color:rgba(255,255,255,.85); margin:0; }
.active-head strong { font-family:'Space Grotesk', sans-serif; font-size:1.05rem; letter-spacing:.06em; }
.active-body { padding:18px 16px 16px; }
.barcode { background:#fff; border-radius:6px; display:block; padding:14px 0 6px; }
.barcode svg { display:block; height:110px; width:100%; }
.barcode[role=button] { cursor:zoom-in; }
/* The barcode large: a white card over almost the whole screen, and in it the code turned so its
   length runs along the phone's long side, whichever way the screen is (--turn, set by the page). The card is a size container, so the turned block can
   be given the card's height as its width and the card's width as its height — the two swap when it
   turns. Quiet zones stay inside the SVG, and nothing but white is near the bars. */
.modal-overlay.barcode-overlay { background:rgba(36,32,31,.7); }
.modal.barcode-modal { background:#fff; container-type:size; height:calc(100vh - 40px); height:calc(100dvh - 40px); max-height:none; max-width:none; overflow:hidden; padding:0; position:relative; }
@media (max-width: 460px) { .modal.barcode-modal { height:calc(100vh - 24px); height:calc(100dvh - 24px); } }
.barcode-turned { display:flex; flex-direction:column; gap:10px; height:calc(100cqw - 24px); left:50%; position:absolute; text-align:center; top:50%;
  transform:translate(-50%, -50%) rotate(var(--turn, 90deg)); width:calc(100cqh - 24px); }
/* Turned by 0 or 180 degrees — the screen itself is landscape — the block lies along the card, so it
   takes the card's own width and height instead of swapping them. */
.barcode-turned.across { height:calc(100cqh - 24px); width:calc(100cqw - 24px); }
.barcode-turned svg { display:block; flex:1; min-height:0; width:100%; }
.barcode-turned .barcode-text { font-size:1.3rem; margin:0; }
.barcode-turned small { color:var(--muted); font-size:.78rem; }
.barcode-text { color:var(--ink); font-family:'Space Grotesk', sans-serif; font-size:1.02rem; font-variant-numeric:tabular-nums; letter-spacing:.12em; margin:8px 0 0; overflow-wrap:anywhere; text-align:center; }
.balance-line { align-items:baseline; display:flex; flex-wrap:wrap; gap:4px 12px; justify-content:space-between; margin-top:18px; }
.balance-big { color:var(--green); display:block; font-family:'Space Grotesk', sans-serif; font-size:clamp(2rem, 9vw, 2.7rem); font-variant-numeric:tabular-nums; font-weight:700; line-height:1.05; }
.balance-big.empty { color:var(--muted); }
.balance-note { color:var(--muted); font-size:.8rem; }
.balance-note.unchecked { color:var(--amber); }
.active-actions { display:flex; gap:9px; margin-top:16px; }
.active-actions > * { flex:1; }
.receipt-note { background:var(--paper); border:1px solid var(--line); border-radius:8px; display:flex; flex-direction:column; gap:2px; margin:4px 0 12px; padding:10px 12px; }
.receipt-note small { color:var(--muted); font-size:.8rem; }
.hint.alert { background:var(--alert-soft); color:var(--alert); }
.pay-plan { font-size:.88rem; line-height:1.5; list-style:none; margin:12px 0 0; padding:0; }
.pay-plan li { align-items:center; border-bottom:1px solid var(--line); display:flex; gap:8px; padding:6px 0; }
.hint { background:var(--amber-soft); border-radius:8px; color:#7a4a0e; font-size:.84rem; line-height:1.45; margin:14px 0 0; padding:10px 12px; }
.queue-note { color:var(--muted); font-size:.82rem; margin:14px 2px 0; text-align:center; }
/* The Active tab fits the screen — card, slider and the SPAR buttons at the bottom — and is never
   scrolled: at the till a slider half under the tab bar, or a SPAR Code button that has to be scrolled
   to, is a fumble with a queue behind it. Three things on it matter at the till and the rest does not:
   the barcode, the archive slider and the SPAR buttons. So the tab gives way in that order. Everything
   else is drawn at --s times its size and goes first; then it is taken off the tab altogether, least
   needed first (.fit-hidden); only then do the slider and the SPAR buttons shrink, at --p; and the
   barcode gives up height last of all. fitActive() in the script measures its way through those
   steps, and stops at the first that fits. Neither scale goes above 1: on a tall phone the space is
   simply left free, between the card and the SPAR buttons. The barcode keeps the full width and a
   height of its own, since a scanner needs bars, not margins, and horizontal padding stays put so its
   width does not depend on how short the screen is. Apart from all that, --t is for width: every
   label on the tab's buttons is drawn at --t times its size, one factor for all of them, so that when
   one would not fit its button on a narrow screen they all shrink together and stay the same size
   relative to each other. The SPAR row's own rules sit with it further down. */
#view-active { display:flex; flex-direction:column; }
#view-active .fit-hidden { display:none; }
#view-active .offline-note { font-size:calc(.82rem * var(--s, 1)); margin-bottom:calc(12px * var(--s, 1)); padding:calc(8px * var(--s, 1)) 12px; }
#view-active .archived-notice { gap:calc(12px * var(--s, 1)); margin-bottom:calc(14px * var(--s, 1)); padding:calc(12px * var(--s, 1)) 14px; }
#view-active .archived-notice .tick { height:calc(32px * var(--s, 1)); width:calc(32px * var(--s, 1)); }
#view-active .archived-notice strong { font-size:calc(.95rem * var(--s, 1)); }
#view-active .archived-notice small { font-size:calc(.8rem * var(--s, 1)); }
#view-active .archived-notice .small-button { font-size:calc(.8rem * var(--s, 1)); padding:calc(7px * var(--s, 1)) 11px; }
#view-active .active-head { padding:calc(12px * var(--s, 1)) 16px; }
#view-active .active-head .kicker { font-size:calc(.68rem * var(--s, 1)); }
#view-active .active-head strong { font-size:calc(1.05rem * var(--s, 1)); }
#view-active .active-body { padding:calc(18px * var(--s, 1)) 16px calc(16px * var(--s, 1)); }
#view-active .barcode { padding:calc(14px * var(--s, 1)) 0 calc(6px * var(--s, 1)); }
#view-active .barcode svg { height:var(--barcode-h, clamp(96px, 30vw, 150px)); }
#view-active .barcode-text { font-size:calc(1.02rem * var(--s, 1)); margin-top:calc(8px * var(--s, 1)); }
#view-active .balance-line { margin-top:calc(18px * var(--s, 1)); row-gap:calc(4px * var(--s, 1)); }
#view-active .figure-label { font-size:calc(.68rem * var(--s, 1)); }
#view-active .balance-big { font-size:calc(clamp(2rem, 9vw, 2.7rem) * var(--s, 1)); }
#view-active .balance-note { font-size:calc(.8rem * var(--s, 1)); }
#view-active .hint { font-size:calc(.84rem * var(--s, 1)); margin-top:calc(14px * var(--s, 1)); padding:calc(10px * var(--s, 1)) 12px; }
#view-active .active-actions { margin-top:calc(16px * var(--s, 1)); }
#view-active .active-actions > * { min-width:0; }
#view-active .active-actions .ghost-button { font-size:calc(1rem * var(--s, 1) * var(--t, 1)); overflow:hidden; padding:calc(9px * var(--s, 1)) 14px; text-overflow:ellipsis; white-space:nowrap; }
#view-active .slide-track { height:calc(56px * var(--p, 1)); margin-top:calc(14px * var(--p, 1)); }
#view-active .slide-fill { width:calc(56px * var(--p, 1)); }
#view-active .slide-thumb { font-size:calc(1.3rem * var(--p, 1)); height:calc(48px * var(--p, 1)); top:calc(4px * var(--p, 1)); width:calc(48px * var(--p, 1)); }
#view-active .slide-track.confirmed .slide-thumb { font-size:0; }
#view-active .slide-track.confirmed .slide-thumb:after { font-size:calc(1.3rem * var(--p, 1)); }
/* The label keeps clear of the thumb at either end, so what fitLabels() measures as its room is room
   the thumb does not cover. */
#view-active .slide-label { font-size:calc(.86rem * var(--p, 1) * var(--t, 1)); padding:0 calc(12px * var(--p, 1)) 0 calc(56px * var(--p, 1)); white-space:nowrap; }
#view-active .slide-track.confirmed .slide-label { padding:0 calc(56px * var(--p, 1)) 0 calc(12px * var(--p, 1)); }
#view-active .queue-note { font-size:calc(.82rem * var(--s, 1)); margin-top:calc(14px * var(--s, 1)); }

/* ---- lists of vouchers ---- */
.toolbar { align-items:center; display:flex; flex-wrap:wrap; gap:8px; justify-content:space-between; margin-bottom:12px; }
.toolbar small { color:var(--muted); font-size:.8rem; }
.voucher-list { display:flex; flex-direction:column; gap:8px; }
/* The row is the target: tapping a voucher opens it. The stripe on its left says at a glance which is
   the active one and which have nothing left. */
.voucher { align-items:center; background:var(--card); border:1px solid var(--line); border-left:4px solid var(--line); border-radius:9px; color:var(--ink); display:flex; gap:12px; padding:12px 13px; text-align:left; width:100%; }
.voucher.active { border-left-color:var(--red); }
.voucher.spent { opacity:.7; }
.voucher-copy { display:flex; flex:1; flex-direction:column; gap:4px; min-width:0; }
.voucher-copy strong { font-family:'Space Grotesk', sans-serif; font-size:.98rem; font-variant-numeric:tabular-nums; letter-spacing:.06em; }
.voucher-copy small { color:var(--muted); font-size:.76rem; }
.voucher-side { align-items:flex-end; display:flex; flex:none; flex-direction:column; gap:3px; }
.voucher-side .amount { color:var(--green); font-size:1.02rem; }
.voucher-side .amount.empty { color:var(--muted); }
.voucher-side small { color:var(--muted); font-size:.72rem; }
.voucher-side small.unchecked { color:var(--amber); }
.pill-row { display:flex; flex-wrap:wrap; gap:5px; }
.pill { background:var(--green-soft); border-radius:999px; color:var(--green); display:inline-block; font-size:.64rem; font-weight:700; letter-spacing:.05em; padding:3px 8px; text-transform:uppercase; white-space:nowrap; }
.pill.active { background:var(--red-soft); color:var(--red); }
.pill.muted { background:var(--paper); border:1px solid var(--line); color:var(--muted); padding:2px 7px; }
.add-tile { align-items:center; background:transparent; border:1.5px dashed var(--line); border-radius:9px; color:var(--red); display:flex; font-weight:700; gap:8px; justify-content:center; margin-top:10px; padding:14px; width:100%; }

/* ---- the voucher dialog ---- */
.modal.voucher-modal { max-width:520px; }
.secret-grid { display:grid; gap:10px; grid-template-columns:1fr auto; margin:16px 0 4px; }
.secret { background:var(--card); border:1px solid var(--line); border-radius:8px; padding:9px 12px; }
.secret small { color:var(--muted); display:block; font-size:.66rem; font-weight:700; letter-spacing:.1em; text-transform:uppercase; }
.secret span { display:block; font-family:'Space Grotesk', sans-serif; font-size:1.02rem; font-variant-numeric:tabular-nums; letter-spacing:.08em; margin-top:2px; overflow-wrap:anywhere; }
.secret button { background:none; border:0; color:var(--red); font-size:.74rem; font-weight:700; padding:0; }
.balance-box { align-items:center; background:var(--card); border:1px solid var(--line); border-radius:8px; display:flex; gap:10px; justify-content:space-between; margin-top:10px; padding:10px 12px; }
.balance-box .amount { color:var(--green); display:block; font-size:1.3rem; }
.balance-box .amount.empty { color:var(--muted); }
.balance-box small { color:var(--muted); display:block; font-size:.74rem; }
.balance-box small.unchecked { color:var(--amber); }
.balance-edit { display:flex; gap:8px; margin-top:10px; }
.balance-edit input { background:var(--card); border:1px solid var(--line); border-radius:5px; color:var(--ink); flex:1; font-size:1.05rem; min-width:0; padding:10px 11px; }
.small-button { background:var(--card); border:1px solid var(--line); border-radius:5px; color:var(--ink); flex:none; font-size:.8rem; font-weight:600; padding:7px 11px; white-space:nowrap; }
.small-button.red { border-color:var(--red); color:var(--red); }
a.small-button { text-decoration:none; }
/* The SPAR Code button and, beside it, one to the receipts in SPAR's app. The row is the last thing
   on the Active tab, under the card, so the code is reachable the moment it is wanted, at the till. It
   used to be fixed above the tab bar as app chrome, which floated it over the card and kept it out of
   the fitting above; in the flow it takes its share of the screen and scales with the rest. Two thirds
   to one third, and solid red against plain white, because the code is what
   the till needs and the receipts are for afterwards — except in German, where the row is split in
   halves, since "Rechnungen" next to its icon does not fit a third of a phone. Each carries the icon of what it opens and
   nothing behind its label: a faint barcode and receipt lines were tried there and only made the two
   look busy. */
/* margin-top:auto is what puts the row at the very bottom of the tab, which fitActive() makes exactly
   as tall as the space above the tab bar; the padding is the least it keeps from the card. */
.spar-bar { display:flex; gap:8px; margin-top:auto; padding-top:calc(14px * var(--p, 1)); }
.spar-bar[hidden] { display:none; }
.spar-button { align-items:center; background:var(--card); border:1px solid var(--line); border-radius:12px;
  box-shadow:0 4px 16px rgba(36,32,31,.10); color:var(--ink); display:flex; flex:1 1 0; font-size:calc(.92rem * var(--p, 1) * var(--t, 1));
  font-weight:700; gap:8px; justify-content:center; min-height:calc(50px * var(--p, 1)); min-width:0; padding:0 10px;
  text-decoration:none; transition:transform .1s; }
.spar-button.primary { background:var(--red); border-color:var(--red); box-shadow:0 4px 16px rgba(228,6,19,.24);
  color:#fff; flex:0 0 calc(65% - 4px); font-size:calc(1rem * var(--p, 1) * var(--t, 1)); letter-spacing:.01em; }
:lang(de) .spar-button.primary { flex-basis:calc(50% - 4px); }
.spar-button:active { transform:scale(.97); }
.spar-button svg { flex:none; height:calc(20px * var(--p, 1)); width:calc(20px * var(--p, 1)); }
.spar-button:not(.primary) svg { color:var(--red); }
.spar-button span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.upload-results { display:flex; flex-direction:column; gap:6px; margin:4px 0 0; }
.upload-results div { background:var(--card); border:1px solid var(--line); border-left:4px solid var(--green); border-radius:7px; font-size:.84rem; padding:8px 11px; }
.upload-results div.duplicate { border-left-color:var(--amber); }
.upload-results div.unreadable { border-left-color:var(--alert); }
.upload-results small { color:var(--muted); display:block; font-size:.74rem; margin-top:2px; overflow-wrap:anywhere; }
.bookmarklet { background:var(--red); border-radius:6px; color:#fff; display:inline-block; font-weight:700; padding:9px 14px; text-decoration:none; }
.code-box { background:var(--card); border:1px solid var(--line); border-radius:6px; color:var(--muted); font-family:ui-monospace, monospace; font-size:.68rem; line-height:1.4; margin:10px 0 0; max-height:90px; overflow:auto; overflow-wrap:anywhere; padding:8px 10px; user-select:all; -webkit-user-select:all; }
.steps { color:var(--ink); font-size:.88rem; line-height:1.5; margin:0 0 14px; padding-left:20px; }
.steps li { margin:4px 0; }

/* ---- slide to archive ----
   A track the width of the card with a round thumb at its left end. The red fill follows the thumb,
   and the label fades as it goes, so how far is left is visible without reading anything. Only a
   release at the far end counts; anywhere short and the thumb springs back. touch-action:none keeps
   the drag from being taken for a scroll. */
.slide-track { background:var(--red-soft); border-radius:999px; height:56px; margin-top:14px; overflow:hidden; position:relative; touch-action:none; user-select:none; -webkit-user-select:none; }
.slide-track:focus-visible { outline:2px solid var(--red); outline-offset:3px; }
.slide-fill { background:var(--red); border-radius:999px; bottom:0; left:0; position:absolute; top:0; width:56px; }
.slide-label { align-items:center; color:var(--red); display:flex; font-size:.86rem; font-weight:700; inset:0; justify-content:center; letter-spacing:.04em; opacity:calc(1 - var(--slid, 0) * 1.4); padding-left:40px; pointer-events:none; position:absolute; }
.slide-thumb { align-items:center; background:#fff; border-radius:50%; box-shadow:0 2px 8px rgba(120,20,24,.28); color:var(--red); cursor:grab; display:flex; font-size:1.3rem; font-weight:700; height:48px; justify-content:center; left:4px; position:absolute; top:4px; touch-action:none; width:48px; }
.slide-track.dragging .slide-thumb { cursor:grabbing; }
.slide-track.springing .slide-thumb { transition:transform .25s cubic-bezier(.2,.9,.3,1.2); }
.slide-track.springing .slide-fill { transition:width .25s cubic-bezier(.2,.9,.3,1.2); }
.slide-track.confirmed .slide-label { color:#fff; opacity:1; padding-left:0; padding-right:44px; }
.slide-track.confirmed .slide-thumb:after { content:'✓'; }
.slide-track.confirmed .slide-thumb { font-size:0; }
.slide-track.confirmed .slide-thumb:after { font-size:1.3rem; }

/* ---- the archived stamp and notice ----
   When the slide completes, a stamp comes down across the card that is still showing — so there is
   no doubt which one went — and the card greys under it. Then the next voucher takes its place, with
   the notice above it saying what happened and offering to undo it. */
.active-card { position:relative; }
.archived-stamp { align-items:center; display:flex; inset:0; justify-content:center; opacity:0; pointer-events:none; position:absolute; }
.archived-stamp span { border:4px solid var(--red); border-radius:10px; color:var(--red); font-family:'Space Grotesk', sans-serif; font-size:clamp(1.8rem, 9vw, 2.6rem); font-weight:700; letter-spacing:.14em; padding:6px 18px; text-transform:uppercase; transform:rotate(-12deg) scale(2.2); }
.active-card.stamped .active-body { filter:grayscale(1); opacity:.35; transition:opacity .35s, filter .35s; }
.active-card.stamped .archived-stamp { animation:stamp-fade .2s ease-out forwards; }
.active-card.stamped .archived-stamp span { animation:stamp-down .32s cubic-bezier(.2,1.4,.4,1) forwards; background:rgba(255,255,255,.85); }
@keyframes stamp-fade { to { opacity:1; } }
@keyframes stamp-down { to { transform:rotate(-12deg) scale(1); } }
.archived-notice { align-items:center; animation:notice-in .3s ease-out; background:var(--green-soft); border:1px solid #b7dfc6; border-radius:12px; display:flex; gap:12px; margin-bottom:14px; padding:12px 14px; }
.archived-notice .tick { align-items:center; background:var(--green); border-radius:50%; color:#fff; display:flex; flex:none; font-weight:700; height:32px; justify-content:center; width:32px; }
.archived-notice div { display:flex; flex:1; flex-direction:column; gap:2px; min-width:0; }
.archived-notice strong { font-size:.95rem; }
.archived-notice small { color:#2f5c41; font-size:.8rem; }
@keyframes notice-in { from { opacity:0; transform:translateY(-8px); } to { opacity:1; transform:none; } }
@media (prefers-reduced-motion: reduce) {
  .active-card.stamped .archived-stamp,.active-card.stamped .archived-stamp span,.archived-notice { animation:none; opacity:1; }
  .archived-stamp span { transform:rotate(-12deg); }
  .slide-track.springing .slide-thumb,.slide-track.springing .slide-fill { transition:none; }
}
