@font-face{font-family:"Plus Jakarta Sans";src:url("fonts/PlusJakartaSans-Regular.ttf") format("truetype");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Plus Jakarta Sans";src:url("fonts/PlusJakartaSans-SemiBold.ttf") format("truetype");font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"Plus Jakarta Sans";src:url("fonts/PlusJakartaSans-Bold.ttf") format("truetype");font-weight:700;font-style:normal;font-display:swap}
/* Type scale (UI-RULES): 32 / 28 / 16 / 14 px — nothing outside it. */
:root{--fs-title:2rem;--fs-metric:1.75rem;--fs-body:1rem;--fs-detail:.875rem;--ink:#1b1f24;--muted:#5f6f85;--line:#d8dee6;--accent:#4338ca;--bg:#f6f7f9;--warn-bg:#fef3c7;--info-bg:#e0f2fe;--info-border:#bae6fd;
/* Kiosk (dark booth) palette: the one surface that inverts the light app.
   These are the de-facto values the kiosk block used as literals; naming them
   lets the booth be retuned from one place instead of N literals. */
--kiosk-bg:#0f172a;--kiosk-surface:#1e293b;--kiosk-ink:#f8fafc;--kiosk-ink-strong:#e2e8f0;--kiosk-muted:#94a3b8;--kiosk-on-accent:#fff;--kiosk-accent:#4338ca;--kiosk-accent-soft:#c7d2fe;--kiosk-line:rgba(148,163,184,.22);--kiosk-line-soft:rgba(148,163,184,.18);--kiosk-line-strong:rgba(148,163,184,.3);--kiosk-tint-faint:rgba(148,163,184,.08);--kiosk-tint:rgba(148,163,184,.12);--kiosk-tint-strong:rgba(148,163,184,.18);--kiosk-guide:rgba(129,140,248,.55);--kiosk-scrim:rgba(2,6,23,.8);--kiosk-shadow:rgba(2,6,23,.5);--kiosk-warn-bg:rgba(217,119,6,.18);--kiosk-warn:#fcd34d;--kiosk-error:#fca5a5;--kiosk-lilac:rgba(196,181,253,.2);--kiosk-lilac-soft:rgba(196,181,253,.18);--kiosk-danger:#dc2626;--kiosk-accent-deep:#312e81;--kiosk-on-accent-soft:rgba(255,255,255,.85);--kiosk-tier-ink:#e9d5ff;--kiosk-face-ink:#1b1f24}
*{box-sizing:border-box}
body{font-family:"Plus Jakarta Sans",system-ui,-apple-system,"Segoe UI",sans-serif;margin:0;background:var(--bg);color:var(--ink);line-height:1.45}
/* Browser surfaces are themed from the palette too — text selection, the
   caret, scrollbars, and the underline offset ship with no design otherwise. */
::selection{background:#c7d2fe;color:var(--ink)}
a{text-underline-offset:.15em}
input,select,textarea{caret-color:var(--accent)}
html{scrollbar-color:#cbd5e1 var(--bg)}
header.nav{background:#0f172a;color:#fff;padding:.6rem 1rem;display:flex;align-items:center;flex-wrap:wrap;gap:.5rem 1rem;position:sticky;top:0;z-index:45}
header.nav .brand{font-weight:700;color:#fff;text-decoration:none;font-size:1rem;display:inline-flex;align-items:center;min-height:2.75rem;padding:.2rem 0}
header.nav nav{display:flex;align-items:center;flex-wrap:wrap;gap:.25rem 1rem;margin-left:auto}
header.nav a{color:#dbeafe;text-decoration:none}
header.nav a.active{text-decoration:underline;font-weight:600}
/* Nav/language links stay visually compact but reach a 44px tap height. */
header.nav nav>a,header.nav .lang-switch a,header.nav .nav-main a{display:inline-flex;align-items:center;min-height:2.75rem;padding:.1rem .25rem}
header.nav details.menu summary{display:inline-flex;align-items:center;min-height:2.75rem}
.lang-switch a{margin-left:.4rem}
/* The primary links are one group, so the bar can wrap as a whole instead of
   spilling a single link onto a second line. */
header.nav .nav-main{display:flex;align-items:center;flex-wrap:wrap;gap:.25rem 1rem}
/* On a phone the bar must stay one row. The primary links already live in
   the bottom tab bar, so the header keeps only the brand, the credit chip and
   the menu. Duplicating them up here is what forced the row to scroll and
   clipped "Pool" down to a single "P". */
@media (max-width:640px){
  header.nav{padding:.45rem .6rem;gap:.35rem .5rem;flex-wrap:nowrap}
  header.nav .brand{flex:0 0 auto}
  header.nav nav{margin-left:auto;flex-wrap:nowrap;gap:.25rem .4rem;min-width:0}
  header.nav .nav-main{display:none}
  /* Only when the tab bar exists. An anonymous visitor has no tab bar, so
     their header keeps its links — hiding them would strand them. Those
     links also must not be clipped: a sideways-scrolling row cut "Peringkat"
     down to "Pering" at the container edge with nothing to indicate the rest
     existed. For a visitor the bar is allowed to take two rows instead, which
     hides nothing and costs nothing on a page they are reading, not operating. */
  header.nav:not(.has-user){flex-wrap:wrap}
  header.nav:not(.has-user) nav{flex-wrap:wrap;overflow:visible}
  header.nav:not(.has-user) .nav-main{display:flex;flex-wrap:wrap;overflow:visible;gap:.1rem .55rem;min-width:0}
  header.nav:not(.has-user) .nav-main a,
  header.nav:not(.has-user) nav > a,
  header.nav:not(.has-user) .lang-switch{flex:0 0 auto;white-space:nowrap}
  /* A guest has no menu to hold the locale, so the bar itself scrolls. */
  header.nav nav{overflow-x:auto;scrollbar-width:none}
  header.nav nav::-webkit-scrollbar{display:none}
  header.nav nav>a,header.nav .lang-switch a{white-space:nowrap;padding:.1rem .15rem}
  header.nav .credit-chip{white-space:nowrap}
}
/* Language options inside the menu: a row of small buttons. */
header.nav details.menu .menu-lang{display:flex;align-items:center;gap:.3rem;padding:.35rem .7rem}
header.nav details.menu .menu-lang a{padding:.3rem .55rem;border-radius:8px;border:1px solid var(--line);font-weight:600}
header.nav details.menu .menu-lang a.active{background:var(--indigo-wash);border-color:var(--indigo-soft)}
main{max-width:960px;margin:1rem auto;padding:0 1rem}
.card{background:var(--surface);border:1px solid var(--line);border-radius:8px;padding:1rem;margin:1rem 0}
.card.narrow{max-width:440px;margin:2rem auto}
h1{font-size:2rem;margin:.4rem 0}
h2{font-size:1.75rem;margin:.6rem 0}
table{border-collapse:collapse;width:100%;background:var(--surface);margin:.5rem 0}
th,td{border:1px solid var(--line);padding:.4rem .5rem;text-align:left;font-size:1rem;vertical-align:top}
/* UI-RULES: numeric values right-aligned, tabular figures for scanning. */
th.num,td.num{text-align:right;font-variant-numeric:tabular-nums}
th.num{padding-right:.6rem}
/* UI-RULES #18: zebra striping lifts row scanability on light tables. The
   dark kiosk rate table opts out (a light stripe reads as noise there). */
tbody tr:nth-child(even) td{background:var(--stripe)}
.kiosk-rates tbody tr:nth-child(even) td{background:transparent}
.table-wrap{overflow-x:auto}
.flashes{list-style:none;padding:0;max-width:960px;margin:.5rem auto}
.flashes li{padding:.5rem .8rem;border-radius:6px;margin:.3rem 0}
.flashes .info{background:var(--info-bg)}
.flashes .warn{background:var(--warn-bg)}
form.inline{display:inline}
button,.button{padding:.45rem .8rem;border-radius:6px;border:1px solid #0f172a;background:#0f172a;color:#fff;cursor:pointer;text-decoration:none;display:inline-block;font-size:1rem}
button.link{background:none;border:none;color:#dbeafe;padding:0;text-decoration:underline;cursor:pointer;font-size:1rem}
input,select{padding:.45rem;border:1px solid var(--line-strong);border-radius:6px;max-width:100%;width:100%;background:var(--surface);color:var(--ink-strong)}
textarea{width:100%;padding:.45rem;border:1px solid var(--line-strong);border-radius:6px;font:inherit;max-width:100%;background:var(--surface);color:var(--ink-strong)}
label{font-size:1rem}
.muted{color:var(--muted);font-size:.875rem}
footer{max-width:960px;margin:2rem auto 1rem;padding:0 1rem}
hr{border:none;border-top:1px solid var(--line);margin:1rem 0}
@media (max-width:480px){header.nav{gap:.4rem .75rem}main{padding:0 .75rem}}

/* M1: pools & items editor */
.badge{display:inline-block;font-size:.875rem;padding:.1rem .5rem;border-radius:999px;vertical-align:middle;font-weight:600}
.badge.draft{background:#e2e8f0;color:#334155}
.badge.published{background:#dcfce7;color:#166534}
/* Plan status on a public profile (v25): a quiet chip beside the name, so it
   reads as a label the account earned rather than a call to action. */
.badge.tier-badge{background:#ede9fe;color:#5b21b6;font-size:.75rem;vertical-align:baseline}
.crumbs{margin:.6rem 0 0}
/* UI-RULES #22: the key identity column stays visible while a wide table
   scrolls horizontally (name/email/time first column). Solid backgrounds
   keep the row content from showing through; the kiosk's dark tables opt
   out because they never scroll sideways and their palette is inverted. */
.table-wrap table th:first-child,.table-wrap table td:first-child{position:sticky;left:0;z-index:1;background:var(--surface)}
.table-wrap table th:first-child{z-index:2}
.table-wrap table tbody tr:nth-child(even) td:first-child{background:var(--stripe)}
.kiosk-rates .table-wrap table th:first-child,.kiosk-rates .table-wrap table td:first-child{position:static;background:transparent}
/* The global sticky-column zebra rule outranks the reset above by specificity,
   leaving even-row item names light-on-light. Match its full path so the kiosk
   keeps a transparent first cell on every row. */
.kiosk-rates .table-wrap table tbody tr:nth-child(even) td:first-child{background:transparent}

/* UI-RULES #20: record count left, page controls right. */
.pagination{display:flex;align-items:center;justify-content:space-between;gap:.6rem;flex-wrap:wrap;margin:.6rem 0}
.pagination-count{font-size:.875rem}
.pagination-controls{display:flex;align-items:center;gap:.6rem;margin-left:auto}
.pagination-page{font-size:.875rem;color:var(--muted)}
.pagination .button{padding:.35rem .7rem;font-size:.875rem;min-height:2.2rem}

/* UI-RULES #4/#13: a visible label above the field, fields grouped before
   results, with an explicit reset. */
.search-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:.6rem}
.search-row .field{display:flex;flex-direction:column;gap:.2rem;min-width:0}
.search-row .field>label{font-size:.875rem;font-weight:600}
.search-row .field>input{min-width:clamp(12rem,40vw,20rem)}
.search-row .field-hint{color:var(--muted);font-size:.875rem}
.search-row .search-actions{display:flex;align-items:center;gap:.5rem}

.actions{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.75rem}
.actions form{display:inline}
button.secondary{background:var(--surface);color:var(--ink-strong)}
button.danger{background:#b91c1c;border-color:#b91c1c}
.item-list{display:flex;flex-direction:column;gap:.75rem}
.item-card{border:1px solid var(--line);border-radius:8px;padding:.75rem;background:var(--surface)}
.item-main{display:flex;gap:.75rem;align-items:flex-start}
.item-thumb{width:64px;height:64px;flex:0 0 64px;border:1px solid var(--line);border-radius:6px;display:flex;align-items:center;justify-content:center;overflow:hidden;background:var(--surface-sunken)}
.item-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.item-thumb span{font-size:.875rem;color:var(--muted);text-align:center;padding:0 .2rem}
.item-info{min-width:0;flex:1}
.item-info .name{font-weight:600;overflow-wrap:anywhere}
.tier{display:inline-block;font-size:.875rem;padding:.05rem .45rem;border-radius:4px;background:var(--indigo-wash);color:var(--indigo-deep);margin-left:.25rem;font-weight:600}
.item-edit{margin-top:.6rem}
.item-edit summary{cursor:pointer;color:var(--accent);font-size:.875rem}
/* Advanced settings folded into a native disclosure. A booth seller edits
   items, weights and stock; the commit-reveal seeds and the last-prize ladder
   belong behind a summary so they are available without being in the way. */
.card.advanced{padding:0}
.card.advanced > summary{cursor:pointer;padding:1rem;display:flex;flex-wrap:wrap;gap:.35rem .6rem;align-items:baseline}
.card.advanced > summary .summary-title{font-weight:700;font-size:1.05rem}
.card.advanced > summary .summary-hint{font-size:.85rem}
.card.advanced[open] > summary{border-bottom:1px solid var(--line)}
.card.advanced .details-body{padding:0 1rem 1rem}
.card.advanced .advanced-block{padding-top:.9rem}
.card.advanced .advanced-block + .advanced-block{border-top:1px dashed var(--line);margin-top:.9rem}
.card.advanced .advanced-block h3{margin:.2rem 0 .5rem}
.item-edit form{margin-top:.5rem;border-top:1px dashed var(--line);padding-top:.5rem}
.item-edit form + form{margin-top:.4rem}
@media (min-width:700px){
  .item-list{gap:.9rem}
  .item-thumb{width:84px;height:84px;flex-basis:84px}
}

/* M2: draws, receipts, roll log */
.confirm-block{border:3px solid var(--danger-ink);background:var(--danger-wash);border-radius:8px;padding:1rem;margin:1rem 0}
html:not(.js) .confirm-block{display:block}
.confirm-heading{color:var(--danger-ink);font-weight:800;font-size:1.75rem;margin:0 0 .4rem}
.confirm-text{font-weight:700;font-size:1rem;margin:.4rem 0}
.confirm-check{display:block;margin-top:.6rem;font-weight:600}
.count-row{margin:.8rem 0}
.count-row input{max-width:8rem}
.draw-list{display:flex;flex-direction:column;gap:.75rem}
.draw-card{border:1px solid var(--line);border-radius:8px;padding:.75rem;background:var(--surface)}
.draw-main{display:flex;gap:.75rem;align-items:flex-start}
.draw-info{min-width:0;flex:1}
.receipt-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.875rem;color:var(--muted);overflow-wrap:anywhere}
.badge.batch{background:#e2e8f0;color:#334155;margin-left:.35rem}
.display-mode{position:fixed;inset:0;background:rgba(2,6,23,.97);color:#f8fafc;z-index:50;display:flex;align-items:center;justify-content:center;padding:1rem;scrollbar-color:#475569 rgba(2,6,23,.97)}
.display-mode[hidden]{display:none}
.display-inner{max-width:min(92vw,720px);width:100%;text-align:center;max-height:100dvh;overflow-y:auto}
.display-stage{display:flex;flex-direction:column;gap:clamp(.6rem,2vmin,1.2rem);align-items:center;justify-content:center;min-height:40dvh}
.display-card{opacity:0;transform:scale(.9);transition:opacity .45s ease,transform .45s ease;padding:clamp(.5rem,2vmin,1rem);width:100%;max-width:min(92vw,520px)}
.display-card.revealed{opacity:1;transform:scale(1)}
.display-img{max-width:min(72vw,min(52dvh,420px));max-height:min(46dvh,420px);border-radius:10px;display:block;margin:0 auto .6rem}
.display-name{font-size:clamp(1.75rem,5vw,2rem);font-weight:800;overflow-wrap:anywhere}
.display-tier{font-size:1rem;color:#c4b5fd;margin-top:.2rem}
.display-receipt{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.875rem;color:#94a3b8;margin-top:.5rem;overflow-wrap:anywhere}
.display-summary{background:rgba(148,163,184,.12);border-radius:10px;padding:1rem;margin-top:1rem;text-align:left}
.display-summary h2{margin-top:0}
.display-summary ul{margin:.4rem 0;padding-left:1.2rem}
.display-controls{display:flex;gap:.6rem;justify-content:center;margin-top:1.2rem;flex-wrap:wrap}
@media (prefers-reduced-motion: reduce){.display-card{transition:none}}

/* M3: credits, top-ups, admin console */
.balance-row{margin:.4rem 0}
.balance-row strong{font-size:1rem}
.preset-row{display:flex;flex-wrap:wrap;gap:.5rem;margin:.6rem 0}
.qr-box{margin:1rem 0;text-align:center}
.qr-box img{border:1px solid var(--line);border-radius:8px;background:var(--surface);padding:.5rem}
.qris-brand{margin:.6rem 0 .2rem}
.qr-box img.qris-logo{border:0;border-radius:0;background:none;padding:0;height:1.6rem;width:auto;display:inline-block}
.pay-amount{font-size:1.75rem}
.paid-note{background:#dcfce7;border-radius:6px;padding:.5rem .8rem;color:#166534;font-weight:600}
.badge.open{background:#e0f2fe;color:#075985}
.badge.paid{background:#dcfce7;color:#166534}
.badge.expired{background:#e2e8f0;color:#334155}
.badge.cancelled{background:var(--surface-sunken);color:var(--muted)}
.badge.received{background:#e0f2fe;color:#075985}
.badge.unreadable{background:var(--warn-bg);color:var(--warn-ink)}
.badge.unmatched{background:var(--warn-bg);color:var(--warn-ink)}
.badge.ambiguous{background:var(--warn-bg);color:var(--warn-ink)}
.badge.matched{background:#dcfce7;color:#166534}
.badge.dismissed{background:#e2e8f0;color:#334155}
.badge.error{background:var(--danger-wash);color:var(--danger-ink)}
.card.alert{border-color:var(--danger-line);background:var(--danger-wash)}
.float-line{font-weight:600}
.float-line.over{color:var(--danger-ink)}
.settings-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:.6rem .9rem}
/* A grid item defaults to min-width:auto, so a long label (a settings key
   spelled out, for instance) refuses to shrink and paints over the column
   beside it. Zeroing the minimum and wrapping the text keeps every cell
   inside its own track. */
.settings-grid > label{min-width:0;overflow-wrap:anywhere}
.settings-grid input{width:100%;box-sizing:border-box}
.attach-form{display:flex;flex-wrap:wrap;gap:.4rem;align-items:center}
.attach-form select{width:auto;min-width:12rem}
.adjust-form{display:flex;flex-wrap:wrap;gap:.4rem;align-items:center}
.adjust-form input[name="amount"]{width:6.5rem}
.adjust-form input[name="reason"]{width:11rem}
.detail-cell{max-width:22rem;overflow-wrap:anywhere;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.875rem}

/* M3b: referrals */
.referral-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:1rem;background:var(--indigo-wash);color:var(--indigo-deep);padding:.15rem .5rem;border-radius:6px}
.share-link{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.875rem;overflow-wrap:anywhere}
.counts-row{display:flex;flex-wrap:wrap;gap:.4rem}
.button.secondary{background:var(--surface);color:var(--ink-strong)}
.badge.joined{background:#e0f2fe;color:#075985}
.badge.pending{background:var(--warn-bg);color:var(--warn-ink)}
.badge.approved{background:#dcfce7;color:#166534}
.badge.rejected{background:var(--danger-wash);color:var(--danger-ink)}
.badge.ineligible{background:#e2e8f0;color:#334155}

/* M4: storefront (public pages, share blocks, QR thumbnails) */
.qr-thumb{width:140px;height:140px;border:1px solid var(--line);border-radius:8px;background:var(--surface);padding:.35rem;display:inline-block}
.qr-thumb-sm{width:64px;height:64px;border:1px solid var(--line);border-radius:6px;background:var(--surface);padding:.2rem;display:inline-block;vertical-align:middle}
.pool-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:.75rem}
.pool-card{border:1px solid var(--line);border-radius:8px;padding:.75rem;background:var(--surface)}
.pool-card h3{margin:.2rem 0;font-size:1rem;overflow-wrap:anywhere}
.share-block{border-top:1px dashed var(--line);padding-top:.6rem;margin-top:.6rem}
.share-block:first-of-type{border-top:none;padding-top:0;margin-top:0}
.share-cell{max-width:16rem}

/* M5: calculator, compliance pages, reports */
.footer-links{display:block;margin-top:.2rem}
.footer-links a{color:var(--muted)}
.legal-links a{color:var(--muted)}
.badge.actioned{background:#dcfce7;color:#166534}
.warnings{list-style:none;padding:0}
.warnings li{border-radius:6px;padding:.4rem .7rem;margin:.3rem 0}
.warnings .warn{background:var(--warn-bg)}
td.negative{color:var(--danger-ink);font-weight:600}
.calc-row{display:flex;gap:.5rem}
.calc-row input{width:auto;flex:1}

/* M6: pull reveal animations — card flip / wheel spin / reel drop.
   Scoped to classes draw.js adds, so the plain (off) reveal stays untouched. */
.anim-card .display-card{opacity:1;transform:none;perspective:900px}
.anim-card .display-card.active{cursor:pointer}
.anim-card .display-card:focus{outline:2px solid rgba(129,140,248,.8);outline-offset:4px}
.anim-card .card-inner{transition:transform .25s ease;transform:rotateY(0deg)}
.anim-card .display-card.flipping .card-inner{transform:rotateY(90deg)}
.anim-card .display-card.flipping-back .card-inner{transform:rotateY(-90deg);transition:none}
.anim-card .card-back{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.6rem;min-height:clamp(10rem,32dvh,20rem);border:1px solid rgba(148,163,184,.4);border-radius:12px;background:linear-gradient(160deg,#1e293b,#0f172a)}
.anim-card .card-mark{font-size:clamp(2rem,7vmin,3.4rem);font-weight:800;color:#64748b;line-height:1}
.anim-card .card-hint{font-size:.875rem;color:#94a3b8}
.anim-card .display-card:not(.active) .card-hint{visibility:hidden}
.anim-card .card-front{display:none}
.anim-card .display-card.face-front .card-back{display:none}
.anim-card .display-card.face-front .card-front{display:block}
.anim-card .display-card.active .card-inner{box-shadow:0 0 0 2px rgba(129,140,248,.65);border-radius:12px}
.anim-card .display-card.revealed .display-tier{animation:tier-glow 1.6s ease 1}
@keyframes tier-glow{0%{text-shadow:none}45%{text-shadow:0 0 18px rgba(196,181,253,.95)}100%{text-shadow:0 0 6px rgba(196,181,253,.35)}}

/* Dice template: a die tumbles over the card back while the pull resolves.
   Purely decorative — the result still comes from the server, so the face
   shows "?" rather than a number that would imply local randomness. */
.dice-cube{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;z-index:3}
.dice-face{display:flex;align-items:center;justify-content:center;width:clamp(4rem,14vmin,6rem);height:clamp(4rem,14vmin,6rem);border-radius:14px;border:2px solid rgba(148,163,184,.55);background:linear-gradient(150deg,#f8fafc,#cbd5e1);color:var(--ink-strong);font-size:clamp(1.6rem,6vmin,2.4rem);font-weight:800;line-height:1;box-shadow:0 10px 28px rgba(2,6,23,.45)}
.dice-roll .card-inner{opacity:.25}
.dice-roll .dice-face{animation:dice-tumble 900ms cubic-bezier(.34,1.36,.64,1) 1}
@keyframes dice-tumble{
  0%{transform:translateY(-18%) rotate(-24deg) scale(.86)}
  35%{transform:translateY(8%) rotate(16deg) scale(1.04)}
  60%{transform:translateY(-4%) rotate(-8deg) scale(.98)}
  80%{transform:translateY(2%) rotate(4deg) scale(1.01)}
  100%{transform:translateY(0) rotate(0) scale(1)}
}
@media (prefers-reduced-motion:reduce){.dice-roll .dice-face{animation:none}}
/* A two-sided pool is a coin, not a die: round, gold, and it spins on the
   Y axis so the flip reads as a coin toss. */
.dice-coin .dice-face{border-radius:50%;border-color:rgba(180,131,24,.75);background:radial-gradient(circle at 32% 28%,#fde68a,#f59e0b 62%,#b45309);color:#451a03;font-size:clamp(1.2rem,4.4vmin,1.8rem)}
.dice-roll.dice-roll .dice-coin .dice-face{animation:dice-coin-flip 900ms cubic-bezier(.34,1.36,.64,1) 1}
@keyframes dice-coin-flip{
  0%{transform:translateY(-14%) rotateY(0) scale(.9)}
  40%{transform:translateY(6%) rotateY(540deg) scale(1.05)}
  70%{transform:translateY(-3%) rotateY(900deg) scale(.99)}
  100%{transform:translateY(0) rotateY(1080deg) scale(1)}
}

/* Session roll reveal: the table page plays a fullscreen result instead of
   reloading straight into the new numbers, so a roll reads like a pull. */
.session-reveal-who{font-weight:600;letter-spacing:.02em;margin:0 0 .6rem;color:#cbd5e1}
.session-reveal .display-stage{position:relative}
.session-reveal .dice-cube{position:static;margin-top:.8rem}
.session-reveal .muted{color:#94a3b8}

.anim-wheel,.anim-reel{position:relative;overflow:hidden;width:100%;border-radius:12px;border:1px solid rgba(148,163,184,.35);background:rgba(15,23,42,.65)}
.anim-spin .display-card:not(.revealed){display:none}
.anim-wheel{height:clamp(120px,24dvh,200px)}
.anim-reel{height:clamp(200px,50dvh,380px)}
.anim-wheel::before{content:"";position:absolute;top:0;bottom:0;left:50%;width:2px;margin-left:-1px;background:rgba(129,140,248,.55);z-index:2;pointer-events:none}
.anim-reel::before{content:"";position:absolute;left:0;right:0;top:50%;height:2px;margin-top:-1px;background:rgba(129,140,248,.55);z-index:2;pointer-events:none}
.anim-wheel-strip{display:flex;will-change:transform}
.anim-reel-strip{display:flex;flex-direction:column;will-change:transform}
.anim-cell{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.35rem;padding:.4rem;overflow:hidden}
.anim-wheel .anim-cell{width:clamp(104px,15vmin,150px);height:100%;border-right:1px solid rgba(148,163,184,.18)}
.anim-reel .anim-cell{height:clamp(72px,12dvh,104px);flex-direction:row;gap:.6rem;border-bottom:1px solid rgba(148,163,184,.18)}
.anim-cell img{width:clamp(40px,7vmin,56px);height:clamp(40px,7vmin,56px);object-fit:cover;border-radius:6px;flex:0 0 auto}
.anim-cell-name{font-size:clamp(.72rem,1.5vmin,.9rem);overflow-wrap:anywhere;text-align:center}
.anim-wheel.spinning .anim-wheel-strip{filter:none}
.anim-reel.spinning .anim-reel-strip{filter:none}
.anim-cell.winner{background:rgba(196,181,253,.16)}
.anim-wheel.landed .anim-cell.winner,.anim-reel.landed .anim-cell.winner{animation:winner-pulse .7s ease 1}
@keyframes winner-pulse{0%{transform:scale(1)}45%{transform:scale(1.06);box-shadow:0 0 22px rgba(196,181,253,.85)}100%{transform:scale(1)}}
.anim-reel.top-tier .anim-cell.winner{animation:winner-pulse .7s ease 1,tier-sparkle 1.4s ease .7s 2}
@keyframes tier-sparkle{0%,100%{box-shadow:0 0 10px rgba(196,181,253,.5)}50%{box-shadow:0 0 30px rgba(233,213,255,1)}}
@media (prefers-reduced-motion: reduce){
  .anim-card .card-inner,.anim-wheel-strip,.anim-reel-strip{transition:none}
  .anim-cell.winner{animation:none}
}

/* v9: nested pools — item badges, chain walks, editor hints */
.badge.nested{background:var(--warn-bg);color:var(--warn-ink)}
.chain-walk{display:flex;flex-wrap:wrap;gap:.35rem;align-items:center;margin:.5rem 0}
.chain-step{display:inline-flex;align-items:baseline;gap:.35rem;border:1px solid var(--line);border-radius:6px;padding:.15rem .5rem;background:var(--surface)}
.chain-step .chain-stage{font-size:.875rem;color:var(--muted);font-weight:600}
.chain-step.chain-leaf{border-color:var(--indigo-soft);background:var(--indigo-wash);box-shadow:0 0 0 2px rgba(167,139,250,.25)}
.chain-arrow{color:var(--muted)}
.chain-mini{font-size:.875rem}
.nested-pick select.warn{border-color:#d97706;background:var(--warn-bg)}

/* v9: chain reveals — stage cards show one at a time (draw.js adds
   .anim-chain to the overlay for chained pulls; .anim-card rides along for
   the card-flip stages, so the M6 card styles above apply unchanged) */
.anim-chain .display-card{display:none}
.anim-chain .display-card.active,.anim-chain .display-card.revealed{display:block}

/* Landing revamp: hero, sample strip, playable demo, transparency.
   All art is CSS-only; motion respects prefers-reduced-motion. */
.hero{display:flex;flex-direction:column;gap:1.2rem;margin:1rem 0}
.hero h1{font-size:2rem;margin:.2rem 0}
.hero-copy p{margin:.5rem 0}
.fair-play{display:inline-block;background:var(--bg);border:1px solid var(--line);color:var(--muted);border-radius:999px;padding:.25rem .7rem;font-weight:600;font-size:.875rem}
.hero-art{position:relative;height:210px;overflow:visible}
.hero-card{position:absolute;width:40%;max-width:170px;border:1px solid var(--line);border-radius:12px;background:var(--surface);padding:.7rem;box-shadow:0 1px 2px rgba(15,23,42,.06);display:flex;flex-direction:column;gap:.35rem}
.hero-card-img{width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:8px;display:block}
.hero-card-name{font-weight:600;font-size:.875rem;overflow-wrap:anywhere}
.hero-reassure{color:var(--muted);font-size:.875rem;margin:.6rem 0 0}
.notice-strip{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem;background:var(--info-bg);border:1px solid var(--info-border);border-radius:6px;padding:.5rem .8rem;margin:.6rem 0}
.notice-strip strong{font-size:.875rem;color:var(--ink)}
.notice-strip .prose{flex:1 1 14rem;min-width:0;font-size:.875rem;color:var(--muted)}
.notice-strip .prose p{margin:0}
.receipt-preview{margin-top:1rem;padding-top:.8rem;border-top:1px solid var(--line)}
.hero-card-1{left:1%;top:8px;transform:rotate(-5deg);animation:hero-float 5.5s ease-in-out infinite}
.hero-card-2{left:30%;top:46px;transform:rotate(2deg);animation:hero-float 6.5s ease-in-out .6s infinite}
.hero-card-3{left:58%;top:6px;transform:rotate(6deg);animation:hero-float 6s ease-in-out 1.1s infinite}
@keyframes hero-float{0%,100%{translate:0 0}50%{translate:0 -10px}}
@media (min-width:700px){.hero{flex-direction:row;align-items:center;gap:2rem}.hero-copy{flex:1}.hero-art{flex:0 0 44%;height:230px}}
.showcase-strip{display:grid;grid-template-columns:repeat(2,1fr);gap:.7rem;margin-top:.6rem}
.showcase-card{border:1px solid var(--line);border-radius:10px;background:var(--surface);padding:.6rem;display:flex;flex-direction:column;gap:.35rem;align-items:flex-start}
.showcase-art{width:100%;aspect-ratio:4/3;border-radius:8px;display:block;object-fit:cover}
.showcase-name{font-weight:600;font-size:.875rem}
@media (min-width:700px){.showcase-strip{grid-template-columns:repeat(4,1fr)}}
/* Landing leaderboard: most-active sellers per window + newest sellers. Public
   name + avatar only; a seller appears only with a published pool. */
.leaderboard-grid{display:grid;grid-template-columns:1fr;gap:.8rem;margin-top:.6rem}
@media (min-width:700px){.leaderboard-grid{grid-template-columns:repeat(3,1fr)}}
.leaderboard-col{border:1px solid var(--line);border-radius:10px;background:var(--surface);padding:.7rem}
.leaderboard-col h3{margin:0 0 .4rem;font-size:.95rem}
.leaderboard-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.4rem}
.leaderboard-row{display:flex;align-items:center;gap:.5rem}
.leaderboard-rank{flex:0 0 1.4rem;font-weight:700;color:var(--muted);text-align:center}
.leaderboard-avatar{width:28px;height:28px;border-radius:999px;object-fit:cover;border:1px solid var(--line)}
.leaderboard-name{font-weight:600;flex:1;overflow-wrap:anywhere}
.leaderboard-count{color:var(--muted);font-size:.8rem;white-space:nowrap}
.leaderboard-recent-heading{margin:.9rem 0 .2rem;font-size:.95rem}
.leaderboard-recent{flex-direction:row;flex-wrap:wrap;gap:.6rem}
/* Tier chips are deliberately one neutral style. They used to have a rarity
   ladder (C < UC < R < SR < SSR < UR < LR) with its own colours, which put a
   gold chip next to "5%" and an indigo chip next to "30%" on the same table —
   so a buyer read the colour as rarer than the number. A tier is a label, not
   a probability; one quiet style keeps the label visible and the number
   authoritative. The code (C/UC/R/SR/SSR/UR/LR) is always spelled out, so the
   label still carries its meaning without implying a ranking by hue. */

/* P1: tier is a radio set now — touch-friendly, no free-text keyboard */
.tier-options{display:flex;flex-wrap:wrap;gap:.4rem .9rem}
.tier-opt{display:inline-flex;align-items:center;gap:.3rem;font-size:.875rem;white-space:nowrap}
.tier-opt input{width:auto;max-width:none;margin:0}
label .label,.label{font-weight:600}
.badge.demo{background:var(--surface-sunken);color:var(--ink);border:1px solid var(--line)}
.admin-tabs{display:flex;flex-wrap:wrap;gap:.3rem;margin:.6rem 0 1rem;border-bottom:1px solid var(--line)}
.admin-tabs a{padding:.4rem .8rem;min-height:2.75rem;display:inline-flex;align-items:center;border-radius:8px 8px 0 0;color:var(--muted);text-decoration:none;font-weight:600}
.admin-tabs a.active{background:var(--surface);color:var(--ink);border:1px solid var(--line);border-bottom-color:#fff;margin-bottom:-1px}
@media (max-width:640px){
  /* One scrollable tab row reads as a single control; a ragged wrap reads
     as unrelated links. */
  .admin-tabs{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:thin}
  .admin-tabs a{flex:0 0 auto;white-space:nowrap}
}
.badge.kiosk-flag{background:#e0f2fe;color:#075985;border:1px solid #bae6fd}
.actions-cell{display:flex;flex-wrap:wrap;gap:.35rem}
/* Pools list: the primary action (Open) leads; the rest sit in a quiet
   secondary cluster so a seller presenting the list sees the one button that
   matters first. */
.row-actions{display:inline-flex;flex-wrap:wrap;align-items:center;gap:.6rem;margin-left:.35rem}
.row-actions .link{color:var(--muted);font-size:.875rem;font-weight:600;text-decoration:none;white-space:nowrap}
.row-actions .link:hover{color:var(--accent);text-decoration:underline}
.server-message{background:var(--info-bg);border:1px solid var(--info-border);border-radius:6px;padding:.5rem .8rem;margin:.3rem 0}
.demo-buttons{display:flex;flex-wrap:wrap;gap:.5rem;margin:.6rem 0}
.algo-steps{margin:.6rem 0;padding-left:1.2rem;display:flex;flex-direction:column;gap:.35rem}
.algo-notes{margin:.4rem 0;padding-left:1.2rem;color:var(--muted);font-size:.875rem}
.algo-notes li{margin:.25rem 0}
.algo-source{margin:.6rem 0 0;font-size:.875rem}
.docs-links a{color:var(--accent)}
.demo-banner{font-size:.875rem;color:#94a3b8;margin:0 0 .6rem}
@media (prefers-reduced-motion: reduce){
  .hero-card{animation:none}
}

/* Import/export: error lists and the template grid. */
.template-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:.6rem}
.template-card{border:1px solid var(--line);border-radius:8px;padding:.6rem}
.template-card .name{font-weight:600}
.import-errors{background:var(--danger-wash);border:1px solid var(--danger-line);border-radius:6px;padding:.5rem .7rem;margin:.5rem 0}
.import-errors ul{margin:.3rem 0 0;padding-left:1.2rem}

/* M6: brand mark, and the long-form prose (legal pages, user guide). */
header.nav .brand{display:inline-flex;align-items:center;gap:.45rem}
header.nav .brand .brand-mark{height:1.5rem;width:auto;display:block}
.prose p{margin:.55rem 0}
.prose p:first-child{margin-top:0}
.prose p:last-child{margin-bottom:0}
.prose ul,.prose ol{margin:.55rem 0;padding-left:1.25rem}
.prose li{margin:.15rem 0}
.prose code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.875em;background:var(--indigo-wash);color:var(--indigo-deep);padding:.05rem .3rem;border-radius:4px}
.contact-actions{display:flex;flex-wrap:wrap;gap:.6rem;margin:.9rem 0 .1rem}
.contact-actions .contact-btn{flex:1 1 auto;justify-content:center;text-align:center}

/* Menu tidy-up: credit chip top-right + dropdown (Profile/Credits/Referrals/Help). */
header.nav .credit-chip{color:#fde68a;font-weight:700;white-space:nowrap}
header.nav details.menu{position:relative}
header.nav details.menu summary{cursor:pointer;list-style:none;color:#dbeafe;font-weight:600;padding:.15rem .4rem;border-radius:6px}
header.nav details.menu summary::-webkit-details-marker{display:none}
header.nav details.menu[open] summary{background:rgba(255,255,255,.12)}
header.nav details.menu .menu-panel{position:absolute;right:0;top:calc(100% + .4rem);background:var(--surface);color:var(--ink);border:1px solid var(--line);border-radius:10px;box-shadow:0 12px 30px rgba(15,23,42,.18);min-width:12rem;padding:.4rem;display:flex;flex-direction:column;gap:.1rem;z-index:60}
header.nav details.menu .menu-panel a,header.nav details.menu .menu-panel button{padding:.5rem .7rem;border-radius:8px;color:var(--ink);text-decoration:none;text-align:left;font:inherit;background:none;border:0;cursor:pointer}
header.nav details.menu .menu-panel a:hover,header.nav details.menu .menu-panel button:hover{background:var(--indigo-wash)}
header.nav details.menu .menu-panel form{margin:0}

/* Sample-image gallery. */
.sample-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.7rem;margin:.6rem 0}
.sample-card{margin:0;border:1px solid var(--line);border-radius:10px;background:var(--surface);padding:.5rem;display:flex;flex-direction:column;gap:.45rem}
.sample-card img{width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:8px;display:block}
.sample-card figcaption{display:flex;align-items:center;justify-content:space-between;gap:.4rem;flex-wrap:wrap}
.sample-card .name{font-weight:600;font-size:.875rem}

/* Calculator: manual rows. */
.calc-row{display:flex;gap:.4rem;align-items:center;flex-wrap:wrap}
.calc-row input{flex:1;min-width:8rem}
.calc-row-remove{padding:.35rem .6rem;font-weight:700}

/* Kiosk page: a clean booth display that fits the device screen.
   The shell is a fixed-height grid in dynamic viewport units (dvh), so it
   tracks the real browser viewport on phones and tablets alike; the stage
   takes the leftover row and every element scales from vmin/dvh so nothing
   overflows and nothing is stranded in a corner. */
body.kiosk-body{background:var(--kiosk-bg);color:var(--kiosk-ink);min-height:100dvh;overflow:hidden}
body.kiosk-body .kiosk{height:100dvh;max-width:none;margin:0;padding:clamp(.5rem,1.6vmin,1.1rem) clamp(.6rem,2vmin,1.4rem);display:grid;grid-template-rows:auto 1fr auto auto;gap:clamp(.4rem,1.4vmin,.9rem)}
/* The global main{max-width:960px;margin:1rem auto} rule would shrink and
   centre the kiosk shell (auto margins make a grid item content-sized), so
   the kiosk main opts out and stretches to the viewport. */
body.kiosk-body main.kiosk-main{max-width:none;margin:0;padding:0;width:100%}
.kiosk-head{display:flex;align-items:center;gap:clamp(.3rem,1.2vmin,.7rem);flex-wrap:wrap;position:relative}
.kiosk-head .kiosk-brand{display:inline-flex;align-items:center;gap:.35rem;flex:0 0 auto}
.kiosk-head .kiosk-brand-mark{height:clamp(1.2rem,3vmin,1.7rem);width:auto;display:block}
.kiosk-head .kiosk-brand-name{font-weight:800;font-size:clamp(.85rem,2vmin,1.15rem);color:var(--kiosk-ink-strong)}
.kiosk-head .kiosk-pro{margin-left:.1rem;font-size:clamp(.62rem,1.4vmin,.78rem);padding:.1rem .4rem}
.kiosk-head .kiosk-pool{font-weight:800;font-size:clamp(.95rem,2.4vmin,1.35rem);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.kiosk-head .kiosk-cost{color:var(--kiosk-muted);font-size:clamp(.72rem,1.5vmin,.9rem);white-space:nowrap}
.kiosk-head .kiosk-head-actions{margin-left:auto;display:flex;align-items:center;gap:clamp(.3rem,1.2vmin,.6rem);flex:0 0 auto;flex-wrap:nowrap}
.kiosk-head .kiosk-head-actions>*{flex:0 0 auto}
/* One control height across the header (UI-RULES: buttons >= 44px). */
.kiosk-head .kiosk-step,.kiosk-head .kiosk-speed select,.kiosk-head .kiosk-exit{min-height:2.75rem;height:clamp(2.75rem,7vmin,3.4rem)}
.kiosk-head .kiosk-exit{color:var(--kiosk-accent-soft);text-decoration:none;font-size:clamp(.78rem,1.6vmin,.95rem);white-space:nowrap;display:inline-flex;align-items:center;gap:.3rem}
.kiosk-head .kiosk-exit .kiosk-exit-icon{width:1em;height:1em;display:block;flex:0 0 auto}
.kiosk-head .kiosk-speed{display:inline-flex;align-items:center;gap:.3rem}
.kiosk-head .kiosk-speed .kiosk-speed-icon{width:1em;height:1em;display:block;flex:0 0 auto;opacity:.85}
.kiosk-head .kiosk-speed .kiosk-speed-text{font-size:clamp(.72rem,1.7vmin,.9rem);font-weight:700;color:var(--kiosk-muted);white-space:nowrap}
.kiosk-head .kiosk-speed select{appearance:none;-webkit-appearance:none;background:var(--kiosk-tint-strong);color:var(--kiosk-ink);border:0;border-radius:clamp(10px,2.2vmin,14px);font-weight:700;font-size:clamp(.9rem,2vmin,1.1rem);padding:0 1.4rem 0 .5rem;cursor:pointer;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23cbd5e1' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right .35rem center;background-size:1em}
.kiosk-credit-warn{position:absolute;left:50%;top:calc(100% + .35rem);transform:translateX(-50%);z-index:5;padding:.3rem clamp(.5rem,1.6vmin,.85rem);border-radius:999px;background:var(--kiosk-warn-bg);color:var(--kiosk-warn);font-size:clamp(.72rem,1.6vmin,.88rem);font-weight:600;white-space:nowrap;max-width:min(92vw,560px);overflow:hidden;text-overflow:ellipsis;box-shadow:0 6px 18px var(--kiosk-shadow);pointer-events:none}
.kiosk-head .kiosk-rates-toggle{display:inline-flex;align-items:center;gap:.35rem;width:auto;min-width:0;padding:.35rem clamp(.5rem,1.6vmin,.8rem);font-size:clamp(.78rem,1.7vmin,.95rem);border-radius:clamp(8px,1.8vmin,12px)}
.kiosk-head .kiosk-rates-toggle .kiosk-eye{font-size:clamp(.95rem,2vmin,1.15rem);display:inline-flex;align-items:center}
.kiosk-head .kiosk-rates-toggle .kiosk-eye svg{width:1em;height:1em;display:block}
.kiosk-head .kiosk-rates-toggle .kiosk-rates-label{font-weight:700}
.kiosk-head .kiosk-rates-toggle.on{background:var(--kiosk-accent);color:var(--kiosk-on-accent)}
.visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.kiosk-main{min-height:0;display:grid;grid-template-rows:1fr auto;gap:clamp(.4rem,1.4vmin,.9rem)}
.kiosk-stage{min-height:0;display:flex;align-items:center;justify-content:center;background:var(--kiosk-tint-faint);border:1px solid var(--kiosk-line);border-radius:clamp(12px,2.4vmin,20px);overflow:hidden;position:relative}
.kiosk-idle{color:var(--kiosk-muted);font-size:clamp(.9rem,2vmin,1.15rem);padding:1rem;text-align:center}
/* Idle panel: a 1080p stage left empty reads as a dead screen from three
   metres away. It carries the pool's real shape instead. clearStage() wipes
   this on the first pull, so nothing has to hide it later. */
.kiosk-idle-panel{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:clamp(.35rem,1.2vmin,.7rem);padding:clamp(1rem,4vmin,2.5rem);max-width:min(92%,70rem);max-height:100%;overflow-y:auto;text-align:center}
.kiosk-idle-title{font-size:clamp(1.3rem,4.2vmin,2.6rem);font-weight:800;line-height:1.15;letter-spacing:-.015em;color:var(--kiosk-ink-strong);margin:0;overflow-wrap:anywhere}
.kiosk-idle-panel .kiosk-idle{padding:0;font-size:clamp(.9rem,2.1vmin,1.25rem)}
.kiosk-idle-meta{font-size:clamp(.75rem,1.7vmin,.95rem);color:var(--kiosk-muted);margin:0;font-variant-numeric:tabular-nums}
.kiosk-idle-chips{display:flex;flex-wrap:wrap;justify-content:center;gap:clamp(.3rem,1vmin,.55rem);margin-top:clamp(.4rem,1.4vmin,.9rem)}
.kiosk-idle-chip{display:inline-flex;align-items:center;gap:.35rem;padding:clamp(.25rem,.8vmin,.45rem) clamp(.5rem,1.6vmin,.85rem);border-radius:999px;background:var(--kiosk-tint);border:1px solid var(--kiosk-line);color:var(--kiosk-ink);font-size:clamp(.7rem,1.6vmin,.9rem);font-weight:600;max-width:22rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.kiosk-idle-chip.more{color:var(--kiosk-muted);font-weight:700}
.kiosk-idle-chip .tier{background:var(--kiosk-lilac);color:var(--kiosk-tier-ink)}
/* A spent, expired or revoked token still renders — the holder deserves the
   reason. It had no styling at all, so the block fell back to the light card
   on a dark booth page and read as a different site. */
.kiosk-main .token-blocked{margin:auto;max-width:min(92vw,34rem);padding:clamp(1.2rem,3.5vmin,2.2rem);background:var(--kiosk-surface);border:1px solid var(--kiosk-line-strong);border-radius:clamp(12px,2.4vmin,20px);text-align:center;box-shadow:0 10px 30px var(--kiosk-shadow)}
.kiosk-main .token-blocked h1{font-size:clamp(1.15rem,3.2vmin,1.8rem);line-height:1.25;letter-spacing:-.01em;margin:0 0 .6rem;color:var(--kiosk-ink-strong)}
.kiosk-main .token-blocked p{margin:0 0 .5rem;font-size:clamp(.85rem,2vmin,1.05rem);line-height:1.55;color:var(--kiosk-ink)}
.kiosk-main .token-blocked p.muted{color:var(--kiosk-muted);margin-bottom:0}
/* Narrow screens: the header cluster is nowrap by design (one control height,
   nothing wrapping mid-toolbar on a booth). On a phone that same rule pushed
   the whole shell 35px sideways — the stage and the control row inherited the
   overflow. Below 640px the cluster is allowed to wrap instead. */
@media (max-width:640px){
  .kiosk-head .kiosk-head-actions{flex-wrap:wrap;justify-content:flex-end;max-width:100%}
  .kiosk-head .kiosk-pool{white-space:normal;overflow:visible;text-overflow:clip}
  .kiosk-head .kiosk-speed .kiosk-speed-text{display:none}
  /* The stage is short here (the rates table takes half the screen) and the
     idle panel is centred, so a panel taller than the stage lost its title
     off the top edge. Tighter type and gaps keep it inside without a scroll. */
  .kiosk-idle-panel{padding:clamp(.6rem,2.5vmin,1.2rem);gap:clamp(.2rem,.8vmin,.4rem)}
  .kiosk-idle-title{font-size:clamp(1.05rem,4.6vmin,1.6rem)}
  .kiosk-idle-chips{gap:.25rem;margin-top:.25rem}
  .kiosk-idle-chip{font-size:.7rem;padding:.2rem .5rem;max-width:15rem}
}

/* The batch anticipation beat: a big count that scales in, then settles,
   so the crowd reads "how many are coming" before the first reveal. */
.kiosk-anticipate{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.4rem;text-align:center}
.kiosk-anticipate-count{font-size:clamp(3.5rem,16vmin,7rem);font-weight:800;line-height:1;color:var(--kiosk-ink);opacity:0;transform:scale(.7);transition:opacity .45s ease-out,transform .45s cubic-bezier(.13,.75,.15,1)}
.kiosk-anticipate.in .kiosk-anticipate-count{opacity:1;transform:scale(1)}
.kiosk-anticipate-label{color:var(--kiosk-muted);font-size:clamp(.95rem,2.4vmin,1.3rem);font-weight:600;margin:0;opacity:0;transition:opacity .45s ease-out .12s}
.kiosk-anticipate.in .kiosk-anticipate-label{opacity:1}
.kiosk-status{color:var(--kiosk-error);text-align:center;font-size:clamp(.85rem,1.8vmin,1.05rem)}
.kiosk-summary{text-align:right}
.kiosk-results-pane .kiosk-summary{width:100%}
.kiosk-remaining{color:var(--kiosk-muted);font-size:clamp(.8rem,1.8vmin,1rem);font-weight:600;margin:0;white-space:nowrap}
.kiosk-remaining[hidden]{display:none}
.kiosk-results{display:flex;flex-wrap:wrap;gap:.5rem;justify-content:center;max-height:22dvh;overflow-y:auto}
.kiosk-mini{background:var(--kiosk-tint);border-radius:10px;padding:.4rem;display:flex;flex-direction:column;align-items:center;gap:.25rem;max-width:clamp(72px,13vmin,120px)}
.kiosk-mini img{width:clamp(40px,8vmin,64px);height:clamp(40px,8vmin,64px);object-fit:cover;border-radius:8px}
.kiosk-mini span{font-size:clamp(.68rem,1.5vmin,.82rem);text-align:center;overflow-wrap:anywhere}
.kiosk-controls{display:grid;grid-template-columns:1fr auto 1fr;grid-template-areas:"results start ." "results batch .";gap:clamp(.4rem,1.4vmin,.8rem) clamp(.5rem,2vmin,1.1rem);align-items:center;justify-items:center;width:100%}
.kiosk-results-pane{grid-area:results;min-width:0;align-self:stretch;display:flex;justify-content:flex-end}
.kiosk-start{grid-area:start;justify-self:center}
.kiosk-batch{grid-area:batch;min-width:0;display:flex;align-items:center;justify-content:center;gap:clamp(.4rem,1.6vmin,.8rem);flex-wrap:wrap}
.kiosk-start{font-size:clamp(1.05rem,3vmin,1.5rem);font-weight:800;padding:clamp(.8rem,2.4vmin,1.05rem) clamp(1.5rem,5vmin,2.6rem);min-height:2.75rem;border-radius:clamp(12px,2.6vmin,18px);background:var(--kiosk-accent);color:var(--kiosk-on-accent);border:0;cursor:pointer}
.kiosk-start:disabled{opacity:.5;cursor:default}
.kiosk-stepper{display:flex;align-items:center;gap:clamp(.5rem,2.2vmin,1rem);background:var(--kiosk-tint);border:1px solid var(--kiosk-line-strong);border-radius:clamp(8px,1.8vmin,12px);padding:.3rem .45rem}
.kiosk-step{font-size:clamp(1.2rem,3vmin,1.7rem);font-weight:800;width:clamp(2.75rem,8vmin,3.6rem);height:clamp(2.75rem,8vmin,3.6rem);min-width:2.75rem;min-height:2.75rem;border-radius:clamp(10px,2.2vmin,14px);border:0;background:var(--kiosk-tint-strong);color:var(--kiosk-ink);cursor:pointer}
/* The −/+ stepper buttons stay own-size: smaller than the header chrome (the
   DESIGN.md exception lets kiosk chrome relax the 44px floor), but scoped here
   so the shared .kiosk-step class does not shrink the header controls. */
.kiosk-stepper .kiosk-step{position:relative;font-size:clamp(.82rem,2vmin,1.1rem);width:clamp(1.1rem,3.2vmin,1.44rem);height:clamp(1.1rem,3.2vmin,1.44rem);min-width:1.1rem;min-height:1.1rem;border-radius:clamp(6px,1.4vmin,9px)}
/* Keep the small visible mark but restore a >=44px tappable area for a
   customer holding the device, via an invisible centred hit target. */
.kiosk-stepper .kiosk-step::after{content:"";position:absolute;left:50%;top:50%;width:2.75rem;height:2.75rem;transform:translate(-50%,-50%)}
/* Kiosk header icons: authored SVG in one stroke style — no emoji or bare
   unicode glyphs. The fullscreen button swaps expand/collapse by state class. */
.kiosk-head .kiosk-step svg{width:1.15em;height:1.15em;display:block}
.kiosk-head .kiosk-step .kiosk-icon-collapse{display:none}
.kiosk-head .kiosk-step.is-fullscreen .kiosk-icon-expand{display:none}
.kiosk-head .kiosk-step.is-fullscreen .kiosk-icon-collapse{display:block}
.kiosk-step:disabled{opacity:.35;cursor:default}
.kiosk-count{font-size:clamp(.95rem,2.2vmin,1.25rem);font-weight:800;min-width:clamp(1.4rem,4vmin,2.1rem);text-align:center}
.kiosk-rates{background:var(--kiosk-tint-faint);border:1px solid var(--kiosk-line);border-radius:clamp(12px,2.4vmin,16px);padding:clamp(.5rem,1.6vmin,.9rem) clamp(.6rem,2vmin,1.1rem);max-height:34dvh;overflow-y:auto}
.kiosk-rates h2{margin:.1rem 0 .45rem;font-size:clamp(.9rem,1.9vmin,1.1rem)}
.kiosk-rates table{width:100%}
.kiosk-rates th,.kiosk-rates td{padding:.3rem .4rem;border-bottom:1px solid var(--kiosk-line-soft);text-align:left}
.kiosk-footer{text-align:center;color:var(--kiosk-muted);font-size:clamp(.75rem,1.6vmin,.95rem)}
.kiosk-confirm{position:fixed;inset:0;background:var(--kiosk-scrim);display:flex;align-items:center;justify-content:center;z-index:80;padding:1rem}
.kiosk-confirm-box{background:var(--kiosk-surface);border:1px solid var(--kiosk-line-strong);border-radius:16px;padding:clamp(1rem,3vmin,1.5rem) clamp(1.1rem,3.4vmin,1.8rem);max-width:min(420px,88vw);text-align:center}
.kiosk-confirm-box p{font-size:clamp(1rem,2.6vmin,1.25rem);font-weight:600;margin:.2rem 0 .8rem}
.kiosk-confirm-actions{display:flex;gap:.6rem;justify-content:center;flex-wrap:wrap}
.kiosk-confirm-actions button{font-size:clamp(.95rem,2.2vmin,1.1rem);padding:clamp(.55rem,1.8vmin,.75rem) clamp(.9rem,3vmin,1.3rem);border-radius:12px;border:0;cursor:pointer}
.kiosk-confirm-actions .danger{background:var(--kiosk-danger);color:var(--kiosk-on-accent)}
.kiosk-confirm-actions .secondary{background:var(--kiosk-tint-strong);color:var(--kiosk-ink)}

/* Kiosk stage: card flip. The card is sized from the stage, scaled to 2×
   the non-kiosk card (batch 5) and still proportional in both orientations. */
.kiosk-card{width:min(92%,80dvh,760px);perspective:1200px}
.kiosk-card-inner{position:relative;width:100%;aspect-ratio:4/3;transition:transform .25s ease;transform:rotateY(0deg)}
/* Batch 5: the kiosk flip is the non-kiosk two-half flip (was a single 180° turn). */
.kiosk-card.flipping .kiosk-card-inner{transform:rotateY(90deg)}
.kiosk-card.flipping-back .kiosk-card-inner{transform:rotateY(-90deg);transition:none}
.kiosk-face{position:absolute;inset:0;border-radius:clamp(12px,2.6vmin,18px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:clamp(.5rem,2vmin,1rem)}
.kiosk-face.back{background:linear-gradient(150deg,var(--kiosk-accent),var(--kiosk-accent-deep));font-size:clamp(2.4rem,10vmin,4.4rem);font-weight:800;color:var(--kiosk-on-accent-soft)}
.kiosk-face.front{background:var(--kiosk-on-accent);color:var(--kiosk-face-ink);display:none}
.kiosk-card.face-front .kiosk-face.back{display:none}
.kiosk-card.face-front .kiosk-face.front{display:flex}
.kiosk-face.front img{max-width:78%;max-height:62%;border-radius:10px}
.kiosk-name{font-weight:800;font-size:clamp(.9rem,2.4vmin,1.2rem);text-align:center;overflow-wrap:anywhere}

/* Kiosk stage: wheel/reel strips. Both fill the stage and scale with it;
   cells are 2× the non-kiosk sizes (batch 5), and the centre guide line
   matches the non-kiosk strip (it had been lost in the kiosk port). */
.kiosk-wheel{position:relative;width:100%;height:100%;overflow:hidden;display:flex;align-items:center}
.kiosk-reel{position:relative;width:100%;height:100%;overflow:hidden;display:flex;flex-direction:column;justify-content:center}
.kiosk-wheel::before{content:"";position:absolute;top:0;bottom:0;left:50%;width:2px;margin-left:-1px;background:var(--kiosk-guide);z-index:2;pointer-events:none}
.kiosk-reel::before{content:"";position:absolute;left:0;right:0;top:50%;height:2px;margin-top:-1px;background:var(--kiosk-guide);z-index:2;pointer-events:none}
.kiosk-wheel .kiosk-strip{display:flex;align-items:stretch}
.kiosk-reel .kiosk-strip{display:flex;flex-direction:column}
.kiosk-cell{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.35rem;padding:.4rem;overflow:hidden}
.kiosk-wheel .kiosk-cell{width:clamp(192px,34vmin,340px);height:100%;border-right:1px solid var(--kiosk-line-soft)}
.kiosk-reel .kiosk-cell{height:clamp(140px,26dvh,240px);flex-direction:row;gap:.6rem;border-bottom:1px solid var(--kiosk-line-soft)}
.kiosk-cell img{width:clamp(80px,16vmin,128px);height:clamp(80px,16vmin,128px);object-fit:cover;border-radius:8px;flex:0 0 auto}
.kiosk-cell-name{font-size:clamp(1.1rem,3.2vmin,1.7rem);text-align:center;overflow-wrap:anywhere}
.kiosk-cell.winner{background:var(--kiosk-lilac)}
/* Motion parity with the non-kiosk strip: blur while spinning, a winner
   pulse on landing, and the extra sparkle for a top tier on the reel.
   The strip is promoted to its own layer while it moves, so the blur is
   applied on the GPU instead of repainting the strip's cells each frame. */
.kiosk-wheel.spinning .kiosk-strip,.kiosk-reel.spinning .kiosk-strip{will-change:transform,filter}
.kiosk-wheel.spinning .kiosk-strip{filter:none}
.kiosk-reel.spinning .kiosk-strip{filter:none}
.kiosk-wheel.landed .kiosk-cell.winner,.kiosk-reel.landed .kiosk-cell.winner{animation:winner-pulse .7s ease 1}
.kiosk-reel.top-tier .kiosk-cell.winner{animation:winner-pulse .7s ease 1,tier-sparkle 1.4s ease .7s 2}

/* Kiosk pick cards: 2× the non-kiosk size (batch 5). */
.kiosk-body .pick-card{width:clamp(120px,16vmin,176px);height:clamp(174px,23.2vmin,256px);font-size:clamp(1.8rem,4.5vmin,3.4rem)}

/* Kiosk page: dark table + tier overrides (the global table style is light). */
.kiosk-rates table{background:transparent;color:var(--kiosk-ink-strong)}
.kiosk-rates th,.kiosk-rates td{border:0;border-bottom:1px solid var(--kiosk-line-soft);font-size:1rem}
/* "18 / 20" broke across two lines on a phone, which reads as two numbers
   and makes the column uncomparable down the list. */
.kiosk-rates th.num,.kiosk-rates td.num{white-space:nowrap;font-variant-numeric:tabular-nums}
.kiosk-rates .tier{background:var(--kiosk-lilac-soft);color:var(--kiosk-tier-ink);border-radius:999px;padding:.1rem .55rem;font-size:.875rem;font-weight:700}
.kiosk .muted{color:var(--kiosk-muted)}

/* [hidden] must win: the flex rule above would otherwise keep the confirm
   overlay (and any future flex panel) visible all the time. */
.kiosk-confirm[hidden]{display:none}

.kiosk-card{position:relative;z-index:1;transition:transform .28s ease}

/* A brief pressed state on the one-shot skip button while it is fast-forwarding. */
.kiosk-head .kiosk-step.on{background:var(--kiosk-accent);color:var(--kiosk-on-accent)}

/* P2: pick-a-card tray (the "pick" reveal — three face-down cards) */
.pick-tray{display:flex;flex-direction:column;align-items:center;gap:1rem;padding:1rem;width:100%}
.pick-prompt{font-weight:700;margin:0;font-size:clamp(1rem,2.6vmin,1.3rem);color:var(--kiosk-ink)}
.pick-row{display:flex;gap:1rem;flex-wrap:wrap;justify-content:center}
.pick-card{border:2px solid var(--kiosk-line);background:linear-gradient(160deg,var(--kiosk-surface),var(--kiosk-bg));color:var(--kiosk-ink-strong);font-weight:800;cursor:pointer;transition:transform .2s ease,opacity .3s ease,border-color .2s ease,box-shadow .2s ease}
.pick-card:hover,.pick-card:focus-visible{transform:translateY(-8px);border-color:var(--kiosk-accent-soft);box-shadow:0 10px 24px var(--kiosk-shadow)}
.pick-card.chosen{transform:translateY(-12px) scale(1.06);border-color:var(--kiosk-warn)}
.pick-card.discarded{opacity:.2;transform:scale(.9)}
.pick-mark{pointer-events:none}
.display-card,.kiosk-card,.kiosk-cell{position:relative}

/* P2: sparkle/confetti burst on a reveal, scaled by rarity (draw.js/kiosk.js) */
.confetti{position:absolute;left:50%;top:40%;width:0;height:0;pointer-events:none;z-index:5}
.confetti-bit{position:absolute;width:8px;height:8px;border-radius:2px;background:#f59e0b;animation:confetti-fly 1.1s ease-out forwards;animation-delay:var(--delay,0ms)}
.confetti-bit:nth-child(3n){background:#a855f7}
.confetti-bit:nth-child(3n+1){background:#22d3ee}
.confetti-bit:nth-child(3n+2){background:#f43f5e}
@keyframes confetti-fly{0%{transform:translate(0,0) rotate(0);opacity:1}100%{transform:translate(var(--dx,0),var(--dy,-80px)) rotate(var(--rot,180deg));opacity:0}}
@media (prefers-reduced-motion: reduce){.confetti{display:none}}

/* P2: bulk item table ("Save all"); rows become cards on narrow screens */
/* P3: import/export fold into <details> cards so the long editor page is shorter. */
.editor-fold summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:.4rem}
.editor-fold summary::-webkit-details-marker{display:none}
.editor-fold summary::before{content:"▸";color:var(--muted);transition:transform .15s ease}
.editor-fold[open] summary::before{transform:rotate(90deg)}
.editor-fold .editor-fold-title{font-weight:700;font-size:1.25rem;margin:0}

.item-table{width:100%;border-collapse:collapse}
.item-table th,.item-table td{padding:.45rem .5rem;border-bottom:1px solid var(--line);text-align:left;vertical-align:middle}
.item-table input,.item-table select{width:100%;max-width:100%}
.item-cell{display:flex;align-items:center;gap:.5rem}
.item-thumb-sm{width:36px;height:36px;border-radius:6px;object-fit:cover;flex:0 0 36px}
.bulk-actions{display:flex;align-items:center;gap:.75rem;margin:.75rem 0;flex-wrap:wrap}
@media (max-width:640px){
  .item-table thead{display:none}
  .item-table,.item-table tbody,.item-table tr,.item-table td{display:block;width:100%}
  .item-table tr{border:1px solid var(--line);border-radius:8px;margin-bottom:.6rem;padding:.5rem}
  .item-table td{border:none;padding:.25rem 0}
  .item-table td::before{content:attr(data-label);display:block;font-size:.875rem;color:var(--muted);margin-bottom:.15rem}
}

/* P3: item on/off switch + the "Managed by Gachasik" badge */
.switch{display:inline-flex;align-items:center;gap:.35rem;font-size:.875rem;white-space:nowrap}
.switch input{width:auto;max-width:none;margin:0}
.badge.managed{background:#0f172a;color:#e2e8f0}

/* Batch 3 / Phase B: sticky nav, touch targets, consistent buttons */

/* Touchscreen friendliness: kill the 300ms tap delay, the grey flash and
   accidental double-tap zoom, and give every control a 44px tap target. */
a,button,input,select,textarea,summary,label{touch-action:manipulation}
a,button,summary,label{-webkit-tap-highlight-color:transparent}
button,.button{min-height:2.75rem;display:inline-flex;align-items:center;justify-content:center;line-height:1.2}
input,select{min-height:2.75rem}
/* The kiosk keeps its own scaled sizing (vmin/dvh), so it opts out. */
.kiosk-body button,.kiosk-body .button,.kiosk-body input,.kiosk-body select{min-height:0}

/* Checkbox rows: the box sits beside its label, with a comfortable target,
   instead of stacking above it. */
.check-row{display:flex;align-items:center;gap:.55rem;min-height:2.75rem;padding:.15rem 0}
.check-row input[type=checkbox]{width:1.35rem;height:1.35rem;min-height:0;margin:0;flex:0 0 auto;accent-color:#4338ca}
.check-row .check-text{display:flex;flex-direction:column;gap:.1rem}
.check-row .check-hint{color:var(--muted);font-size:.875rem}

/* Sticky bottom tab bar on small screens; hidden on wide screens where the
   top nav already carries the links. */
.tabbar{position:sticky;bottom:0;z-index:40;display:none;grid-template-columns:repeat(4,1fr);gap:.25rem;
  background:var(--surface);border-top:1px solid #e2e8f0;padding:.35rem .4rem calc(.35rem + env(safe-area-inset-bottom))}
.tabbar a{display:flex;align-items:center;justify-content:center;min-height:3rem;padding:.4rem;border-radius:10px;
  color:var(--ink-strong);text-decoration:none;font-size:.875rem;font-weight:600;text-align:center}
.tabbar a:hover,.tabbar a:focus-visible{background:var(--indigo-wash)}
@media (max-width:640px){.tabbar{display:grid}
  /* The bar is sticky at the bottom, so it sits over whatever is under it
     while you scroll. A half-rem of padding left the last table row, the last
     button and the footer hidden behind it on every signed-in page. Reserve
     the bar's real height plus the device's home-indicator inset. */
  main{padding-bottom:calc(4.5rem + env(safe-area-inset-bottom))}
  footer{padding-bottom:calc(4.5rem + env(safe-area-inset-bottom))}}

/* A sticky back/context bar for the pool area, so the way out is always
   within reach on a long page. */
/* UI-RULES #22/batch 5: the pool bar sticks just below the sticky header,
   whose measured height base.html publishes as --gacha-nav-h (0 without JS,
   which keeps the previous top:0 behavior). */
.pool-bar{position:sticky;top:var(--gacha-nav-h,0);z-index:30;display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;
  background:var(--surface-sunken);border-bottom:1px solid var(--line-soft);padding:.4rem .2rem;margin:-.2rem 0 .6rem}
.pool-bar .pool-bar-title{font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pool-bar .pool-bar-actions{margin-left:auto;display:flex;gap:.4rem;flex-wrap:wrap}

/* Clear keyboard focus everywhere. */
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,summary:focus-visible{
  outline:3px solid #6366f1;outline-offset:2px}

/* A generic "wide table becomes labelled cards on a phone" helper, the same
   pattern the item table already uses. Opt a table in with class="list-table"
   and give each cell a data-label. */
@media (max-width:640px){
  .list-table thead{display:none}
  .list-table,.list-table tbody,.list-table tr,.list-table td{display:block;width:100%}
  .list-table tr{border:1px solid var(--line);border-radius:8px;margin-bottom:.6rem;padding:.5rem}
  .list-table td{border:none;padding:.25rem 0}
  /* Label beside its value, not above it. Stacked labels cost four lines per
     row, which on a phone turned a ten-row table into ~2,500 px — the reason
     the dashboard ran to 25 screens. A fixed label column keeps the same
     information in about a quarter of the height. */
  .list-table td{display:grid;grid-template-columns:7.5rem 1fr;align-items:baseline;gap:.4rem;padding:.2rem 0}
  .list-table td::before{content:attr(data-label);display:block;font-size:.8rem;color:var(--muted);margin:0}
  .list-table td[data-label=""]{display:block}
  .list-table td[data-label=""]::before{display:none}
  .list-table .actions-cell{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:.3rem}
  .list-table .share-cell{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}
  /* A cell whose value is prose (an item name, a note) reads better beside a
     narrow label than under it, so the grid holds; only the padding differs. */
  .list-table td.num{text-align:left}

  /* The pool list is also a .list-table, so its overrides must come AFTER
     the generic block — same specificity, later wins. Placing them first
     silently undid the grid and left every row at 215 px. */
  /* Pool list: a card per pool. The name spans the card, the three facts sit
     side by side in one row, and the actions close it — five stacked
     label/value pairs cost 246 px per pool, which is most of a screen for two
     pools. */
  .pools-table tr{display:grid;grid-template-columns:1fr 1fr 1fr;gap:.35rem .6rem;align-items:start}
  .pools-table td{display:block;padding:0;min-width:0}
  .pools-table td:first-child{grid-column:1 / -1}
  .pools-table .actions-cell{grid-column:1 / -1;display:flex;flex-wrap:wrap;gap:.4rem;padding-top:.15rem}
  .pools-table td::before{content:attr(data-label);display:inline;margin:0 .3rem 0 0;font-size:.72rem;color:var(--muted)}
  .pools-table td:first-child::before,.pools-table td.actions-cell::before{display:none}
  .pools-table td{font-size:.9rem}
  .pools-table td.num{text-align:left}
  .pools-table .share-cell{display:flex;align-items:center;gap:.5rem}
}

/* Phase C: multistage route rows — the per-route breakdown plus a total line
   for prizes reachable by more than one path. */
.badge.routes{background:var(--indigo-wash);color:var(--indigo-deep)}
.combined-table .route-total td{border-top:1px solid var(--line);font-weight:600}
.combined-table tr.route-total{background:rgba(99,102,241,.05)}
/* On the dark kiosk surface the shared light-theme rules above paint a pale
   stripe that reads as a stray bar. Re-tint the combined table for kiosk. */
.kiosk .combined-table,.kiosk .combined-table td,.kiosk .combined-table th{border-color:var(--kiosk-line-soft);color:var(--kiosk-ink)}
.kiosk .combined-table .route-total td{border-top:1px solid var(--kiosk-line);font-weight:700}
.kiosk .combined-table tr.route-total{background:var(--kiosk-tint)}
.kiosk .combined-table .badge.routes{background:var(--kiosk-tint-strong);color:var(--kiosk-ink)}
.kiosk .combined-table .tier{background:var(--kiosk-tint);color:var(--kiosk-tier-ink)}

/* Phase G: the seller help overlay — a soft, replayable keymap. */
.help-overlay{position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
  background:rgba(15,23,42,.55);padding:1rem;overflow:auto}
.help-overlay[hidden]{display:none}
body.help-open{overflow:hidden}
.help-panel{background:var(--surface);color:var(--ink);border-radius:14px;max-width:640px;width:100%;
  max-height:90vh;overflow:auto;padding:1.1rem 1.2rem 1.3rem;box-shadow:0 20px 60px rgba(0,0,0,.3)}
.help-head{display:flex;align-items:center;gap:.6rem}
.help-head h2{margin:0;font-size:1.75rem;flex:1}
.help-close{background:none;border:none;color:var(--muted);font-size:1.75rem;line-height:1;cursor:pointer;min-height:0;padding:.1rem .4rem}
.help-keys td:first-child{width:42%}
.help-key{display:inline-block;background:var(--indigo-wash);color:var(--indigo-deep);border:1px solid var(--indigo-soft);border-radius:6px;
  padding:.15rem .5rem;font-weight:600;font-size:.875rem}
.help-foot{text-align:right;margin:.8rem 0 0}

/* Batch 5: profile photo — the seller's own preview and the public avatar. */
.profile-avatar{width:96px;height:96px;border-radius:50%;object-fit:cover;border:2px solid var(--line);display:block}
.seller-avatar{width:72px;height:72px;border-radius:50%;object-fit:cover;border:2px solid var(--line);display:block;margin:.4rem 0}
.seller-bio{max-width:48rem}

/* UI-RULES #21: a compact draw trend above the detail lists. */
.trend{margin:.4rem 0 1rem}
.trend-head{display:flex;justify-content:space-between;align-items:baseline;gap:.6rem;margin:0 0 .4rem;flex-wrap:wrap}
.trend-title{font-weight:600}
.trend-bars{display:flex;align-items:flex-end;gap:.25rem;height:104px;padding:.2rem 0 0;border-bottom:1px solid var(--line)}
.trend-bar{flex:1 1 0;min-width:0;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;height:100%;position:relative;padding-bottom:18px}
.trend-bar-fill{flex:0 0 auto;display:block;width:100%;max-width:22px;min-height:2px;background:#6366f1;border-radius:4px 4px 0 0;align-self:center}
.trend-bar-label{position:absolute;bottom:0;font-size:.875rem;color:var(--muted);white-space:nowrap}
@media (max-width:640px){
  /* 14 date labels collide at phone width (33px labels on a ~24px pitch):
     show every third label, anchored to today so the latest date always reads. */
  .trend-bar:not(:nth-last-child(3n+1)) .trend-bar-label{display:none}
}
.trend-max{margin:.3rem 0 0;font-size:.875rem}

/* Phase H: dashboard stat tiles. */
.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:.6rem;margin:.6rem 0 1rem}
.stat{background:var(--surface-sunken);border-radius:10px;padding:.7rem .8rem;display:flex;flex-direction:column;gap:.15rem}
.stat-num{font-size:1.75rem;font-weight:700;line-height:1.1;font-variant-numeric:tabular-nums}
.stat-label{color:var(--muted);font-size:.875rem}
.balance-line{font-size:1.75rem;font-weight:700}
