/* Shop Sales — shared styles. Mobile-first, calm, high-contrast, big tap targets. */
:root {
  --bg: #f4f6f8;
  --card: #ffffff;
  --ink: #16191d;
  --muted: #6b7480;
  --line: #e4e8ec;
  --primary: #1f4fd8;
  --primary-ink: #ffffff;
  --primary-soft: #eaf0ff;
  --good: #0f9d58;
  --good-soft: #e7f6ee;
  --warn: #b45309;
  --danger: #d13a3a;
  --danger-soft: #fdecec;
  --radius: 14px;
  --shadow: 0 1px 2px rgba(16,25,40,.06), 0 6px 20px rgba(16,25,40,.06);
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  font-size: 16px;
  line-height: 1.4;
}
.wrap { max-width: 560px; margin: 0 auto; padding: 0 16px 96px; }
.hidden { display: none !important; }

/* Header */
.appbar {
  position: sticky; top: 0; z-index: 20;
  background: var(--card);
  border-bottom: 1px solid var(--line);
}
.appbar-inner {
  max-width: 560px; margin: 0 auto; padding: 12px 16px;
  display: flex; align-items: center; gap: 12px;
}
.appbar .brand { font-weight: 700; font-size: 17px; letter-spacing: -.01em; }
.appbar .brand small { display:block; font-weight: 500; color: var(--muted); font-size: 12px; }
.appbar .spacer { flex: 1; }

.sync-pill {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line); background: #fff;
  border-radius: 999px; padding: 7px 12px; font-size: 13px; font-weight: 600;
  color: var(--ink); cursor: pointer; white-space: nowrap;
}
.sync-pill .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--good); }
.sync-pill.pending { color: var(--warn); border-color: #f3d9b0; background: #fff8ee; }
.sync-pill.pending .dot { background: var(--warn); }

.link-btn { background: none; border: none; color: var(--primary); font-weight: 600; cursor: pointer; font-size: 14px; padding: 6px; }

/* Appbar version pill: shows the running app version (read off the SW cache name),
   pulses green when we've just auto-updated, tap = manual update check. */
.ver-pill {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line); background: #fff;
  border-radius: 999px; padding: 6px 10px; font-size: 12px; font-weight: 700;
  color: var(--muted); cursor: pointer; white-space: nowrap; font-family: inherit;
  transition: color .2s, border-color .2s, background .2s;
}
.ver-pill:hover { color: var(--ink); border-color: #cfd6dd; }
.ver-pill.checking { animation: verPulse 1s ease-in-out infinite; }
.ver-pill.bump { animation: verBump .6s cubic-bezier(.2,.9,.3,1.4); }
.ver-pill.updated, .ver-pill.uptodate { color: var(--good); border-color: #bfe6cd; background: var(--good-soft); }
@keyframes verBump { 0% { transform: scale(1); } 30% { transform: scale(1.18); } 60% { transform: scale(.94); } 100% { transform: scale(1); } }
@keyframes verPulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }

/* QR print size picker (per-product Print QR modal): saved-preset chips + a ＋ to
   save the current W×H, above the manual W×H box. */
.qr-preset-row { display: flex; align-items: center; gap: 8px; }
.qr-presets { display: flex; flex-wrap: wrap; gap: 8px; flex: 1; min-height: 36px; align-items: center; }
.qr-chip {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line); background: #fff; color: var(--ink);
  border-radius: 999px; padding: 7px 8px 7px 12px; font-size: 13px; font-weight: 600;
  cursor: pointer; font-family: inherit;
}
.qr-chip small { color: var(--muted); font-weight: 700; }
.qr-chip.active { border-color: var(--primary); background: var(--primary-soft); color: var(--primary); }
.qr-chip .qr-chip-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 17px; height: 17px; border-radius: 50%; background: var(--line);
  color: var(--ink); font-size: 13px; line-height: 1;
}
.qr-chip.active .qr-chip-x { background: #cdd9fb; }
.qr-preset-add {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 10px;
  border: 1px dashed #c7ced6; background: #fff; color: var(--primary);
  font-size: 22px; font-weight: 700; line-height: 1; cursor: pointer;
}
.qr-preset-form { border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; margin-top: 12px; background: var(--bg); }
.qr-preset-form label { margin-top: 0; }

/* Cards */
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 18px; margin-top: 16px;
}
h1 { font-size: 22px; margin: 20px 0 4px; letter-spacing: -.02em; }
h2 { font-size: 17px; margin: 0 0 12px; }
.sub { color: var(--muted); font-size: 14px; margin: 0 0 12px; }

/* Forms */
label { display: block; font-weight: 600; font-size: 14px; margin: 14px 0 6px; }
input[type=text], input[type=password], input[type=number], input[type=tel], input[type=date], select {
  width: 100%; padding: 14px; font-size: 17px;
  border: 1.5px solid var(--line); border-radius: 12px; background: #fff; color: var(--ink);
  -webkit-appearance: none; appearance: none;
}
input:focus, select:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.money-in { position: relative; }
.money-in::before { content: "Rs"; position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--muted); font-size: 14px; font-weight: 600; pointer-events: none; }
.money-in input { padding-left: 40px; }

/* Buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; padding: 16px; font-size: 17px; font-weight: 700;
  border-radius: 12px; border: 1.5px solid transparent; cursor: pointer; margin-top: 14px;
}
.btn.primary { background: var(--primary); color: var(--primary-ink); }
.btn.primary:active { filter: brightness(.95); }
.btn.ghost { background: #fff; color: var(--ink); border-color: var(--line); }
.btn.subtle { background: var(--primary-soft); color: var(--primary); }
.btn.danger { background: var(--danger-soft); color: var(--danger); }
.btn.good { background: var(--good); color: #fff; }
.btn:disabled { opacity: .5; }
.btn-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.btn.small { width: auto; padding: 10px 14px; font-size: 14px; margin: 0; }

/* Switch */
.switch-row { display: flex; align-items: center; justify-content: space-between; margin-top: 18px; padding: 4px 0; }
.switch-row .switch-label { font-weight: 700; font-size: 16px; }
.switch { position: relative; width: 52px; height: 30px; flex: none; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider { position: absolute; inset: 0; background: #cfd6dd; border-radius: 999px; transition: .15s; }
.slider::before { content:""; position:absolute; width: 24px; height: 24px; left: 3px; top: 3px; background:#fff; border-radius:50%; transition:.15s; box-shadow: 0 1px 3px rgba(0,0,0,.2); }
.switch input:checked + .slider { background: var(--primary); }
.switch input:checked + .slider::before { transform: translateX(22px); }

/* Total-to-collect preview strip (the shop shows the amount to collect here) */
.total-strip {
  margin-top: 16px; padding: 14px 16px; border-radius: 12px;
  background: var(--good-soft); color: var(--good);
  display: flex; align-items: center; justify-content: space-between; font-weight: 700;
  font-size: 18px;
}

/* Summary rows */
.sum-line { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px dashed var(--line); font-size: 15px; }
.sum-line:last-child { border-bottom: none; }
.sum-line .k { color: var(--muted); }
.sum-line.total { font-weight: 800; font-size: 18px; border-top: 2px solid var(--line); margin-top: 4px; padding-top: 12px; }

/* Totals tiles */
.tiles { display: grid; grid-template-columns: repeat(3,1fr); gap: 10px; margin-top: 16px; }
.tile { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 14px; text-align: center; box-shadow: var(--shadow); }
.tile .n { font-size: 20px; font-weight: 800; letter-spacing: -.02em; }
.tile .l { font-size: 12px; color: var(--muted); margin-top: 2px; }
/* Revenue is no longer the green metric — it's the primary/blue one.
   Profit is the green metric (red when negative). */
.tile.good .n { color: var(--primary); }
.tile.profit .n { color: var(--good); }
.tile.profit.neg .n { color: var(--danger); }
/* Comparison-vs-baseline badge inside a tile. */
.delta { font-size: 12px; font-weight: 800; margin-top: 2px; color: var(--muted); }
.delta.up { color: var(--good); }
.delta.down { color: var(--danger); }

/* Sale list */
.sale { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 14px; margin-top: 10px; box-shadow: var(--shadow); }
.sale .top { display: flex; justify-content: space-between; align-items: baseline; }
.sale .time { color: var(--muted); font-size: 13px; }
.sale .amt { font-weight: 800; font-size: 17px; }
.sale .meta { color: var(--muted); font-size: 13px; margin-top: 4px; }
.sale .pf { color: var(--good); font-weight: 700; font-size: 13px; }
.sale .acts { display: flex; gap: 8px; margin-top: 10px; }
.sale.deleted { opacity: .5; }
.badge { display:inline-block; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 999px; background: var(--primary-soft); color: var(--primary); }

/* Tabs */
.tabs { display: flex; gap: 8px; margin-top: 16px; background: #eef1f4; padding: 5px; border-radius: 12px; }
.tab { flex: 1; text-align: center; padding: 10px; border-radius: 9px; font-weight: 700; color: var(--muted); cursor: pointer; }
.tab.active { background: #fff; color: var(--ink); box-shadow: var(--shadow); }

/* Toast / message */
.msg { margin-top: 14px; padding: 12px 14px; border-radius: 10px; font-size: 14px; font-weight: 600; }
.msg.ok { background: var(--good-soft); color: var(--good); }
.msg.err { background: var(--danger-soft); color: var(--danger); }
.msg.info { background: var(--primary-soft); color: var(--primary); }

.pin-input { text-align: center; letter-spacing: 8px; font-size: 24px; font-weight: 800; }
.empty { text-align: center; color: var(--muted); padding: 30px 10px; }
.center { text-align: center; }
.big-check { width: 72px; height: 72px; border-radius: 50%; background: var(--good-soft); color: var(--good); display: flex; align-items: center; justify-content: center; font-size: 40px; margin: 6px auto 12px; }
.shop-pick { width:100%; text-align:left; padding: 16px; font-size: 17px; font-weight: 700; border:1.5px solid var(--line); background:#fff; border-radius: 12px; margin-top: 10px; cursor: pointer; }
.shop-pick:active { border-color: var(--primary); }
.footnote { color: var(--muted); font-size: 12px; margin-top: 10px; text-align: center; }
.settings-row { display:flex; gap: 10px; align-items:center; flex-wrap: wrap; }

/* Admin table */
.table-scroll { overflow-x: auto; margin-top: 12px; -webkit-overflow-scrolling: touch; }
table { width: 100%; border-collapse: collapse; font-size: 14px; min-width: 1180px; }
th, td { text-align: right; padding: 10px 12px; border-bottom: 1px solid var(--line); white-space: nowrap; vertical-align: top; }
th:first-child, td:first-child, th.l, td.l { text-align: left; }
thead th { color: var(--muted); font-weight: 700; font-size: 12px; text-transform: uppercase; letter-spacing: .03em; position: sticky; top: 0; background: var(--card); }
tbody tr:hover { background: #fafbfc; }
td.tot { font-weight: 800; }
td.pf { color: var(--good); font-weight: 800; }
td.pf.neg, .pf.neg { color: var(--danger); }
.filters { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 8px; }
.chip { padding: 8px 14px; border-radius: 999px; border: 1.5px solid var(--line); background: #fff; font-weight: 700; font-size: 14px; cursor: pointer; color: var(--muted); }
.chip.active { background: var(--primary); color: #fff; border-color: var(--primary); }
.select-inline { width: auto; min-width: 160px; padding: 9px 12px; font-size: 14px; }
.admin-wrap { max-width: 1000px; }

/* Sales date-range control + shop filter row */
.sales-controls { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 10px; }
.range-trigger {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 14px; border: 1.5px solid var(--line); border-radius: 12px;
  background: #fff; color: var(--ink); font-weight: 700; font-size: 14px; cursor: pointer;
}
.range-trigger:active { border-color: var(--primary); }
.range-trigger .rt-icon { font-size: 15px; }
.range-trigger .rt-caret { color: var(--muted); font-size: 11px; }

/* Range picker modal (scrim + centered panel) — mirrors the drawer's scrim. */
.range-scrim { position: fixed; inset: 0; background: rgba(16,25,40,.4); z-index: 70; opacity: 0; pointer-events: none; transition: opacity .18s ease; }
.range-scrim.open { opacity: 1; pointer-events: auto; }
.range-panel {
  position: fixed; z-index: 80; left: 50%; top: 50%; transform: translate(-50%, -46%);
  width: min(680px, 94vw); max-height: 90vh; overflow: auto;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  box-shadow: 0 12px 48px rgba(16,25,40,.24);
  opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease;
}
.range-panel.open { opacity: 1; pointer-events: auto; transform: translate(-50%, -50%); }
.range-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid var(--line); font-weight: 800; font-size: 16px; }
.range-body { display: flex; gap: 14px; padding: 14px 16px; }
.preset-list { display: flex; flex-direction: column; gap: 6px; width: 150px; flex: none; }
.qp { text-align: left; padding: 11px 12px; border: 1.5px solid var(--line); background: #fff; border-radius: 10px; font-weight: 700; font-size: 13px; color: var(--ink); cursor: pointer; }
.qp:active { background: var(--bg); }
.qp.active { background: var(--primary); color: #fff; border-color: var(--primary); }

/* Self-contained calendar (no external lib). */
.cal { flex: 1; min-width: 0; }
.cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.cal-title { font-weight: 800; font-size: 15px; }
.cal-nav { width: 36px; height: 36px; border: 1.5px solid var(--line); background: #fff; border-radius: 9px; font-size: 18px; line-height: 1; cursor: pointer; color: var(--ink); }
.cal-nav:disabled { opacity: .4; cursor: default; }
.cal-dow { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; margin-bottom: 4px; }
.cal-dow span { text-align: center; font-size: 11px; font-weight: 700; color: var(--muted); padding: 4px 0; }
/* minmax(0,1fr) — plain 1fr lets each column keep its content min-width, and with
   aspect-ratio:1 cells that inflates to ~60px each and overflows the panel on a
   phone. minmax(0,…) lets the 7 columns shrink to fit the width instead. */
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
.cal-cell { aspect-ratio: 1; min-height: 38px; }
.cal-cell.empty { visibility: hidden; }
.cal-cell.day {
  display: flex; align-items: center; justify-content: center;
  border: none; background: transparent; border-radius: 9px;
  font-size: 14px; font-weight: 600; color: var(--ink); cursor: pointer; padding: 0;
}
.cal-cell.day:hover { background: var(--primary-soft); }
.cal-cell.day.today { box-shadow: inset 0 0 0 1.5px var(--muted); }
.cal-cell.day.in-range { background: var(--primary-soft); border-radius: 0; }
.cal-cell.day.sel-start, .cal-cell.day.sel-end { background: var(--primary); color: #fff; }
.cal-cell.day.sel-start { border-radius: 9px 0 0 9px; }
.cal-cell.day.sel-end { border-radius: 0 9px 9px 0; }
.cal-cell.day.sel-start.sel-end { border-radius: 9px; }
.cal-cell.day.disabled { color: var(--line); cursor: default; pointer-events: none; }

.range-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 16px; border-top: 1px solid var(--line); flex-wrap: wrap; }
.picker-sel { font-weight: 700; font-size: 14px; color: var(--muted); }
.range-foot-btns { display: flex; gap: 8px; }
.range-foot-btns .btn.small { margin: 0; }

/* Phone: the quick-pick sidebar becomes a wrapping row above the calendar. */
@media (max-width: 560px) {
  .range-body { flex-direction: column; gap: 10px; }
  .preset-list { flex-direction: row; flex-wrap: wrap; width: auto; }
  .qp { flex: 1 1 30%; text-align: center; }
  /* Stacked full-width, the 7-col grid stretched cells into big rectangles.
     Cap + center the calendar and keep cells square (no over-tall rows). */
  .cal { flex: none; width: 100%; max-width: 340px; margin: 0 auto; align-self: center; }
  .cal-cell { min-height: 0; }
}

/* Admin drawer nav — landing view is sales-only; other sections open from here */
.icon-btn { background: none; border: none; font-size: 22px; line-height: 1; padding: 6px 4px; margin: 0; cursor: pointer; color: var(--ink); flex: none; }
/* The nav is a fixed-height (100vh) flex COLUMN. As sections were added over time
   (AD6 group labels + the L4 Supplier/Payable split + Cash box + Fixed assets) the
   list outgrew a phone's viewport and the LAST item (Devices) fell below the fold
   with no way to reach it — it was in the DOM but clipped off-screen, NOT removed.
   overflow-y:auto makes the nav scroll so every item stays reachable (bug-160). */
.drawer {
  position: fixed; top: 0; left: 0; height: 100%; width: 78%; max-width: 300px;
  background: var(--card); z-index: 60; transform: translateX(-100%);
  transition: transform .22s ease; box-shadow: 2px 0 24px rgba(16,25,40,.14);
  display: flex; flex-direction: column;
  overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  padding: 6px 8px calc(8px + env(safe-area-inset-bottom));
}
.drawer.open { transform: translateX(0); }
.drawer-head { font-weight: 800; font-size: 18px; padding: 16px 12px 8px; letter-spacing: -.01em; }
.drawer-item {
  display: block; width: 100%; text-align: left; background: none; border: none;
  border-radius: 10px; padding: 14px 12px; font-size: 16px; font-weight: 700;
  color: var(--ink); cursor: pointer;
}
.drawer-item:active { background: var(--bg); }
.drawer-item.active { background: var(--primary-soft); color: var(--primary); }
.drawer-scrim {
  position: fixed; inset: 0; background: rgba(16,25,40,.4); z-index: 50;
  opacity: 0; pointer-events: none; transition: opacity .22s ease;
}
.drawer-scrim.open { opacity: 1; pointer-events: auto; }

/* Scan row + camera overlay */
.scan-row { display: flex; gap: 10px; align-items: stretch; flex-wrap: wrap; }
/* min-width:0 lets the field shrink to fit; without it a flex item keeps its
   intrinsic min-width (input default size) on iOS Safari / some Android WebViews
   and pushes the row past the card edge. flex-wrap drops Scan below only if truly
   cramped. Do not remove min-width:0 — it looks redundant but prevents overflow. */
.scan-row input { flex: 1 1 55%; min-width: 0; }
.scan-btn { width: auto; margin: 0; padding: 0 18px; white-space: nowrap; flex: none; }
.scanner { position: fixed; inset: 0; z-index: 100; background: #000; display: flex; align-items: center; justify-content: center; }
#scanVideo { width: 100%; height: 100%; object-fit: cover; }
.scan-frame { position: absolute; width: 66vw; max-width: 300px; aspect-ratio: 1; border: 3px solid #fff; border-radius: 16px; box-shadow: 0 0 0 100vmax rgba(0,0,0,.45); }
.scan-bar { position: absolute; bottom: 0; left: 0; right: 0; padding: 20px 16px calc(20px + env(safe-area-inset-bottom)); display: flex; align-items: center; justify-content: space-between; gap: 12px; background: linear-gradient(transparent, rgba(0,0,0,.7)); }
.scan-bar span { color: #fff; font-weight: 600; font-size: 15px; }
.sku-hit { color: var(--good); font-weight: 700; }
.thumb { width: 44px; height: 44px; border-radius: 8px; object-fit: cover; background: #eef1f4; border: 1px solid var(--line); flex: none; }
.thumb-zoomable { cursor: zoom-in; }
/* Product photo lightbox (admin): full-screen overlay showing the ZOOM image on a
   product-thumb tap. Above the sale-detail / range modals (z-index 80); below the
   shop scanner (100, a different app). Click anywhere / the ✕ closes it. */
.photo-lightbox { position: fixed; inset: 0; z-index: 95; display: none; align-items: center; justify-content: center; background: rgba(10,14,20,.86); padding: 20px; }
.photo-lightbox.open { display: flex; }
.photo-lightbox img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 12px; box-shadow: 0 12px 44px rgba(0,0,0,.5); }
.photo-lightbox-x { position: absolute; top: calc(14px + env(safe-area-inset-top)); right: 16px; width: 40px; height: 40px; border: none; border-radius: 50%; background: rgba(255,255,255,.16); color: #fff; font-size: 26px; line-height: 1; cursor: pointer; }
.prod-row { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px dashed var(--line); }
.prod-row:last-child { border-bottom: none; }
.prod-row .info { flex: 1; min-width: 0; }
.prod-row .sku { font-weight: 800; letter-spacing: .02em; }
.prod-row .sub2 { color: var(--muted); font-size: 13px; }
.prod-check, .pick-check { width: 22px; height: 22px; flex: none; accent-color: var(--primary); cursor: pointer; }
.selectall { display: inline-flex; align-items: center; gap: 8px; margin: 0; font-size: 14px; font-weight: 700; color: var(--muted); cursor: pointer; }
.selectall input { width: 20px; height: 20px; accent-color: var(--primary); cursor: pointer; }

/* Full-width product-photo placeholder: tap to capture/choose, shows a preview. */
.imagedrop {
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-height: 150px; margin-top: 6px; padding: 16px;
  border: 2px dashed var(--line); border-radius: 12px; background: #fafbfc;
  color: var(--muted); text-align: center; cursor: pointer; overflow: hidden;
}
.imagedrop:active { border-color: var(--primary); background: var(--primary-soft); }
.imagedrop-hint { font-size: 14px; font-weight: 600; }
.imagedrop-img { width: 100%; max-height: 320px; object-fit: contain; border-radius: 8px; }

/* QR label printing now happens inside a self-contained print iframe (built in
   admin.js) that carries its own styles — reliable on mobile, unlike the old
   whole-page visibility hack. No print CSS is needed on the main document. */

/* Per-shop stock editor (tracked products) + add-product opening-stock inputs */
.stock-editor { margin: 2px 0 12px 34px; padding: 10px 12px; background: #fafbfc; border: 1px solid var(--line); border-radius: 10px; }
/* The head is an accordion toggle in the products list: the rows collapse behind it
   so a tracked product's row stays compact until tapped. */
.stock-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; background: none; border: none; text-align: left; cursor: pointer; padding: 2px 0;
  font-size: 12px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .03em;
}
.stock-caret { font-size: 11px; transition: transform .15s ease; }
.stock-editor.open .stock-caret { transform: rotate(180deg); }
.stock-body { margin-top: 6px; }
/* max-width — without it the space-between row stretches across the whole admin
   card on desktop, splaying the shop name and its input to opposite edges. */
.stock-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 5px 0; max-width: 360px; }
.stock-shop { font-weight: 600; font-size: 14px; }
/* input.stock-qty — the base input[type=number]{width:100%} rule outweighs a plain
   .stock-qty, so the qty box expanded to full row width; matching the element makes
   the compact fixed width actually apply. */
input.stock-qty { width: 90px; flex: none; padding: 8px 10px; font-size: 15px; text-align: right; }
.stock-qty.neg { color: var(--danger); border-color: var(--danger); font-weight: 800; }

/* PROPER SAVE for a stock cell (2026-08-05). The qty used to commit on BLUR with the
   reason collected through a browser prompt() and NOTHING shown on success — the owner
   could not tell whether an audit-stamped ledger move had landed. Now an edit puts the
   cell in a visible PENDING state and reveals its reason + Save; nothing writes until
   he presses it. `.stock-*` is an ADMIN-ONLY class family (the shop app has no stock
   editor), so styling it here is collision-free under the shared-style.css rule. */
.stock-cell { padding: 2px 0; border-radius: 8px; }
.stock-cell.dirty { background: var(--primary-soft, #eef3ff); box-shadow: inset 3px 0 0 var(--primary, #3b6cf5); padding-left: 8px; margin-left: -8px; }
.stock-save { display: flex; align-items: center; gap: 6px; padding: 6px 0 2px; flex-wrap: wrap; }
.stock-save.hidden { display: none; }
input.stock-reason, input.wo-reason { width: auto; flex: 1 1 170px; min-width: 0; padding: 7px 9px; font-size: 14px; }
.stock-msg .msg, .stock-goods-msg .msg, .stock-track-msg .msg { margin: 6px 0 2px; padding: 6px 9px; font-size: 13px; }
.stock-step { min-width: 34px; padding: 6px 0; font-size: 17px; line-height: 1; }
/* The tracking-on seed form: one row per shop, then a shared reason + Save — the same
   deliberate shape as a cell edit, instead of a chain of browser prompts. */
.track-on-form { margin: 6px 0 4px; padding: 10px 12px; background: #fafbfc; border: 1px solid var(--line); border-radius: 10px; max-width: 380px; }

/* New-sale alert opt-in banner (admin). Shows until notifications are granted;
   the button is the user gesture that requests permission. */
.alert-optin {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-top: 14px; padding: 14px 16px;
  background: var(--primary-soft); border: 1px solid #cddcff; border-radius: 12px;
}
.alert-optin .optin-text { flex: 1 1 240px; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.alert-optin .optin-text strong { font-size: 15px; }
.alert-optin .optin-text span { color: var(--muted); font-size: 13px; }
.alert-optin .btn.small { flex: none; }

/* "New version — reload" bar (shared by shop + admin). Fixed + non-blocking;
   appears when a new service worker is deployed so an already-open page can pull
   the new code without the owner knowing to hard-reload. */
.sw-update-bar {
  position: fixed; left: 50%; bottom: calc(16px + env(safe-area-inset-bottom));
  transform: translateX(-50%); z-index: 90;
  display: flex; align-items: center; gap: 14px;
  max-width: calc(100vw - 24px);
  background: var(--ink); color: #fff;
  padding: 12px 14px 12px 18px; border-radius: 999px;
  box-shadow: 0 6px 24px rgba(16,25,40,.28);
  font-size: 14px; font-weight: 600;
}
.sw-update-bar .sw-update-btn {
  flex: none; border: none; cursor: pointer;
  background: #fff; color: var(--ink);
  font-weight: 800; font-size: 14px;
  padding: 8px 16px; border-radius: 999px;
}
.sw-update-bar .sw-update-btn:disabled { opacity: .6; }

/* Compact tappable sale row — the admin sales list is now a stack of these
   instead of the wide multi-column table. Shop bold left, amount right,
   small muted time under the shop. Tap opens the sale detail modal. */
.sale-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  text-align: left; background: var(--card);
  border: 1px solid var(--line); border-radius: 12px;
  padding: 14px 16px; margin-top: 10px; box-shadow: var(--shadow);
  cursor: pointer; color: var(--ink);
}
.sale-row:active { border-color: var(--primary); background: #fafbfc; }
.sale-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.sale-row-shop { font-weight: 800; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sale-row-time { color: var(--muted); font-size: 12px; }
.sale-row-amt { flex: none; font-weight: 800; font-size: 17px; }

/* Sale detail modal — reuses .range-scrim / .range-panel for scrim + panel.
   The body is a stack of .sum-line rows (shared with the shop summary) plus
   the payment proof image and a Delete button. */
/* display:block — it carries .range-body (for padding/scroll) which is display:flex.
   On desktop that laid the detail rows out in a horizontal row; block stacks them. */
#saleDetailBody { display: block; padding: 14px 16px; }
#saleDetailBody .sum-line { font-size: 15px; }
.sale-proof-img {
  width: 100%; max-height: 360px; object-fit: contain;
  margin-top: 12px; border-radius: 10px; border: 1px solid var(--line); background: #fafbfc;
}

/* Payment wizard step (shop): two big side-by-side choice buttons, then a
   revealed online-proof block (optional amount + screenshot capture). */
.pay-choice { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 6px; }
.pay-choice .btn { margin-top: 0; padding: 22px 16px; font-size: 18px; }
#onlineProof { margin-top: 16px; }
/* The transfer-screenshot preview reuses the .imagedrop look; keep it capped. */
#payProofPreview { width: 100%; max-height: 320px; object-fit: contain; border-radius: 8px; }

/* ==================================================================== */
/* Net-accounting (2026-07-12) — receivables, expenses, drawer. All new
   classes are .net-* and APPENDED here; nothing above is touched. Reuses
   the existing tokens/classes (.card/.btn/.sale/.money-in/.pay-choice…). */
/* ==================================================================== */

/* Calm re-edit (L2): the 6 tabs no longer cram (dropped the flex:1 + shrunk font).
   They size to their content and scroll horizontally inside the pill; the scrollbar is
   softened away. Same pill look. */
.net-tabs { gap: 6px; flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.net-tabs::-webkit-scrollbar { display: none; }
.net-tabs .tab { flex: 0 0 auto; padding: 9px 14px; font-size: 14px; white-space: nowrap; }
/* Calm re-edit round-2: a soft edge-fade signals the tab row scrolls (scrollbar is
   hidden). Scroll-aware — shop.js (onTabsScroll) toggles .fade-l / .fade-r so only an
   edge that actually has hidden tabs melts off; no fade at all when every tab fits. */
.net-tabs.fade-r { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent 100%); mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent 100%); }
.net-tabs.fade-l { -webkit-mask-image: linear-gradient(to right, transparent 0, #000 22px); mask-image: linear-gradient(to right, transparent 0, #000 22px); }
.net-tabs.fade-l.fade-r { -webkit-mask-image: linear-gradient(to right, transparent 0, #000 22px, #000 calc(100% - 22px), transparent 100%); mask-image: linear-gradient(to right, transparent 0, #000 22px, #000 calc(100% - 22px), transparent 100%); }

/* "Received now" balance line under the money field: a balance to collect later
   (warn) or an overpaid note (blue). */
.net-balance-line { margin-top: 10px; padding: 10px 14px; border-radius: 10px; font-size: 14px; font-weight: 700; background: #fff8ee; color: var(--warn); border: 1px solid #f3d9b0; }
.net-balance-line strong { font-weight: 800; }
.net-balance-line.net-overpaid { background: var(--primary-soft); color: var(--primary); border-color: #cddcff; }

/* Customer-required affordances when a sale carries a balance. */
.badge.net-req { background: var(--danger-soft); color: var(--danger); }
.net-req-note { color: var(--danger); font-weight: 600; }

/* Recent-expense-name chips (tap to fill the name field). */
.net-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 2px; }
.net-chip { padding: 8px 14px; border-radius: 999px; border: 1.5px solid var(--line); background: #fff; font-weight: 700; font-size: 14px; color: var(--ink); cursor: pointer; font-family: inherit; }
.net-chip:active { border-color: var(--primary); background: var(--primary-soft); color: var(--primary); }

/* One-tap on-balance supplier suggestion (amendment 2026-07-16): sits under a
   supplier picker in the sale-form cost zone; empty = takes no space. */
.net-sup-suggest { margin: 4px 0 6px; }
.net-sup-suggest:empty { display: none; }
.net-sup-suggest .net-chip { font-size: 13px; padding: 6px 12px; }

/* Section head above a today-list (Expenses). */
.net-list-head { font-size: 12px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; margin: 22px 0 2px; }

/* Round 2 (2026-07-16): segmented Paid-now/Pay-later (shop expense form), the Payable
   Product|Expense sub-tabs, and the per-sale category EXTRAS block. All net-* (shop-
   scoped — the shared style.css discipline: never restyle a shared class). */
.net-seg { display: flex; gap: 0; border: 1.5px solid var(--line); border-radius: 12px; overflow: hidden; width: fit-content; margin: 4px 0 2px; }
.net-seg button { border: none; background: #fff; color: var(--muted); font-weight: 700; font-size: 14px; padding: 9px 18px; cursor: pointer; font-family: inherit; }
.net-seg button.on { background: var(--primary-soft); color: var(--primary); }
.net-subtabs { display: flex; gap: 8px; margin: 14px 0 2px; }
.net-subtabs button { border: 1.5px solid var(--line); background: #fff; color: var(--muted); font-weight: 700; font-size: 14px; padding: 8px 18px; border-radius: 999px; cursor: pointer; font-family: inherit; }
.net-subtabs button.on { border-color: var(--primary); background: var(--primary-soft); color: var(--primary); }
/* Per-category EXTRAS: a "More costs" block of closed category chips; an open chip
   flips to the accent state; each opened chip reveals a cost row below it. */
.net-extra-block { margin-top: 12px; }
.net-extra-label { font-size: 12px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; margin-bottom: 4px; }
.net-chip.on { border-color: var(--primary); background: var(--primary-soft); color: var(--primary); }
.net-cost-row { border: 1px solid var(--line); border-radius: 12px; padding: 10px 12px; margin: 8px 0; background: #fff; }
.net-cost-row-head { display: flex; align-items: center; justify-content: space-between; font-weight: 700; font-size: 14px; margin-bottom: 6px; }
.net-cost-row-x { border: none; background: none; color: var(--muted); font-size: 18px; cursor: pointer; padding: 0 4px; }
/* A HISTORIC RESERVED COST, SHOWN AS RECORDED AND NOT EDITABLE (2026-08-08). Muted and
   without a border accent so it reads as a record rather than a field. `.net-*` = shop
   scope; style.css is shared with /admin/ and an unscoped class would bleed there. */
.net-cost-row-locked { background: var(--bg, #f7f7f8); border-style: dashed; }
.net-cost-row-locked .net-cost-row-head { color: var(--muted); }
/* A settle button on a shop pay-later due row. */
.net-due-settle { margin-top: 8px; }

/* Balances: outstanding header + tappable rows. */
.net-out-head { display: flex; align-items: center; justify-content: space-between; margin-top: 16px; padding: 14px 16px; border-radius: 12px; background: #fff8ee; border: 1px solid #f3d9b0; color: var(--warn); font-weight: 700; }
.net-out-head strong { font-size: 20px; font-weight: 800; }
.net-tap { cursor: pointer; }
.net-tap:active { border-color: var(--primary); background: #fafbfc; }
.net-cta { margin-top: 8px; color: var(--primary); font-weight: 700; font-size: 13px; }
/* Amount-owed tag on a Today sale row. */
.net-owed { color: var(--warn); font-weight: 700; }

/* Supplier payables (2026-07-14). Generic muted text (a supplier with no opening
   shows "not set"); the statement head inside the Pay sheet.
   (2026-08-10: .sup-toggle / .sup-caret / .sup-block removed here — dead. The
   sale-form supplier+cost sub-block toggle they styled belonged to the collapse/
   expand accordion that the New-sale form redesign deleted; zero references left
   in index.html or shop.js, checked before removing.) */
.net-muted { color: var(--muted); }
.net-stmt-head { margin-top: 14px; font-size: 12px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; }

/* Drawer card: the running number front-and-centre + since-last-count movement. */
.net-drawer-card { text-align: center; }
.net-drawer-l { color: var(--muted); font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.net-drawer-num { font-size: 40px; font-weight: 800; letter-spacing: -.02em; margin: 6px 0 4px; color: var(--good); }
.net-drawer-num.net-muted { color: var(--muted); font-size: 26px; }
.net-since { margin: 14px 0 6px; text-align: left; border: 1px solid var(--line); border-radius: 12px; padding: 6px 14px; background: #fafbfc; }
.net-since-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px dashed var(--line); font-size: 15px; font-weight: 600; }
.net-since-row:last-child { border-bottom: none; }
.net-in { color: var(--good); font-weight: 700; }
/* ROUND 6 calm-register sweep: routine drawer OUTFLOWS (expenses, owner-took,
   supplier-paid, refunds) are normal cash movement, not losses — a wall of alarm-red
   for everyday operations is exactly the "high-cortisol" the owner flagged. The − sign
   already carries the direction; the number reads in calm INK. Alarm-red (--danger) is
   RESERVED for a true negative: a drawer SHORT (.net-short), split out below. */
.net-out { color: var(--ink); font-weight: 700; }
.net-short { color: var(--danger); font-weight: 700; }
.net-drawer-base { margin-top: 6px; }

/* Bottom sheet (Receive / Owner took cash / Count / Close). Scrim + a panel that
   slides up from the bottom, capped to the app width and centred. */
.net-scrim { position: fixed; inset: 0; background: rgba(16,25,40,.4); z-index: 70; opacity: 0; pointer-events: none; transition: opacity .18s ease; }
.net-scrim.open { opacity: 1; pointer-events: auto; }
.net-sheet {
  position: fixed; z-index: 80; left: 50%; bottom: 0; transform: translate(-50%, 100%);
  width: min(560px, 100vw); max-height: 92vh; overflow: hidden;
  display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 18px 18px 0 0; box-shadow: 0 -8px 40px rgba(16,25,40,.24);
  opacity: 0; pointer-events: none; transition: transform .2s ease, opacity .2s ease;
  padding-bottom: env(safe-area-inset-bottom);
}
.net-sheet.open { opacity: 1; pointer-events: auto; transform: translate(-50%, 0); }
.net-sheet-head { display: flex; align-items: center; justify-content: space-between; padding: 16px 18px; border-bottom: 1px solid var(--line); font-weight: 800; font-size: 17px; }
.net-sheet-x { background: none; border: none; font-size: 20px; line-height: 1; color: var(--muted); cursor: pointer; padding: 4px 6px; }
.net-sheet-body { padding: 4px 18px 10px; overflow-y: auto; }
.net-sheet-cust { font-weight: 800; font-size: 16px; margin-top: 10px; }
.net-sheet-foot { display: flex; gap: 10px; padding: 12px 18px calc(12px + env(safe-area-inset-bottom)); border-top: 1px solid var(--line); }
.net-sheet-foot .btn { margin-top: 0; flex: 1; }

/* ==================================================================== */
/* Calm re-edit (2026-07-15) — shop-scoped .net-* additions only. NEVER
   restyles the shared .btn.danger / .tile / .prod-row / .sale-row. */
/* ==================================================================== */

/* L1 — sale/cost split in the new-sale form. SALE prices read as the primary money;
   the COST + supplier inputs sit in a quieter, tinted secondary zone behind the
   "Suppliers & cost" accordion so a fast scan never lands a sale price in a cost box. */
.net-sale-label { color: var(--ink); font-weight: 700; }
.net-cost-zone { margin-top: 6px; padding: 6px 14px 8px; background: #fafbfc; border: 1px solid var(--line); border-radius: 12px; }
.net-cost-zone label { color: var(--muted); font-weight: 600; margin-top: 12px; }
.net-cost-zone > label:first-child { margin-top: 6px; }
.net-cost-zone .money-in input, .net-cost-zone select { background: #fff; }

/* X1 — the row-level delete is a quiet "⋯" affordance, not an always-red button. Red
   now shows only inside the confirm sheet it opens (SH4). Pushed to the row's right. */
.net-row-more { flex: none; width: auto; margin: 0 0 0 auto; padding: 6px 12px; background: none; border: none; color: var(--muted); font-size: 22px; line-height: 1; letter-spacing: 1px; cursor: pointer; border-radius: 8px; font-family: inherit; }
.net-row-more:active { background: var(--bg); color: var(--ink); }

/* X2 / SH3 — settled loading. Panes keep their last-rendered DOM across tab switches
   (instant re-paint); on first entry a light skeleton shows while the background fetch
   runs, and the list containers reserve a min-height so switching holds layout steady. */
.net-skel { height: 64px; border-radius: 12px; margin-top: 10px; background: linear-gradient(100deg, #eef1f4 30%, #f6f8fa 50%, #eef1f4 70%); background-size: 200% 100%; animation: netSkel 1.2s ease-in-out infinite; }
@keyframes netSkel { 0% { background-position: 180% 0; } 100% { background-position: -20% 0; } }
#todayList, #balancesList, #expTodayList, #suppliersList, #drawerBody { min-height: 72px; }

/* Withdrawal purpose one-tap (statement round, 2026-07-17 — Move 7). Shop-only
   classes (admin never references .wdp / .wd-purpose — collision-free). Calm chips:
   the selected one tints good; the rest stay quiet. Default is "Owner took" (draw). */
.wd-purpose { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 6px; }
.wdp { flex: 1 1 40%; padding: 10px 8px; border: 1px solid var(--line); border-radius: 10px; background: var(--card); color: var(--muted); font: inherit; font-size: 13px; font-weight: 700; cursor: pointer; }
.wdp.on { border-color: #bfe6cd; background: var(--good-soft, #eaf7ef); color: var(--good); }

/* ---- Cash-loop round (Move 9): returns + cash-in — calm, functional register ---- */
/* A quiet secondary link (muted ink, no red, no fill) — the deliberate one-drill-down
   for cash-in + the older-sale return lookup; never a fat primary tap target. */
.btn-link {
  display: block; width: 100%; margin: 10px 0 0; padding: 10px 4px;
  background: none; border: none; color: var(--muted); font-family: inherit;
  font-size: 14px; font-weight: 600; text-align: center; cursor: pointer;
}
.btn-link:hover { color: var(--ink); }
.ret-lookup { margin: 0 0 12px; }
.drawer-cashin { margin-top: 6px; }
.ret-restock { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--ink); margin: 6px 0 12px; }
.ret-restock input { width: 18px; height: 18px; }

/* ============================================================================
   ROUND 6 (Moves 10+11): month snapshot + statements + customer rollup. All in the
   calm functional register — INK numbers, muted accents, red ONLY for true negatives
   (.neg). .adm-* = admin-scoped, .net-shop rollup + .cust-* = shop-scoped (collision-
   free per the scoping discipline). Layouts reuse existing card / sum-line furniture.
   ============================================================================ */
/* Generic calm helpers used by the statement lines. */
.ink { color: var(--ink); }
.sum-line.strong { font-weight: 700; }
.sum-line.strong .k { font-weight: 700; }
.sum-line.sub2 { font-size: 13px; color: var(--muted); padding: 4px 0; border-bottom: none; }
.sum-line .k { color: var(--muted); }
.stmt-group { margin: 12px 0 2px; font-size: 12px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; }
.stmt-stock-shop { margin-top: 12px; padding-top: 8px; border-top: 1px solid var(--line); }
.stmt-stock-shop .ss-head { font-weight: 700; color: var(--ink); margin-bottom: 4px; }

/* Month lock chip + drift badge (Move 10) — calm: an open month is neutral, a closed
   one reads muted-good, a DRIFT is a quiet amber note (never alarm-red — it is a
   read-only signal, not a loss). */
.adm-month-chip { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 12px; margin-bottom: 12px; border: 1px solid var(--line); border-radius: 12px; background: var(--card); }
.adm-month-chip .mc-state { font-weight: 700; color: var(--ink); }
.adm-month-chip.closed .mc-state { color: var(--good); }
.adm-month-chip .mc-clean { font-size: 12px; font-weight: 700; color: var(--muted); }
.adm-month-chip .mc-drift { font-size: 12px; font-weight: 700; color: var(--warn); background: #fff8ee; border: 1px solid #f3d9b0; border-radius: 999px; padding: 3px 10px; }

/* Counter nudge (Move 11.4) — a quiet amber hint that a known phone owes from before.
   A NUDGE, never a block: warm, low-key, not alarm-red. */
.cust-owes { margin: 6px 0 2px; padding: 8px 12px; border-radius: 10px; background: #fff8ee; border: 1px solid #f3d9b0; color: var(--warn); font-size: 13px; font-weight: 600; }

/* Shop Balances grouped by person (Move 11.4) — a calm group header above a person's
   sales; presentation only, the per-sale receive rows are unchanged beneath it. */
.bal-person { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin: 16px 0 2px; }
.bal-person .bp-who { font-weight: 700; color: var(--ink); font-size: 14px; }
.bal-person .bp-tot { font-size: 12px; font-weight: 700; color: var(--muted); }
.ret-find-row { margin-top: 8px; }


/* ===========================================================================
   MULTI-LINE SALE FORM (2026-08-01)
   Product blocks + the sticky money footer. One block is OPEN at a time; the
   rest collapse to a tappable summary, because three full product forms stacked
   on a phone is not a form anyone can use.
   =========================================================================== */
/* THE PER-ITEM BLOCK (2026-08-10). The collapse/expand accordion is GONE — items are
   sections of one bill now (.net-item / .net-ibar / .net-ibody above), so the box, the
   summary row, the caret and the collapsed state all went with it. What survives is the
   one MONEY-BEARING rule: a frame this shop does not hold is refused, and it has to LOOK
   refused. The red state sits on the INPUT because the typed path deliberately leaves the
   number in the box (clearing it mid-keystroke would fight the salesman), and a message one
   element below is easy to scroll past on a phone. `.sale-line` is kept as the hook (the
   item body still carries it) so the selector reads the same as it always did. */
.sale-line input.oos {
  border-color: var(--danger);
  background: var(--danger-soft);
}

/* Add-product button: clearly secondary to the sale itself, full width, dashed so
   it reads as "there is room for more" rather than as a primary action.
   (Base rule restored 2026-08-10 — this got eaten by the New-sale form redesign's
   style.css hunk along with the five blocks below it; .net-billfoot .add-line-btn
   below only zeroes the margin, it never carried the button's own look.) */
.add-line-btn{width:100%;border:1px dashed var(--line,#cbd3df) !important;background:#fff !important;
  color:var(--brand,#1f4fd8) !important;font-weight:600;padding:12px;border-radius:12px;margin:2px 0 14px}
.add-line-btn:active{background:#f4f7ff !important}

/* Sticky money footer. Sits above the iOS home indicator; the form gets bottom
   padding so the last field can never hide behind it. Still the footer for the
   Sale/Money/Cost steps of the new bill (restored 2026-08-10 — referenced live at
   index.html #moneySticky and the two Money/Cost step footers). */
.money-sticky{position:sticky;bottom:0;z-index:5;margin:14px -14px -14px;padding:10px 14px calc(10px + env(safe-area-inset-bottom));
  background:rgba(255,255,255,.97);backdrop-filter:blur(8px);border-top:1px solid var(--line,#e3e6ec)}
.money-sticky .total-strip{margin:0 0 8px}
.money-sticky .btn{width:100%;margin:0}
#step-money{padding-bottom:4px}

/* Day picker on the Today tab. Restored 2026-08-10 — #dayPicker + .day-chip still
   render live in shop.js/index.html; unrelated to the New-sale form and never
   should have been touched by that hunk. */
.day-picker{display:flex;gap:8px;overflow-x:auto;padding:2px 0 10px;-webkit-overflow-scrolling:touch}
.day-picker::-webkit-scrollbar{display:none}
.day-chip{flex:0 0 auto;border:1px solid var(--line,#e3e6ec);background:#fff;color:var(--ink-2,#5b6472);
  border-radius:999px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap}
.day-chip.on{background:var(--brand,#1f4fd8);border-color:var(--brand,#1f4fd8);color:#fff}
.day-chip-n{opacity:.7;font-weight:500;margin-left:2px}

/* Confirm-screen item separator (multi-line summary). Restored 2026-08-10 —
   shop.js still emits .sum-line.sum-item for every multi-line Check screen. */
.sum-line.sum-item{border-top:1px solid var(--line,#eef1f5);margin-top:6px;padding-top:8px;font-weight:700}

/* ==================================================================== */
/* ORDER STATUS (2026-08-02) — shop-scoped .net-* only, per the guardrail
   above. Adds no :root variable and restyles nothing shared, so admin
   (which loads this same file) is untouched. Restored 2026-08-10 — the
   status pill and slide-to-confirm bar are both still live on the Sales
   tab; wireSwipe drags .net-swipe-thumb in real pixels against
   getBoundingClientRect(), so this is a functional restore, not cosmetic. */
/* ==================================================================== */

/* The status chip. ONE colour language across the whole app, so a salesman
   learns it once: amber = we still have work to do, blue = the customer's
   move, green = finished. Same three tones drive the swipe bar below.
   Built on the same pill geometry as .badge but not on .badge itself —
   .badge is shared, and the guardrail says do not restyle shared classes. */
.net-ostat { display:inline-block; font-size:11px; font-weight:700; padding:2px 8px; border-radius:999px; letter-spacing:.2px; }
.net-ostat-prep  { background:#fdf1e3; color:var(--warn); }
.net-ostat-ready { background:var(--primary-soft); color:var(--primary); }
.net-ostat-done  { background:var(--good-soft); color:var(--good); }

/* Slide-to-confirm (replaces tap + confirm sheet for status changes).
   46px tall so the thumb clears the 44px touch-target floor. The track is
   tinted with the status it moves TO, and the fill grows under the thumb so
   the colour arriving IS the colour the order becomes. */
.net-swipe { position:relative; height:46px; margin-top:10px; border-radius:12px; overflow:hidden;
  background:var(--bg); border:1px solid var(--line); user-select:none; -webkit-user-select:none; touch-action:pan-y; }
.net-swipe-fill { position:absolute; left:0; top:0; bottom:0; width:38px; border-radius:12px 0 0 12px; transition:width .18s ease; }
.net-swipe-label { position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-size:13.5px; font-weight:700; color:var(--muted); pointer-events:none; padding-left:34px; transition:opacity .12s linear; }
.net-swipe-thumb { position:absolute; left:4px; top:4px; width:38px; height:36px; margin:0; padding:0;
  display:flex; align-items:center; justify-content:center;
  border:none; border-radius:9px; background:#fff; color:var(--ink);
  font-size:20px; font-weight:800; line-height:1; font-family:inherit; cursor:grab;
  box-shadow:0 1px 3px rgba(16,25,40,.22); transition:transform .18s ease;
  /* The drag is horizontal; without this the list scrolls underneath it. */
  touch-action:none; }
.net-swipe-thumb:active { cursor:grabbing; }
/* Mid-drag the thumb must track the finger exactly — no easing, or it lags. */
.net-swipe.dragging .net-swipe-thumb, .net-swipe.dragging .net-swipe-fill { transition:none; }
/* Fired: latched at full width while the write is in flight. */
.net-swipe.busy { opacity:.65; }
.net-swipe.busy .net-swipe-thumb { cursor:default; }
.net-swipe-ready .net-swipe-fill { background:var(--primary-soft); }
.net-swipe-ready .net-swipe-thumb { color:var(--primary); }
.net-swipe-ready.busy .net-swipe-fill { background:var(--primary); }
.net-swipe-done .net-swipe-fill { background:var(--good-soft); }
.net-swipe-done .net-swipe-thumb { color:var(--good); }
.net-swipe-done.busy .net-swipe-fill { background:var(--good); }

/* The "⋯" order-options sheet: full-width stacked actions, destructive last. */
.net-acts { display:flex; flex-direction:column; gap:8px; margin:14px 0 6px; }
.net-acts .btn.net-act { margin-top:0; }

/* ==================================================================== */
/* THE NEW-SALE BILL (2026-08-10) — the four-step Sale/Money/Cost/Check
   form. SHOP-SCOPED .net-* ONLY, per the guardrail above: this file is
   shared with /admin/, so nothing here restyles a shared class and no
   bare :root variable is added. Both custom properties are named
   --net-* rather than bare for exactly that reason.

   THE STICKY STACK, bottom to top:
     item bar (3) < step rail (4) < money footer (5) < appbar (20)

   BUG-252 (2026-08-10, fixed same day): stacking TWO sticky siblings by
   giving both `top: 0` does NOT make the second one land below the first.
   Sticky positioning is computed per-element from the scrolling ancestor's
   edge — it has no notion of a sibling occupying that space first. With
   `.net-rail { top: 0 }`, the rail raced the appbar for the SAME y=0 and
   lost only on paint order (z-index): the rail's box was still placed at
   [0, railHeight], with its own top portion hidden behind the opaque
   appbar. The item bar's offset (--net-stick-top, correctly measured as
   appbar height + rail height) was therefore aimed at the position the
   rail SHOULD have vacated, not the position the rail was actually in —
   leaving a dead gap between the rail's real bottom edge and the pinned
   item bar exactly `appbar height` tall. Confirmed via
   test/manual-sticky-check.html (gap == 68px == appbar 66 + slack 2 at
   375x812, before the fix; 0px after).

   FIX: give the rail a REAL offset too — `top: var(--net-appbar-h, 0px)`,
   the appbar's own measured height, set by the SAME syncSticky() call that
   sets --net-stick-top. The rail now sticks flush under the appbar instead
   of underneath it, so --net-stick-top (appbar + rail) lands the item bar
   flush under the rail with no gap. Kept as a JS measurement rather than a
   fixed px value because the appbar's height is only known at runtime (the
   shop-name span wraps to two lines on narrow phones — see the "toggle
   2-row appbar" case in the harness above).

   Verified clean of the traps that silently kill position:sticky — .wrap,
   .card, #pane-new and .net-bill set no overflow, transform, filter or
   contain, and `body` sets no overflow-x at all (so the "clip, never
   hidden" rule is already satisfied by absence; body is NOT touched). */
/* ==================================================================== */
.net-rail { display: flex; gap: 6px; position: sticky; top: var(--net-appbar-h, 0px); z-index: 4;
  background: var(--bg); margin: 0 -16px; padding: 12px 16px 8px; }
.net-rail button { flex: 1; min-width: 0; border: 1px solid var(--line); background: #fff; color: var(--muted);
  border-radius: 10px; padding: 8px 4px; font-family: inherit; font-size: 11px; font-weight: 700; cursor: pointer;
  text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.net-rail button.on { background: var(--primary); border-color: var(--primary); color: #fff; }
.net-rail button.done { background: var(--primary-soft); border-color: #cddcff; color: var(--primary); }
.net-rail button:disabled { opacity: .55; cursor: default; }

/* THE BILL: one container, items are SECTIONS inside it separated by a hairline, each with a
   sticky item bar carrying that item's handle. Exactly one bar is pinned at a time because
   each bar is sticky inside its OWN section, so item 2's bar pushes item 1's out as it
   arrives (owner: "it does feel whole bills not some item's that gotta be scrolled to get
   clicked"). .net-item MUST stay position:relative for that containment. */
.net-bill { border: 1px solid var(--line); border-radius: 14px; background: #fff; margin: 2px 0 0; }
.net-item { position: relative; }
.net-item + .net-item { border-top: 1px solid var(--line); }
.net-ibar { position: sticky; top: var(--net-stick-top, 0px); z-index: 3; display: flex; align-items: center; gap: 8px;
  padding: 9px 12px; background: #eef1f4; border-bottom: 1px solid var(--line); }
.net-item:first-child > .net-ibar { border-radius: 13px 13px 0 0; }
.net-ibar-no { font-weight: 800; font-size: 13px; color: #3d4551; white-space: nowrap; }
.net-ibar-sub { flex: 1; min-width: 0; font-weight: 600; font-size: 12px; color: #5b6472; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.net-ibar-amt { font-weight: 700; font-size: 13px; color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
.net-ibar-x { border: 0; background: none; color: #7a8494; font-size: 16px; line-height: 1; padding: 4px 0 4px 8px; cursor: pointer; font-family: inherit; flex: none; }
.net-ibody { padding: 0 12px 14px; }
.net-ibody > label:first-child { margin-top: 12px; }
.net-item.net-flag > .net-ibar { background: var(--danger-soft); }
.net-item.net-flag > .net-ibar .net-ibar-no,
.net-item.net-flag > .net-ibar .net-ibar-sub { color: var(--danger); }
.net-billfoot { padding: 10px 12px; border-top: 1px dashed var(--line); }
.net-billfoot .add-line-btn { margin: 0; }

.net-teach { margin: 10px 0 2px; font-size: 13px; color: var(--muted); font-weight: 600; }
.net-back { display: block; width: 100%; text-align: center; background: none; border: none; color: var(--primary);
  font-family: inherit; font-weight: 600; font-size: 14px; padding: 10px; margin-top: 6px; cursor: pointer; }
.net-costtot { margin: 0 0 8px; padding: 12px 14px; border-radius: 12px; background: #f4f6f8; border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between; font-weight: 700; font-size: 15px; color: #5b6472; }
.net-costtot strong { color: var(--ink); font-weight: 800; }

/* bug-248: the HELD-SHUT fitting row on a line that already carries recorded fitting money.
   Amber, not red — nothing is wrong, the money is simply already counted. */
.net-guard { background: #fff8ee; border: 1px solid #f3d9b0; }
.net-guard .net-cost-row-head { color: var(--warn); }
.net-guard-t { font-size: 13px; color: var(--warn); font-weight: 600; margin: 2px 0 8px; }
.net-guard-btn { width: 100%; background: #fff; border: 1.5px solid #f3d9b0; color: var(--warn); border-radius: 10px;
  padding: 10px; font-family: inherit; font-weight: 700; font-size: 13px; cursor: pointer; }

/* .msg.warn — amber. A non-blocking nudge is not an error, and it is not information either.
   Purely additive: `warn` appears in admin.js ZERO times (checked, not assumed), so this
   combination currently renders base-.msg-only and nothing existing changes appearance. */
.msg.warn { background: #fff8ee; color: var(--warn); border: 1px solid #f3d9b0; }


/* THE RECEIPT IS STEP TWO (2026-08-11). The Done screen now carries an ORDERED PAIR: the
   WhatsApp update button, then the receipt beneath it. The subordination has to be VISIBLE
   or the mechanism (message first, so the customer is top of WhatsApp's send-to list) reads
   as two equal buttons and the order gets ignored. So: an indent rail, a smaller caption
   above the button, and the button itself left on the existing `.btn.subtle` look rather
   than a new one — a THIRD button weight on one screen would be noise. Shop-scoped `.net-*`
   names, per the shared-stylesheet rule: style.css also serves /admin/. */
.net-step2 { margin-top: 10px; padding-left: 14px; border-left: 2px solid var(--line); text-align: left; }
.net-step2-lbl { font-size: 12px; line-height: 1.4; color: var(--muted); margin: 0 0 6px; }
.net-step2 .btn { margin-top: 0; }
/* PRINT sits under the send button and is deliberately quieter (2026-08-11): sending the image
   is the daily action and printing is the rare one, so it keeps the existing `.btn.ghost`
   weight and just gets its own top gap and a smaller face. Still a full-width tap target —
   quieter is not smaller-to-hit. */
.net-step2-alt { margin-top: 8px !important; font-size: 14px; }

/* ---- THE PRINT SHEET HOST (2026-08-11 round 3) -----------------------------------------
   The receipt is PRINTED by rendering it as HTML into #netPrintHost inside the live document
   and calling window.print() on the top-level window — the one mechanism that opens a real
   print dialog on Android Chrome, which has no inline PDF viewer and therefore turned the old
   "open the PDF" path into a silent download (bug-256).

   THESE RULES OWN VISIBILITY ONLY. Everything about how the receipt LOOKS lives in receipt.js
   (TYPOGRAPHER C emits its own <style> alongside the markup), so the sheet's typography sits
   next to the other two typographers instead of in a stylesheet that is SHARED with /admin/.

   SHARED-STYLESHEET SAFETY: both rules are gated on `body.net-printing`, a class only the shop
   app's printReceiptSheet() ever adds and which it removes on `afterprint`. The admin shell
   never sets it, so an admin Ctrl+P is completely unaffected — without the gate, a
   `body > *:not(#netPrintHost)` hide would print a blank page from /admin/. */
#netPrintHost { display: none; }
@media print {
  body.net-printing > *:not(#netPrintHost) { display: none !important; }
  body.net-printing #netPrintHost { display: block !important; }
}
