:root {
  --ink: #172622;
  --muted: #697773;
  --paper: #f5f7f3;
  --surface: #ffffff;
  --line: #dce3de;
  --green: #19744b;
  --green-soft: #dcefe4;
  --blue: #286b8b;
  --blue-soft: #dcebf2;
  --coral: #c75d45;
  --coral-soft: #f5e2dd;
  --yellow: #aa7c17;
  --yellow-soft: #f5edcf;
  --sidebar: #152822;
  font-family: Aptos, Calibri, sans-serif;
  color: var(--ink);
  background: var(--paper);
}

* { box-sizing: border-box; }
html { overflow-x: hidden; scroll-behavior: smooth; }
body { margin: 0; min-height: 100vh; overflow-x: hidden; background: var(--paper); }
.login-screen { position: fixed; inset: 0; z-index: 50; display: grid; place-items: center; background: var(--paper); }
.login-screen[hidden] { display: none; }
.login-card { width: 320px; padding: 28px; background: #fff; border: 1px solid #e2e8f0; border-radius: 14px; display: flex; flex-direction: column; gap: 12px; box-shadow: 0 12px 40px rgba(0,0,0,.08); }
.login-card .brand-mark { margin: 0 auto; }
.login-card h1 { margin: 0; text-align: center; font-size: 20px; }
.login-card p { margin: 0; text-align: center; color: #64748b; }
.login-card input { padding: 12px; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 14px; }
.login-card button { padding: 12px; border: 0; border-radius: 8px; background: #1a7f4b; color: #fff; font-weight: 600; cursor: pointer; }
.login-msg { min-height: 18px; font-size: 13px; text-align: center; }
.login-msg.error { color: #c0392b; }
.session-block { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line); }
.session-block .admin-hint { margin: 0 0 12px; text-align: left; }
.session-block .simulate-button { width: 100%; }
.session-block .login-msg { margin-top: 10px; }
body.auth-locked { overflow: hidden; }
button, select { font: inherit; }
button { cursor: pointer; }

.sidebar {
  position: fixed; inset: 0 auto 0 0; width: 220px; padding: 28px 18px;
  display: flex; flex-direction: column; background: var(--sidebar); color: #eef6f1;
}
.brand { display: flex; align-items: center; gap: 12px; color: inherit; text-decoration: none; font-family: Bahnschrift, sans-serif; font-weight: 700; line-height: 1.05; }
.brand-mark { width: 38px; height: 38px; display: grid; place-items: center; border: 2px solid #67bc8c; border-radius: 50%; }
.brand-mark::before, .brand-mark span { content: ""; width: 18px; height: 8px; border: 2px solid #eef6f1; border-left: 0; border-right: 0; transform: rotate(-12deg); }
nav { display: grid; gap: 8px; margin-top: 54px; }
.nav-item { min-height: 44px; padding: 0 12px; display: flex; align-items: center; gap: 12px; color: #afc1ba; text-decoration: none; border-radius: 6px; }
.nav-item:hover, .nav-item.active { color: white; background: #254038; }
.nav-item svg { width: 19px; }
.sidebar-foot { margin-top: auto; padding: 14px 12px; display: flex; align-items: center; gap: 9px; color: #afc1ba; font-size: 13px; border-top: 1px solid #30483f; }
.live-dot { width: 8px; height: 8px; display: inline-block; border-radius: 50%; background: #54c77d; box-shadow: 0 0 0 4px rgb(84 199 125 / 14%); }

main { min-width: 0; margin-left: 220px; padding: 30px clamp(24px, 4vw, 64px) 60px; }
.topbar { display: flex; align-items: center; justify-content: space-between; gap: 24px; }
.eyebrow { margin: 0 0 5px; color: var(--muted); font: 700 12px Bahnschrift, sans-serif; text-transform: uppercase; letter-spacing: .08em; }
h1, h2 { margin: 0; font-family: Bahnschrift, sans-serif; letter-spacing: 0; }
h1 { font-size: clamp(26px, 3vw, 38px); }
h2 { font-size: 21px; }
.top-actions { display: flex; align-items: center; gap: 16px; }
.icon-button { width: 42px; height: 42px; display: grid; place-items: center; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--ink); }
.icon-button:hover { border-color: var(--green); color: var(--green); }
.icon-button svg { width: 18px; }
.icon-button.spinning svg { animation: spin .65s linear infinite; }
.install-button { height: 42px; padding: 0 14px; display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--green); border-radius: 6px; color: var(--green); background: var(--surface); font-weight: 700; }
.install-button:hover { color: white; background: var(--green); }
.install-button svg { width: 17px; }
.install-button[hidden] { display: none; }
.operator { display: flex; align-items: center; gap: 10px; }
.operator > span { width: 40px; height: 40px; display: grid; place-items: center; border-radius: 50%; background: var(--coral); color: white; font-weight: 700; }
.operator strong, .operator small { display: block; }
.operator strong { font-size: 14px; }.operator small { color: var(--muted); font-size: 12px; margin-top: 2px; }

.summary { margin: 32px 0 38px; display: grid; grid-template-columns: repeat(4, minmax(150px, 1fr)); border: 1px solid var(--line); background: var(--surface); }
.metric { min-height: 112px; padding: 22px; display: flex; align-items: center; gap: 16px; border-right: 1px solid var(--line); }
.metric:last-child { border-right: 0; }
.metric-icon { width: 42px; height: 42px; flex: 0 0 42px; display: grid; place-items: center; border-radius: 6px; }
.metric-icon svg { width: 20px; }
.metric small, .metric strong { display: block; }
.metric small { color: var(--muted); font-size: 13px; }
.metric strong { margin-top: 4px; font: 700 27px Bahnschrift, sans-serif; }
.accent-green .metric-icon { color: var(--green); background: var(--green-soft); }
.accent-blue .metric-icon { color: var(--blue); background: var(--blue-soft); }
.accent-coral .metric-icon { color: var(--coral); background: var(--coral-soft); }
.accent-yellow .metric-icon { color: var(--yellow); background: var(--yellow-soft); }

.workspace, .accounting-section, .activity-section { min-width: 0; scroll-margin-top: 24px; }
.workspace { contain: inline-size; overflow: hidden; }
.section-heading { margin-bottom: 16px; display: flex; align-items: end; justify-content: space-between; gap: 20px; }
.sync-note { color: var(--muted); font-size: 13px; }
.sync-note .live-dot { margin-right: 5px; }
.table-wrap { width: 100%; max-width: 100%; overflow-x: auto; border: 1px solid var(--line); background: var(--surface); }
table { width: 100%; min-width: 820px; border-collapse: collapse; }
th { padding: 13px 18px; text-align: left; color: var(--muted); background: #edf1ed; font: 700 11px Bahnschrift, sans-serif; text-transform: uppercase; letter-spacing: .06em; }
td { height: 72px; padding: 12px 18px; border-top: 1px solid var(--line); font-size: 14px; }
.machine-name { display: flex; align-items: center; gap: 11px; font-weight: 700; }
.machine-symbol { width: 34px; height: 34px; display: grid; place-items: center; border-radius: 6px; color: var(--blue); background: var(--blue-soft); }
.machine-symbol svg { width: 18px; }
.secondary { display: block; margin-top: 3px; color: var(--muted); font-size: 12px; }
.status { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 700; }
.status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--green); }
.status.queued::before { background: var(--yellow); }
.status.offline::before { background: #9aa5a1; }
select { min-width: 100px; height: 36px; padding: 0 30px 0 10px; border: 1px solid var(--line); border-radius: 5px; color: var(--ink); background: white; }
.pay-button { min-width: 112px; height: 38px; border: 0; border-radius: 5px; color: white; background: var(--green); font-weight: 700; }
.pay-button:hover { background: #105d3a; }
.pay-button:disabled { cursor: wait; opacity: .55; }
.payment-actions { display: flex; align-items: center; gap: 6px; }
.simulate-button { width: 42px; height: 38px; border: 1px solid var(--line); border-radius: 5px; color: var(--muted); background: white; font-size: 12px; font-weight: 700; }
.simulate-button:hover { color: var(--green); border-color: var(--green); }
.loading-row, .empty-state { padding: 32px; text-align: center; color: var(--muted); }

.accounting-section { margin-top: 38px; }
.accounting-summary { display: grid; grid-template-columns: repeat(3, minmax(150px, 1fr)); border: 1px solid var(--line); background: var(--surface); }
.accounting-summary article { min-height: 92px; padding: 18px; border-right: 1px solid var(--line); }
.accounting-summary article:nth-child(3n) { border-right: 0; }
.accounting-summary article:nth-child(-n+3) { border-bottom: 1px solid var(--line); }
.accounting-summary small, .accounting-summary strong { display: block; }
.accounting-summary small { color: var(--muted); font-size: 12px; }
.accounting-summary strong { margin-top: 9px; font: 700 21px Bahnschrift, sans-serif; }
.price-list { margin: 18px 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.price-item { min-height: 74px; padding: 14px 16px; display: flex; align-items: center; justify-content: space-between; gap: 16px; border-left: 4px solid var(--green); background: var(--surface); }
.price-item:nth-child(2) { border-left-color: var(--yellow); }
.price-item:nth-child(3) { border-left-color: var(--blue); }
.price-item span:last-child { text-align: right; }
.price-item strong, .price-item small { display: block; }
.price-item small { margin-top: 4px; color: var(--muted); font-size: 11px; }
.accounting-table { min-width: 700px; }
.amount-column { text-align: right; }
.provider { display: inline-block; min-width: 54px; padding: 4px 7px; border-radius: 4px; text-align: center; color: var(--green); background: var(--green-soft); font-size: 11px; font-weight: 700; }
.provider.simulated { color: var(--muted); background: #edf1ed; }
.provider.aba { color: var(--blue); background: var(--blue-soft); }
.provider.manual { color: var(--yellow); background: var(--yellow-soft); }

.activity-section { margin-top: 38px; }
.activity-list { border-top: 1px solid var(--line); }
.activity-item { min-height: 58px; padding: 12px 4px; display: grid; grid-template-columns: 34px 1fr auto; align-items: center; gap: 12px; border-bottom: 1px solid var(--line); }
.activity-icon { width: 32px; height: 32px; display: grid; place-items: center; color: var(--green); background: var(--green-soft); border-radius: 50%; }
.activity-icon svg { width: 16px; }
.activity-item p { margin: 0; font-size: 14px; }.activity-item time { color: var(--muted); font-size: 12px; }
.toast { position: fixed; right: 24px; bottom: 24px; max-width: min(380px, calc(100vw - 48px)); padding: 13px 18px; border-radius: 6px; color: white; background: var(--ink); box-shadow: 0 12px 30px rgb(23 38 34 / 20%); opacity: 0; transform: translateY(10px); pointer-events: none; transition: .2s ease; }
.toast.visible { opacity: 1; transform: translateY(0); }
.toast.error { background: #923d31; }
.payment-dialog { width: min(420px, calc(100vw - 32px)); padding: 24px; border: 0; border-radius: 8px; color: var(--ink); background: var(--surface); box-shadow: 0 24px 80px rgb(10 26 21 / 35%); }
.payment-dialog::backdrop { background: rgb(10 26 21 / 70%); backdrop-filter: blur(3px); }
.payment-dialog-head { display: flex; align-items: center; justify-content: space-between; }
.qr-frame { width: min(320px, 100%); aspect-ratio: 1; margin: 22px auto 14px; display: grid; place-items: center; overflow: hidden; border: 1px solid var(--line); background: white; }
.qr-frame img { width: 100%; height: 100%; object-fit: contain; }
.payment-amount { text-align: center; font: 700 28px Bahnschrift, sans-serif; }
.payment-meta { margin: 10px 0 18px; display: flex; justify-content: center; gap: 14px; color: var(--muted); font-size: 13px; }
.payment-meta span + span { padding-left: 14px; border-left: 1px solid var(--line); font-variant-numeric: tabular-nums; }
.payment-state { min-height: 44px; padding: 0 14px; display: flex; align-items: center; justify-content: center; gap: 10px; border-radius: 5px; color: var(--yellow); background: var(--yellow-soft); font-size: 13px; font-weight: 700; text-align: center; }
.payment-state.paid { color: var(--green); background: var(--green-soft); }
.payment-state.expired, .payment-state.failed { color: #923d31; background: var(--coral-soft); }
.payment-state.expired .live-dot, .payment-state.failed .live-dot { background: #923d31; box-shadow: none; }
.offline-banner { position: fixed; z-index: 10; inset: auto 0 0 220px; padding: 9px 18px; text-align: center; color: white; background: #923d31; font-size: 13px; font-weight: 700; }
.offline-banner[hidden] { display: none; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
@keyframes spin { to { transform: rotate(360deg); } }

@media (max-width: 900px) {
  .sidebar { position: static; width: auto; height: 68px; padding: 0 20px; flex-direction: row; align-items: center; }
  .brand br, .sidebar-foot { display: none; }
  nav { margin: 0 0 0 auto; display: flex; }
  .nav-item { padding: 0 10px; }.nav-item span { display: none; }
  main { margin-left: 0; padding: 24px 20px 48px; }
  .offline-banner { left: 0; }
  .summary { grid-template-columns: repeat(2, 1fr); }
  .accounting-summary { grid-template-columns: repeat(2, 1fr); }
  .accounting-summary article { border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); }
  .accounting-summary article:nth-child(2n) { border-right: 0; }
  .accounting-summary article:nth-last-child(-n+2) { border-bottom: 0; }
  .price-list { grid-template-columns: 1fr; }
  .metric:nth-child(2) { border-right: 0; }.metric:nth-child(-n+2) { border-bottom: 1px solid var(--line); }
}
@media (max-width: 560px) {
  .brand > span:last-child { display: none; }
  .operator div, .sync-note { display: none; }
  .install-button { width: 42px; padding: 0; justify-content: center; }
  .install-button span { display: none; }
  .topbar { align-items: flex-start; }
  .summary { margin-top: 24px; }
  .metric { min-height: 92px; padding: 15px; gap: 11px; }
  .metric-icon { width: 36px; height: 36px; flex-basis: 36px; }
  .metric strong { font-size: 22px; }
  .activity-item { grid-template-columns: 34px 1fr; }.activity-item time { grid-column: 2; }
}

/* ---- Multi-tenant administration ---- */
.org-switcher {
  background: var(--surface, #fff);
  border: 1px solid var(--line, #e5e7eb);
  border-radius: 8px;
  color: inherit;
  font: inherit;
  font-size: 13px;
  padding: 8px 10px;
  max-width: 200px;
}
.admin-section { margin-top: 28px; }
.admin-section h3 { font-size: 15px; font-weight: 600; margin: 4px 0 0; }
.admin-form {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 12px 0 20px;
}
.admin-form input,
.admin-form select {
  border: 1px solid var(--line, #e5e7eb);
  border-radius: 8px;
  background: var(--surface, #fff);
  color: inherit;
  font: inherit;
  font-size: 13px;
  padding: 8px 10px;
  min-width: 180px;
  flex: 1 1 180px;
}
.admin-form button { flex: 0 0 auto; }
.machine-price-form { align-items: center; }

/* Dryer run time: the pulse economics (how many pulses a package sends, and what one pulse buys).
   A small GRID rather than a flex row, because `.admin-form` children go full-width below 640px
   while a wrapping row of four inputs plus a button crams between 640 and 720. minmax(0, 1fr) is
   this project's existing fix for a min-content blowout on a narrow screen. */
.pulse-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.pulse-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted, #6b7280); }
.pulse-field input { width: 100%; min-width: 0; }

/* The derived line is the whole point of the card: it says what the customer will actually get. It
   turns coral and says "no price yet" when a change would re-key a package, because that is the one
   moment money is at stake. Tabular figures so the numbers do not jump as they are typed. */
.pulse-derived { margin: 2px 0 0; font-size: 12px; color: var(--muted, #6b7280); font-variant-numeric: tabular-nums; }
.pulse-derived.pulse-rekeyed { color: var(--danger, #c0392b); font-weight: 600; }
.pulse-newprices:empty { display: none; }
.pulse-warning { margin: 6px 0; font-size: 12px; color: var(--danger, #c0392b); }
.pulse-details { width: 100%; }
.pulse-details summary { cursor: pointer; list-style: none; }
.pulse-details summary::-webkit-details-marker { display: none; }
.pulse-chip { display: inline-block; padding: 3px 8px; border-radius: var(--radius-sm); background: var(--chip-bg, #eef2f7); font-size: 12px; }
.pulse-chip.own { background: var(--green-soft, #e6f4ea); font-weight: 600; }

@media (max-width: 400px) {
  /* Three pulse boxes in a 280px panel leave each about 84px, which is narrower than a 2-digit
     number plus its spinner. Stacked, and the derived line gets room to breathe instead of wrapping
     mid-token. */
  .pulse-grid { grid-template-columns: 1fr; }
  .pulse-derived { line-height: 1.6; }
}
.machine-price-name { flex: 0 0 160px; font-weight: 600; }

/* =====================================================================
   RESPONSIVE / MOBILE-FIRST UX LAYER (appended layer - overrides above)
   ===================================================================== */
:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
button, select, input, textarea { font: inherit; letter-spacing: inherit; }
body { -webkit-tap-highlight-color: transparent; }
button:active { transform: translateY(1px); }
.icon-button, .install-button, .simulate-button, .pay-button { transition: background .15s ease, color .15s ease, border-color .15s ease; }

/* Nicer container edges (desktop keeps real table layout) */
.table-wrap { border-radius: 10px; }
.table-wrap table { border-collapse: separate; border-spacing: 0; }

/* ---------- <=980px: shell turns phone-style (bottom tab nav) ---------- */
@media (max-width: 980px) {
  .sidebar {
    position: fixed; inset: auto 0 0 0; width: auto; height: auto;
    padding: 6px max(10px, env(safe-area-inset-right)) max(8px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left));
    flex-direction: row; align-items: center; justify-content: center;
    border-top: 1px solid #2d463d; background: var(--sidebar);
    box-shadow: 0 -6px 24px rgb(12 30 24 / 18%); z-index: 45;
  }
  .sidebar .brand, .sidebar .brand br, .sidebar-foot { display: none; }
  .sidebar nav { display: flex; width: 100%; margin: 0; gap: 2px; }
  .sidebar .nav-item {
    flex: 1 1 0; min-width: 0; min-height: 54px; padding: 5px 2px;
    flex-direction: column; justify-content: center; gap: 3px;
    border-radius: 10px; text-align: center; color: #b9c8c0;
  }
  .sidebar .nav-item span { display: block; font-size: 10px; line-height: 1.1; }
  .sidebar .nav-item svg { width: 20px; height: 20px; flex: none; }

  main { margin-left: 0; padding: calc(16px + env(safe-area-inset-top)) 18px calc(96px + env(safe-area-inset-bottom)); }
  .workspace, .accounting-section, .activity-section, .admin-section { scroll-margin-top: 84px; }

  header.topbar {
    position: sticky; top: 0; z-index: 40; padding: 12px 0 14px;
    background: var(--paper); border-bottom: 1px solid transparent;
  }
  h1 { font-size: clamp(20px, 5vw, 26px); }
  .summary { grid-template-columns: repeat(2, 1fr); }
  .metric { border-right: 0; border-bottom: 1px solid var(--line); }
  .metric:nth-child(odd) { border-right: 1px solid var(--line); }
  .metric:nth-last-child(-n+2) { border-bottom: 0; }
  .accounting-summary { grid-template-columns: repeat(2, 1fr); }
  .accounting-summary article { border-right: 0; border-bottom: 1px solid var(--line); }
  .accounting-summary article:nth-child(odd) { border-right: 1px solid var(--line); }
  .accounting-summary article:nth-last-child(-n+2) { border-bottom: 0; }
  .price-list { grid-template-columns: 1fr; }
  .offline-banner { inset: auto 0 0 0; bottom: 64px; z-index: 44; }
  .toast { z-index: 60; }

  /* scroll-jump anchors should not hide under the sticky topbar */
  section[id] { scroll-margin-top: 90px; }
}

/* ---------- <=720px: tables collapse into labelled cards ---------- */
@media (max-width: 720px) {
  .table-wrap { overflow: visible; border: 0; background: transparent; }
  .table-wrap > table { display: block; width: 100%; min-width: 0; }
  .table-wrap thead { display: none; }
  .table-wrap tbody { display: block; }
  .table-wrap tbody tr {
    display: block; margin: 0 0 14px; padding: 0;
    border: 1px solid var(--line); border-radius: 12px; background: var(--surface);
    overflow: hidden; box-shadow: 0 1px 3px rgb(16 42 33 / 6%);
  }
  .table-wrap tbody tr td {
    display: flex; flex-direction: column; align-items: stretch;
    gap: 5px; width: 100%; height: auto; min-height: 0;
    padding: 10px 16px; border: 0; border-bottom: 1px solid var(--line);
  }
  .table-wrap tbody tr > td:last-child { border-bottom: 0; }
  .table-wrap tbody tr td::before {
    content: attr(data-label); flex: none;
    color: var(--muted); font: 700 10px Bahnschrift, sans-serif;
    text-transform: uppercase; letter-spacing: .06em; line-height: 1.3;
  }
  .table-wrap tbody tr td:empty { display: none; }

  /* Action cells (buttons / program picker) flow as a row under no label */
  .table-wrap td.cell-actions { flex-direction: row; flex-wrap: wrap; gap: 8px; align-items: center; padding-top: 12px; padding-bottom: 12px; }
  .table-wrap td.cell-actions::before { content: none; }

  /* Make card content fill the row nicely */
  .table-wrap tbody tr td > * { width: 100%; }
  .machine-controls, .payment-actions { flex-wrap: wrap; }
  .machine-controls .simulate-button, .payment-actions .simulate-button { width: auto; min-width: 78px; height: 40px; }
  .table-wrap select { width: 100%; min-width: 0; }
  .machine-name { align-items: center; gap: 10px; font-size: 15px; }
  .machine-symbol { width: 38px; height: 38px; flex: 0 0 38px; }
  .accounting-table, .table-wrap table { min-width: 0; }
  .amount-column { text-align: left; }
  .loading-row { padding: 24px 16px; }
}

/* ---------- <=640px: full-width touch forms + phone sizing ---------- */
@media (max-width: 640px) {
  main { padding: calc(12px + env(safe-area-inset-top)) 14px calc(104px + env(safe-area-inset-bottom)); }
  .topbar { align-items: flex-start; }
  .topbar .eyebrow, #today { display: none; }
  .top-actions { flex-wrap: wrap; gap: 8px; }
  .operator > span { width: 36px; height: 36px; }
  .operator div, .sync-note { display: none; }
  .install-button { width: 40px; height: 40px; padding: 0; justify-content: center; }
  .install-button span { display: none; }
  input, select, textarea { font-size: 16px; }
  select { height: 44px; }
  .metric { min-height: 96px; }
  .summary { margin-top: 18px; }
  .activity-item { grid-template-columns: 34px 1fr; }
  .activity-item time { grid-column: 2; }
  .section-heading { align-items: flex-start; flex-direction: column; gap: 4px; }

  /* Admin forms stack into one column, full-width fields */
  .admin-form { flex-direction: column; }
  .admin-form input, .admin-form select, .admin-form button { width: 100%; flex: none; min-width: 0; min-height: 44px; }
  .machine-price-name { flex: none; }

  /* Modals become bottom sheets */
  .payment-dialog, #password-dialog {
    position: fixed; inset: auto 0 0 0; margin: 0; width: 100%; max-width: 100%;
    max-height: 92vh; overflow: auto; border-radius: 18px 18px 0 0;
    padding: 20px 18px calc(20px + env(safe-area-inset-bottom));
  }
  .qr-frame { width: min(70vw, 300px); }
  .payment-amount { font-size: 24px; }
}

/* ---------- small phones ---------- */
@media (max-width: 380px) {
  .metric strong { font-size: 19px; }
  .table-wrap tbody tr td { padding: 10px 12px; gap: 10px; }
  .login-card { width: 100%; max-width: 340px; }
  .brand-mark { width: 34px; height: 34px; }
}

/* =====================================================================
   Machine row controls - single tidy Controls column (overrides)
   ===================================================================== */
.machine-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; width: 100%; }
.test-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; width: 100%; margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line); }
.group-label { flex: 0 0 100%; color: var(--muted); font: 700 10px Bahnschrift, sans-serif; text-transform: uppercase; letter-spacing: .06em; }
.btn-ctrl, .btn-test {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  flex: 1 1 auto; min-width: 84px; height: 36px; padding: 0 10px;
  border: 1px solid var(--line); border-radius: 7px; background: #fff;
  color: var(--ink); font-size: 13px; font-weight: 600;
  white-space: nowrap; cursor: pointer; line-height: 1;
}
.btn-ctrl svg, .btn-test svg { width: 15px; height: 15px; flex: none; }
.btn-ctrl:hover { border-color: var(--green); color: var(--green); }
.btn-ctrl.start { background: var(--green); border-color: var(--green); color: #fff; }
.btn-ctrl.start:hover { background: #105d3a; border-color: #105d3a; color: #fff; }
.btn-ctrl.danger { color: var(--coral); }
.btn-ctrl.danger:hover { border-color: var(--coral); color: var(--coral); background: var(--coral-soft); }
.btn-test { background: #f2f5f2; color: var(--muted); font-size: 12px; }
.btn-test:hover { border-color: var(--blue); color: var(--blue); background: var(--blue-soft); }

/* Text action buttons used in admin/tables become text-aware (not 42px squares) */
.simulate-button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; width: auto; min-width: 0; padding: 0 12px; white-space: nowrap; }
.simulate-button svg { width: 15px; height: 15px; }

/* ---- Per washer/dryer payment keys (org admin) ---- */
.admin-hint { margin: 2px 0 12px; color: var(--muted); font-size: 13px; }
.machine-pay { margin: 0 0 10px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); padding: 10px 14px; }
.machine-pay[open] { box-shadow: 0 1px 3px rgb(16 42 33 / 6%); }
.machine-pay summary { display: flex; align-items: center; justify-content: space-between; gap: 10px; cursor: pointer; list-style: none; }
.machine-pay summary::-webkit-details-marker { display: none; }
.machine-pay summary::after { content: "▾"; color: var(--muted); flex: none; }
.machine-pay[open] summary::after { content: "▴"; }
.machine-pay .admin-form { margin-top: 12px; }
.pay-state { font-size: 12px; font-weight: 700; white-space: nowrap; }
.pay-state.org { color: var(--muted); }
/* Offline alerts are muted for this device: visible, but plainly not reporting. */
.pay-state.muted { color: var(--muted); background: var(--line); padding: 2px 8px; border-radius: 999px; }
.pay-state.own { color: var(--green); }
.simulate-button.danger-ghost { color: #b23b2e; border-color: #e3c5c0; background: #fff; }
.simulate-button.danger-ghost:hover { border-color: #b23b2e; color: #b23b2e; background: #f5e2dd; }
@media (max-width: 640px) {
  .machine-pay summary { flex-wrap: wrap; }
}

/* Kill-step test group (owner Test section) */
.kill-test-group { flex: 0 0 100%; display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; padding-top: 8px; border-top: 1px dashed var(--line); }
.kill-test-group::before { content: "Kill steps (test)"; width: 100%; color: var(--muted); font: 700 10px Bahnschrift, sans-serif; text-transform: uppercase; letter-spacing: .06em; }
.kill-test-group .btn-test { flex: 0 0 auto; min-width: 0; }

/* ---- QOL: machine search + auto-refresh ---- */
.section-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.search-input { height: 40px; min-width: 190px; padding: 0 12px; border: 1px solid var(--line); border-radius: 8px; background: #fff; color: var(--ink); font-size: 14px; }
.search-input:focus { outline: 2px solid var(--green); outline-offset: 1px; border-color: transparent; }
.install-button.active { background: var(--green); color: #fff; border-color: var(--green); }
.btn-ctrl:disabled, .btn-test:disabled, .simulate-button:disabled, .pay-button:disabled { opacity: .45; cursor: not-allowed; }
.btn-ctrl:disabled:active, .btn-test:disabled:active { transform: none; }
@media (max-width: 640px) {
  .section-tools { width: 100%; }
  .search-input { flex: 1 1 100%; min-width: 0; }
}

/* =====================================================================
   V3 VISUAL OVERHAUL  (design tokens + component restyle; overrides above)
   ===================================================================== */
:root {
  --ink: #0f1c18;
  --muted: #5c6b66;
  --paper: #f2f6f3;
  --surface: #ffffff;
  --line: #e2e9e4;
  --line-strong: #cfd9d3;
  --green: #17855a;
  --green-strong: #106b47;
  --green-soft: #dff1e7;
  --blue: #2a6f96;
  --blue-soft: #e2eef5;
  --coral: #c2553f;
  --coral-soft: #f9e6e1;
  --yellow: #a97b16;
  --yellow-soft: #f7f0d8;
  --sidebar: #0e211b;
  --radius-lg: 18px;
  --radius: 14px;
  --radius-sm: 10px;
  --shadow-sm: 0 1px 2px rgb(15 28 24 / 5%);
  --shadow: 0 6px 20px rgb(15 28 24 / 7%);
  --shadow-lg: 0 24px 60px rgb(9 24 19 / 28%);
  --ring: 0 0 0 3px rgb(23 133 90 / 18%);
  font-family: ui-sans-serif, "Aptos", "Segoe UI", Calibri, system-ui, sans-serif;
}

* { box-sizing: border-box; }
body {
  margin: 0; min-height: 100vh; color: var(--ink);
  background:
    radial-gradient(1200px 600px at 100% -10%, #e7f2ec 0%, transparent 60%),
    radial-gradient(900px 500px at -10% 110%, #e9eefb 0%, transparent 55%),
    var(--paper);
  -webkit-font-smoothing: antialiased;
}
::selection { background: rgb(23 133 90 / 18%); }
* { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 999px; border: 3px solid transparent; background-clip: content-box; }
*::-webkit-scrollbar-thumb:hover { background: #b8c5be; background-clip: content-box; }

h1, h2, h3, .eyebrow, .metric strong, .accounting-summary strong, .payment-amount { font-family: Bahnschrift, "Aptos Display", ui-sans-serif, sans-serif; letter-spacing: -.01em; }
h1 { font-size: clamp(24px, 2.6vw, 32px); font-weight: 700; }
h2 { font-size: 22px; }
h3 { font-size: 16px; font-weight: 700; }
.eyebrow { color: var(--muted); font-weight: 700; font-size: 11px; letter-spacing: .1em; }

/* ---- Login ---- */
.login-screen { background:
  radial-gradient(800px 500px at 50% -20%, #dcefe4 0%, transparent 60%), var(--paper); }
.login-card { width: min(380px, calc(100vw - 32px)); padding: 32px; border-radius: var(--radius-lg); border: 1px solid var(--line); box-shadow: var(--shadow-lg); gap: 14px; }
.login-card .brand-mark { width: 52px; height: 52px; border-width: 3px; }
.login-card h1 { font-size: 22px; }
.login-card input { padding: 13px 14px; border-radius: var(--radius-sm); border: 1px solid var(--line); font-size: 15px; }
.login-card input:focus { outline: none; border-color: var(--green); box-shadow: var(--ring); }
.login-card button { padding: 13px; border-radius: var(--radius-sm); background: linear-gradient(180deg, var(--green), var(--green-strong)); font-weight: 700; box-shadow: var(--shadow-sm); }
.login-card button:hover { filter: brightness(1.05); }

/* ---- Shell / sidebar ---- */
.sidebar {
  position: fixed; inset: 0 auto 0 0; width: 244px; padding: 26px 16px;
  background: linear-gradient(180deg, #12291f 0%, var(--sidebar) 60%, #0b1a15 100%);
  border-right: 1px solid #1c352b;
}
.brand { padding: 4px 6px; }
.brand-mark { width: 40px; height: 40px; border-width: 2px; border-color: #5fc08c; }
nav { margin-top: 42px; gap: 6px; }
.nav-item { min-height: 46px; padding: 0 14px; border-radius: 12px; color: #a9bcb3; font-weight: 600; font-size: 14px; transition: background .15s, color .15s; }
.nav-item svg { width: 19px; height: 19px; }
.nav-item:hover { color: #fff; background: #1b382e; }
.nav-item.active { color: #fff; background: linear-gradient(180deg, #1e4636, #173a2c); box-shadow: inset 0 0 0 1px #2a5a45, 0 2px 10px rgb(0 0 0 / 18%); }
.sidebar-foot { border-top: 1px solid #1e3a2f; color: #93a89e; font-size: 12px; letter-spacing: .02em; }
.live-dot { background: #4fd88b; box-shadow: 0 0 0 4px rgb(79 216 139 / 16%); }

main { margin-left: 244px; padding: 26px clamp(20px, 3.2vw, 56px) 64px; }
.topbar { gap: 20px; padding-bottom: 16px; border-bottom: 1px solid var(--line); }
.top-actions { gap: 12px; }
.icon-button { width: 42px; height: 42px; border-radius: 12px; border: 1px solid var(--line); background: var(--surface); box-shadow: var(--shadow-sm); }
.icon-button:hover { border-color: var(--green); color: var(--green); box-shadow: var(--ring); }
.install-button { height: 42px; padding: 0 15px; border-radius: 12px; font-weight: 600; }
.operator > span { width: 42px; height: 42px; font-weight: 700; background: linear-gradient(180deg, #d1705a, var(--coral)); box-shadow: var(--shadow-sm); }
.operator small { color: var(--muted); }

/* ---- Metric cards ---- */
.summary { margin: 26px 0 34px; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; border: 0; background: transparent; }
.metric, .metric:nth-child(2), .metric:nth-child(odd), .metric:nth-child(2n), .metric:nth-child(-n+2), .metric:nth-last-child(-n+2) {
  min-height: 116px; padding: 20px; gap: 15px; align-items: center;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);
  box-shadow: var(--shadow-sm); transition: transform .12s ease, box-shadow .18s ease;
}
.metric:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.metric-icon { width: 46px; height: 46px; flex: 0 0 46px; border-radius: 12px; }
.metric-icon svg { width: 22px; height: 22px; }
.metric small { font-size: 12px; text-transform: uppercase; letter-spacing: .07em; font-weight: 700; }
.metric strong { margin-top: 6px; font-size: 30px; font-weight: 700; }

/* ---- Section chrome ---- */
.section-heading { margin: 4px 0 16px; align-items: center; }
.section-heading h2 { font-size: 21px; }
.sync-note { font-size: 12px; font-weight: 600; color: var(--muted); }
.section-tools { gap: 10px; }
.admin-section { margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--line); }
.admin-section h3 { margin: 2px 0 0; }

/* ---- Tables (desktop) ---- */
.table-wrap { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); overflow-x: auto; }
table { border-collapse: separate; border-spacing: 0; }
th { padding: 12px 18px; color: var(--muted); background: #f7faf8; font: 700 11px Bahnschrift, sans-serif; text-transform: uppercase; letter-spacing: .08em; border-bottom: 1px solid var(--line); }
td { height: 66px; padding: 12px 18px; font-size: 14px; border-top: 1px solid var(--line); }
tbody tr:first-child td { border-top: 0; }
tbody tr { transition: background .12s ease; }
tbody tr:hover { background: #fafcfb; }
.machine-symbol { width: 38px; height: 38px; border-radius: 11px; }
.machine-symbol svg { width: 19px; height: 19px; }
.machine-name { gap: 12px; }
.secondary { color: var(--muted); font-size: 11.5px; font-weight: 600; letter-spacing: .02em; }
.status { font-size: 12px; font-weight: 700; letter-spacing: .01em; }
.select, select { border-radius: var(--radius-sm); }

/* ---- Buttons: machine controls & test tools ---- */
.btn-ctrl, .btn-test { border-radius: var(--radius-sm); height: 38px; font-weight: 600; transition: background .14s, border-color .14s, color .14s, box-shadow .14s; }
.btn-ctrl:hover { box-shadow: var(--shadow-sm); }
.btn-ctrl.start { background: linear-gradient(180deg, var(--green), var(--green-strong)); border-color: transparent; }
.btn-ctrl.start:hover { background: linear-gradient(180deg, #1a9062, #0e6443); }
.btn-ctrl.danger { color: var(--coral); }
.btn-ctrl.danger:hover { background: var(--coral-soft); border-color: #e6b3a7; }
.btn-test { background: #f4f7f5; border-color: var(--line); }
.btn-test:hover { background: var(--blue-soft); border-color: #bcd6e6; color: var(--blue); }
.kill-test-group { border-top: 1px dashed var(--line-strong); }
.simulate-button { border-radius: var(--radius-sm); height: 38px; border: 1px solid var(--line); background: var(--surface); font-weight: 600; box-shadow: var(--shadow-sm); }
.simulate-button:hover { border-color: var(--green); color: var(--green); }
.simulate-button.danger-ghost { color: var(--coral); border-color: #ecc7be; box-shadow: none; }
.pay-button { border-radius: var(--radius-sm); background: linear-gradient(180deg, var(--green), var(--green-strong)); }

/* ---- Accounting / price / activity ---- */
.accounting-summary { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; border: 0; background: transparent; }

/* Accounting toolbar + period scope line */
a.install-button { text-decoration: none; }
.ledger-scope { margin: 0 0 14px; color: var(--muted); font-size: 13px; }
.ledger-scope strong { color: var(--ink); }
.ledger-note { margin: 12px 0 18px; color: var(--muted); font-size: 12.5px; }
.accounting-table tfoot td { border-top: 2px solid var(--line-strong); height: auto; padding: 12px 16px; font-size: 13px; font-weight: 700; background: color-mix(in srgb, var(--green) 6%, transparent); }
.accounting-table tfoot strong { font-size: 14px; }
.activity-item time { white-space: nowrap; color: var(--muted); font-size: 12px; }
.activity-more-wrap { display: flex; justify-content: center; padding: 14px 0 4px; }
.activity-more { min-width: 160px; justify-content: center; }
.activity-more svg { transition: transform .15s ease; }
.accounting-summary article,
.accounting-summary article:nth-child(odd),
.accounting-summary article:nth-child(2n),
.accounting-summary article:nth-child(-n+3),
.accounting-summary article:nth-last-child(-n+2) {
  min-height: 96px; padding: 18px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm);
}
.accounting-summary strong { font-size: 22px; }
.price-list { gap: 14px; }
.price-item { min-height: 78px; border: 1px solid var(--line); border-left: 4px solid var(--green); border-radius: var(--radius); box-shadow: var(--shadow-sm); }

/* =====================================================================
   ACCOUNTING CHART
   Every colour is a custom property, so the light and dark themes share one
   definition of the chart. No fill or stroke is hardcoded.
   ===================================================================== */
.accounting-chart {
  margin: 0 0 18px; padding: 16px 18px 14px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm);
}
.accounting-chart figcaption { margin: 0 0 10px; color: var(--muted); font-size: 12.5px; }
/* Scrolled rather than scaled. The viewBox shrinks with the container, and a 760-unit chart at
   phone width would put the axis labels under 5px - legible numbers matter more than fitting. */
.accounting-chart-scroll { overflow-x: auto; }
.accounting-chart svg { display: block; width: 100%; min-width: 520px; height: auto; }
.chart-grid { stroke: var(--line); stroke-width: 1; }
.chart-axis { stroke: var(--line); stroke-width: 1.5; }
.chart-axis-label { fill: var(--muted); font: 10.5px Aptos, Calibri, sans-serif; }
.chart-caption { fill: var(--muted); font: 700 10px Aptos, Calibri, sans-serif; letter-spacing: .05em; }
.chart-bar-machine { fill: var(--green); }
.chart-bar-dropoff { fill: var(--blue); }
/* Lines reuse the bar colours, so switching view does not re-teach the palette. */
.chart-line-machine { fill: none; stroke: var(--green); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart-line-dropoff { fill: none; stroke: var(--blue); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart-dot-machine { fill: var(--green); stroke: var(--surface); stroke-width: 1.2; }
.chart-dot-dropoff { fill: var(--blue); stroke: var(--surface); stroke-width: 1.2; }
.chart-point { fill: var(--yellow); stroke: var(--surface); stroke-width: 1.5; }
.chart-partial { fill: none; stroke: var(--line-strong); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-hit { fill: transparent; }
.chart-bucket:hover .chart-bar-machine,
.chart-bucket:hover .chart-bar-dropoff { filter: brightness(.9); }
[data-theme="dark"] .chart-bucket:hover .chart-bar-machine,
[data-theme="dark"] .chart-bucket:hover .chart-bar-dropoff { filter: brightness(1.25); }
.chart-legend { margin: 12px 0 0; display: flex; flex-wrap: wrap; gap: 18px; color: var(--muted); font-size: 12px; }
.chart-legend span { display: inline-flex; align-items: center; gap: 7px; }
.chart-legend .swatch { width: 11px; height: 11px; border-radius: 3px; }
.chart-legend .swatch.machine { background: var(--green); }
.chart-legend .swatch.dropoff { background: var(--blue); }
.chart-legend .swatch.loyalty { background: var(--yellow); border-radius: 50%; }
.chart-note { margin: 8px 0 0; color: var(--muted); font-size: 12px; }
.activity-list { border-top: 1px solid var(--line); background: var(--surface); border-radius: var(--radius); box-shadow: var(--shadow-sm); padding: 2px 16px; }
.activity-item { min-height: 62px; }
.activity-icon { width: 34px; height: 34px; }
.empty-state, .loading-row { color: var(--muted); }

/* ---- Activity page: audit board (stats + full-height timeline) ---- */
.activity-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; margin-bottom: 20px; }
.activity-stat { display: flex; align-items: center; gap: 14px; min-height: 86px; padding: 16px 18px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); }
.activity-stat .metric-icon { width: 40px; height: 40px; flex: 0 0 40px; border-radius: 11px; }
.activity-stat small { display: block; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .07em; font-weight: 700; }
.activity-stat strong { display: block; margin-top: 4px; font-size: 24px; font-weight: 700; }
body[data-page="activity"] .activity-list { min-height: 420px; padding: 4px 20px; }
.activity-item { grid-template-columns: 34px minmax(0, 1fr) auto; }
.activity-body { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.activity-title { font-size: 14px; font-weight: 600; }
.activity-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.activity-tag { display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border-radius: 999px; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }
.activity-tag.payment { color: var(--green); background: var(--green-soft); }
.activity-tag.machine { color: var(--blue); background: var(--blue-soft); }
.activity-tag.command { color: var(--yellow); background: var(--yellow-soft); }
.activity-tag.system { color: var(--muted); background: color-mix(in srgb, var(--muted) 12%, transparent); }
.activity-icon.payment { color: var(--green); background: var(--green-soft); }
.activity-icon.machine { color: var(--blue); background: var(--blue-soft); }
.activity-icon.command { color: var(--yellow); background: var(--yellow-soft); }
.activity-icon.system { color: var(--muted); }
.activity-empty { min-height: 400px; display: grid; place-content: center; text-align: center; gap: 6px; }
.activity-empty .activity-empty-icon { width: 52px; height: 52px; margin: 0 auto 6px; border-radius: 14px; display: grid; place-items: center; background: var(--green-soft); color: var(--green); }
.activity-empty strong { font-size: 15px; }
.activity-empty .secondary { font-size: 12.5px; font-weight: 500; }
@media (max-width: 1180px) { .activity-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) {
  .activity-stats { grid-template-columns: minmax(0, 1fr); }
  .activity-item { grid-template-columns: 34px minmax(0, 1fr); }
  .activity-item time { grid-column: 2; }
}

/* ---- Dialogs / toast / banner ---- */
.payment-dialog { width: min(440px, calc(100vw - 32px)); padding: 26px; border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); }
.payment-dialog::backdrop { background: rgb(9 24 19 / 62%); backdrop-filter: blur(4px); }
.qr-frame { border-radius: var(--radius); border: 1px solid var(--line); }
.payment-state { border-radius: var(--radius-sm); }
.toast { border-radius: 12px; box-shadow: var(--shadow-lg); font-weight: 600; }
.offline-banner { background: linear-gradient(180deg, #a34433, #8f3527); }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; }

/* ---- Admin forms / payment keys ---- */
.admin-form { gap: 12px; margin: 14px 0 22px; }
.admin-form input, .admin-form select { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 11px 12px; font-size: 13.5px; box-shadow: var(--shadow-sm); }
/* The base `select` rule sets BOTH `height: 36px` and `padding: 0 30px 0 10px`. `.admin-form`
   then overrides the padding with a symmetric 11px/12px while leaving the fixed 36px height —
   and with `box-sizing: border-box` the content box collapses to 36 − 22 = 14px, which is
   SHORTER than the 13.5px font. The selected text is therefore clipped top and bottom.
   Nothing overflows and `scrollHeight` equals `clientHeight`, so every layout check passed
   while the text was visibly cut off. `height: auto` sizes a select to its text exactly as
   the input beside it already does. The 30px on the right is the arrow reserve: without it
   the browser draws the arrow over the last characters (the same rule lives in the base block
   and the two `.admin-form` padding rules wipe it out). */
.admin-form select, .dropoff-form select { height: auto; padding-right: 30px; }
.admin-form input:focus, .admin-form select:focus { outline: none; border-color: var(--green); box-shadow: var(--ring); }
.admin-hint { color: var(--muted); }
.machine-pay { border-radius: var(--radius); box-shadow: var(--shadow-sm); }
.machine-pay summary { padding: 2px 2px; }
.pay-state.own { color: var(--green); }
.org-switcher { border-radius: var(--radius-sm); height: 42px; }

/* ---- Desktop/tablet/mobile responsive ---- */
@media (max-width: 1180px) {
  .summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .accounting-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 980px) {
  .sidebar {
    position: fixed; inset: auto 0 0 0; width: auto; height: auto;
    padding: 6px max(10px, env(safe-area-inset-right)) max(8px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left));
    flex-direction: row; align-items: center; justify-content: center; border-right: 0; border-top: 1px solid #1e3a2f;
    box-shadow: 0 -8px 28px rgb(9 24 19 / 20%); z-index: 45;
  }
  .sidebar .brand, .sidebar-foot { display: none; }
  .sidebar nav { display: flex; width: 100%; margin: 0; gap: 2px; }
  .sidebar .nav-item { flex: 1 1 0; min-height: 54px; padding: 5px 2px; flex-direction: column; justify-content: center; gap: 3px; border-radius: 12px; text-align: center; font-size: 11px; }
  .sidebar .nav-item svg { width: 20px; height: 20px; }
  main { margin-left: 0; padding: calc(18px + env(safe-area-inset-top)) 18px calc(100px + env(safe-area-inset-bottom)); }
  .topbar { position: sticky; top: 0; z-index: 40; padding: 12px 0 14px; background: color-mix(in srgb, var(--paper) 86%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
  section[id] { scroll-margin-top: 92px; }
  .toast { left: 12px; right: 12px; bottom: calc(78px + env(safe-area-inset-bottom)); max-width: none; text-align: center; }
  .offline-banner { inset: auto 0 0 0; bottom: 62px; }
}
@media (max-width: 720px) {
  .table-wrap { border: 0; background: transparent; box-shadow: none; overflow: visible; }
  .table-wrap > table, .table-wrap tbody { display: block; width: 100%; min-width: 0; }
  .table-wrap thead { display: none; }
  .table-wrap tbody tr { display: block; margin: 0 0 14px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); overflow: hidden; }
  .table-wrap tbody tr td { display: flex; flex-direction: column; align-items: stretch; gap: 5px; width: 100%; height: auto; min-height: 0; padding: 11px 16px; border: 0; border-bottom: 1px solid var(--line); }
  .table-wrap tbody tr > td:last-child { border-bottom: 0; }
  .table-wrap tbody tr td::before { content: attr(data-label); flex: none; color: var(--muted); font: 700 10px Bahnschrift, sans-serif; text-transform: uppercase; letter-spacing: .08em; }
  .table-wrap tbody tr td:empty { display: none; }
  .table-wrap td.cell-actions { flex-direction: row; flex-wrap: wrap; gap: 8px; align-items: center; padding-top: 12px; padding-bottom: 12px; }
  .table-wrap td.cell-actions::before { content: none; }
  .table-wrap tbody tr td > * { width: 100%; }

  /* THE ORDER CARD ON A PHONE. Seven full-width labelled rows at 22px of vertical padding each
     made one ticket 558px tall, so four open tickets were 2,406px - the counter scrolls two
     thirds of a screen to reach the next one. Only the stage badges and the buttons need the
     whole width; the facts pair up. A 6-track grid rather than 2, so the same rule covers both
     the half-width pairs and the full-width rows. NOTHING IS HIDDEN: every value keeps its label.
     The money row is TWO-up, not three: three-up gives the Total cell ~53px of content box at
     280px while `KHR 21,100` needs 77, and `body`'s `overflow-x: hidden` would silently clip it
     to "KHR 21,10" - a wrong number is worse than a longer card. A 7-figure total needs ~105px,
     so it stayed fragile even at 375px.
     The tablet range already had its own fix for this (the `.dropoff-section` block in the
     721-1179px query, written because the "Next" buttons were clipped behind a scroll), but
     phones fell through to the generic `.table-wrap` rules, which is how they ended up worse.
     Scoped to `.dropoff-section` for the same reason that block is: Overview, Machines and
     Accounting measure 0 overflow here and are not broken. */
  .dropoff-section .table-wrap tbody tr { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .dropoff-section .table-wrap tbody tr td { padding: 7px 12px; }
  .dropoff-section .table-wrap tbody tr > td:nth-child(1),
  .dropoff-section .table-wrap tbody tr > td:nth-child(2) { grid-column: span 3; }
  .dropoff-section .table-wrap tbody tr > td:nth-child(3),
  .dropoff-section .table-wrap tbody tr > td:nth-child(7) { grid-column: span 6; }
  .dropoff-section .table-wrap tbody tr > td:nth-child(4),
  .dropoff-section .table-wrap tbody tr > td:nth-child(5) { grid-column: span 3; }
  .dropoff-section .table-wrap tbody tr > td:nth-child(6) { grid-column: span 6; }
  /* Cells that sit side by side would otherwise read as one run of text, since the only border
     they carry is the one under them. */
  .dropoff-section .table-wrap tbody tr > td:nth-child(1),
  .dropoff-section .table-wrap tbody tr > td:nth-child(4) { border-right: 1px solid var(--line); }
  /* A stage heading must not wear the grid treatment every other row gets, or the board grows a
     three-across blank cell between stages. A class beats the plain `tbody tr` selector, so this wins
     wherever it sits. */

  .btn-ctrl, .btn-test { flex: 1 1 46%; }
  .kill-test-group .btn-test { flex: 1 1 30%; }
  .accounting-summary, .summary { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .metric { min-height: 96px; padding: 15px; }
  .metric-icon { width: 38px; height: 38px; flex-basis: 38px; }
  .metric strong { font-size: 23px; }
}
@media (max-width: 640px) {
  .topbar .eyebrow, #today { display: none; }
  .operator div, .operator small { display: none; }
  .admin-form { flex-direction: column; }
  .admin-form input, .admin-form select, .admin-form button { width: 100%; }
  .payment-dialog, #password-dialog { position: fixed; inset: auto 0 0 0; margin: 0; width: 100%; max-width: 100%; max-height: 92vh; overflow: auto; border-radius: 20px 20px 0 0; padding: 20px 18px calc(20px + env(safe-area-inset-bottom)); }
}
@media (max-width: 400px) {
  .summary, .accounting-summary { grid-template-columns: 1fr; }
  .btn-ctrl, .btn-test { flex: 1 1 100%; }
  /* The drop-off KPI strip is one column at this width, so five cards cost 620px of an 812px
     iPhone X screen. Compacted rather than made 2-up: five cards in two columns leaves the
     fifth alone on a row with a hole beside it, which is the ragged look we are removing. */
  .dropoff-summary .metric { min-height: 68px; padding: 11px 13px; gap: 10px; }
  .dropoff-summary .metric-icon { width: 32px; height: 32px; flex-basis: 32px; }
  .dropoff-summary .metric-icon svg { width: 17px; height: 17px; }
  .dropoff-summary .metric strong { font-size: 19px; }
  .dropoff-summary .metric small { font-size: 11.5px; }
}

/* ---- Filter chips / sort / details (v3.1) ---- */
.filter-chips { display: inline-flex; flex-wrap: wrap; gap: 2px; padding: 3px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); box-shadow: var(--shadow-sm); }
.chip { border: 0; background: transparent; color: var(--muted); font-weight: 600; font-size: 12.5px; padding: 8px 13px; border-radius: 999px; cursor: pointer; transition: background .15s, color .15s; }
.chip:hover { color: var(--ink); }
.chip.active { background: var(--green-soft); color: var(--green); }
.sort-select { height: 40px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); color: var(--ink); padding: 0 10px; font-size: 13.5px; font-weight: 600; box-shadow: var(--shadow-sm); }
.info-btn { width: 30px; height: 30px; margin-left: auto; flex: none; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--line); border-radius: 9px; background: var(--surface); color: var(--muted); cursor: pointer; }
.info-btn:hover { color: var(--green); border-color: var(--green); }
.info-btn svg { width: 15px; height: 15px; }
.details-list { display: grid; grid-template-columns: auto 1fr; gap: 10px 16px; margin: 14px 0 0; }
.details-list dt { color: var(--muted); font: 700 10px Bahnschrift, sans-serif; text-transform: uppercase; letter-spacing: .07em; align-self: center; }
.details-list dd { margin: 0; font-weight: 600; font-size: 14px; }
@media (max-width: 640px) {
  .details-list { grid-template-columns: 1fr; gap: 2px 0; }
  .details-list dd { margin-bottom: 8px; }
  .filter-chips, .sort-select { width: 100%; }
  .filter-chips { justify-content: space-between; }
  /* The chip row is a segmented PILL, and on a phone its labels cannot fit on one line: at
     280px the five activity filters need ~368px and have 244px. Nothing wrapped, so the last
     chips ran off the viewport - 96px of them at 280px, 32px at 344px (Galaxy Fold cover) -
     and html/body's `overflow-x: hidden` hid it rather than making it scrollable. They wrap
     now. The 999px radius has to go with it, because that on a two-row box draws an ellipse. */
  .filter-chips { border-radius: var(--radius-sm); }
}

/* =====================================================================
   DARK THEME
   ===================================================================== */
[data-theme="dark"] {
  --ink: #e8f1ec;
  --muted: #9db0a7;
  --paper: #0a120f;
  --surface: #111c17;
  --line: #20302a;
  --line-strong: #2c4038;
  --green: #34b57e;
  --green-strong: #279468;
  --green-soft: #123024;
  --blue: #6fb3d6;
  --blue-soft: #132c3b;
  --coral: #e08268;
  --coral-soft: #3a201b;
  --yellow: #d9b45c;
  --yellow-soft: #3a3016;
  --sidebar: #08110d;
  --shadow-sm: 0 1px 2px rgb(0 0 0 / 30%);
  --shadow: 0 8px 24px rgb(0 0 0 / 40%);
  --shadow-lg: 0 24px 60px rgb(0 0 0 / 60%);
}
[data-theme="dark"] body {
  background:
    radial-gradient(1200px 600px at 100% -10%, #10241b 0%, transparent 60%),
    radial-gradient(900px 500px at -10% 110%, #101c2a 0%, transparent 55%),
    var(--paper);
}
[data-theme="dark"] .login-screen { background: radial-gradient(800px 500px at 50% -20%, #10241b 0%, transparent 60%), var(--paper); }
[data-theme="dark"] .login-card { background: var(--surface); border-color: var(--line); }
[data-theme="dark"] .sidebar { background: linear-gradient(180deg, #0c1c16 0%, var(--sidebar) 60%, #050b09 100%); border-right-color: #16261f; }
[data-theme="dark"] .topbar { background: color-mix(in srgb, var(--paper) 82%, transparent); }
[data-theme="dark"] th { background: #0e1915; }
[data-theme="dark"] tbody tr:hover { background: #131f1a; }
[data-theme="dark"] .btn-test { background: #16241e; }
[data-theme="dark"] .btn-test:hover { background: var(--blue-soft); border-color: #2f5a72; color: var(--blue); }
[data-theme="dark"] .provider.simulated { background: #16241e; }
[data-theme="dark"] .icon-button,
[data-theme="dark"] .install-button,
[data-theme="dark"] .search-input,
[data-theme="dark"] .sort-select,
[data-theme="dark"] select { background: var(--surface); color: var(--ink); }
/* Keep the "on" state green: the rule above forces a surface background on every
   .install-button, which left this dark text invisible on a dark button. */
[data-theme="dark"] .install-button.active { background: var(--green); border-color: var(--green); color: #062018; }
[data-theme="dark"] .activity-icon { background: var(--green-soft); }
[data-theme="dark"] .machine-symbol { background: var(--blue-soft); }
[data-theme="dark"] .toast { background: #e8f1ec; color: #0b1310; }
[data-theme="dark"] .toast.error { background: #e08268; color: #1a0f0c; }
[data-theme="dark"] .chip.active { color: var(--green); }

/* =====================================================================
   V3.2 OPS LAYOUT + ALERTS + PROFILE
   ===================================================================== */
.status.running::before { background: var(--yellow); }
.status.queued::before { background: var(--blue); }
.status.offline::before { background: #9aa5a1; }

.sidebar-operator { margin-top: auto; padding: 12px 8px; gap: 10px; border-top: 1px solid #1e3a2f; }
.sidebar-operator > span { width: 34px; height: 34px; font-size: 13px; }
.sidebar-operator strong { display: block; color: #fff; font-size: 13px; line-height: 1.2; }
.sidebar-operator small { display: block; color: #9db0a7; font-size: 11px; margin-top: 2px; }
.sidebar-status { display: flex; align-items: center; gap: 8px; padding: 6px 8px 2px; color: #93a89e; font-size: 12px; }
/* The dot used to be the same green whether the board was live or frozen, which is the whole problem:
   "Live" over a board that stopped updating is read as current. Same treatment the expired payment state
   uses, and it has to be the theme's coral - the sidebar is dark in every theme, so a fixed red for the
   light palette would be the least readable thing on it. */
.sidebar-status.conn-bad { color: var(--coral); }
.sidebar-status.conn-bad .live-dot { background: var(--coral); box-shadow: none; }

#alerts-btn { position: relative; }
.alerts-badge { position: absolute; top: -6px; right: -6px; min-width: 18px; height: 18px; padding: 0 4px; border-radius: 9px; background: var(--coral); color: #fff; font-size: 10.5px; font-weight: 700; display: grid; place-items: center; box-shadow: 0 0 0 2px var(--surface); }
.alerts-list { margin-top: 14px; display: grid; gap: 8px; }
.alert-item { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); cursor: pointer; }
.alert-item:hover { border-color: var(--green); box-shadow: var(--shadow-sm); }
.alert-item strong { font-size: 14px; }
.alert-item small { display: block; color: var(--muted); font-size: 12px; margin-top: 2px; }
.alert-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; background: var(--muted); }
.alert-item.offline .alert-dot { background: #9aa5a1; }
.alert-item.queued .alert-dot { background: var(--blue); }
.alert-item.running .alert-dot { background: var(--yellow); }

@media (min-width: 1200px) {
  main > * { min-width: 0; }

  /* Overview is the only page that pairs the fleet table with the audit feed.
     Every other page renders one full-width column, so no page can leave a
     half-empty grid cell behind. */
  .split-layout { display: grid; grid-template-columns: minmax(0, 2.15fr) minmax(0, 1fr); gap: 22px; align-items: start; }
  .split-layout > .workspace,
  .split-layout > .activity-section { margin-top: 0; }
  body[data-page="overview"] .activity-list { max-height: 430px; overflow: auto; }
}

/* =====================================================================
   V3.3 COMPACT TABLE + GRID VIEW + HISTORY + DARK FIXES
   ===================================================================== */
/* Tighter operations table (was over-spaced) */
.table-wrap td { height: auto; padding: 10px 16px; }
.table-wrap th { padding: 10px 16px; }
.machine-controls { gap: 6px; }
.btn-ctrl, .btn-test { height: 34px; font-size: 12.5px; padding: 0 10px; }
.btn-ctrl.start, .btn-ctrl.danger { padding: 0 12px; }
.machine-controls .btn-ctrl { flex: 0 1 auto; min-width: 68px; }
.machine-name { gap: 10px; }

/* 4-column machine table: fixed widths so it always fits (no side-scroll/clipping) */
@media (min-width: 721px) {
  .workspace .table-wrap { overflow-x: hidden; }
  .workspace .table-wrap table { width: 100%; min-width: 0; table-layout: fixed; }
  .workspace .table-wrap th:nth-child(1), .workspace .table-wrap td:nth-child(1) { width: 30%; }
  .workspace .table-wrap th:nth-child(2), .workspace .table-wrap td:nth-child(2) { width: 17%; }
  .workspace .table-wrap th:nth-child(3), .workspace .table-wrap td:nth-child(3) { width: 15%; }
  .workspace .table-wrap th:nth-child(4), .workspace .table-wrap td:nth-child(4) { width: 38%; }
  .machine-name > span:not(.machine-symbol) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .machine-name .secondary,
  .workspace .table-wrap td .secondary { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
.machine-symbol { width: 34px; height: 34px; }

/* Collapsible test tools keep rows compact */
.test-details { margin-top: 6px; }
.test-details > summary { cursor: pointer; list-style: none; color: var(--muted); font: 700 10.5px Bahnschrift, sans-serif; text-transform: uppercase; letter-spacing: .07em; }
.test-details > summary::-webkit-details-marker { display: none; }
.test-details > summary::after { content: " ▾"; }
.test-details[open] > summary::after { content: " ▴"; }
.test-details[open] .test-actions { margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--line); }
.test-actions { gap: 6px; }
.kill-test-group .btn-test { height: 30px; font-size: 11.5px; }

/* Grid (tile) view */
.machine-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(250px, 100%), 1fr)); gap: 14px; }
.machine-tile { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); padding: 16px; display: grid; gap: 10px; align-content: start; }
.machine-tile header { display: flex; align-items: center; gap: 10px; }
.machine-tile .tile-title { min-width: 0; }
.machine-tile .tile-title strong { display: block; font-size: 15px; }
.machine-tile .tile-title small { display: block; color: var(--muted); font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.machine-tile .tile-status { display: flex; align-items: center; justify-content: space-between; color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 7px 0; }
.machine-tile select { width: 100%; }
.machine-tile .machine-controls { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.machine-tile.offline { opacity: .9; }

/* Details history */
.details-history { margin-top: 18px; }
.details-history h3 { margin: 0 0 8px; color: var(--muted); font: 700 11px Bahnschrift, sans-serif; text-transform: uppercase; letter-spacing: .07em; }
.history-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; max-height: 240px; overflow: auto; }
.history-list li { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); }
.history-list .hist-type { font-weight: 700; font-size: 12.5px; }
.history-list .hist-status { margin-left: auto; font-size: 10.5px; font-weight: 700; padding: 2px 8px; border-radius: 999px; background: var(--green-soft); color: var(--green); }
.history-list .hist-status.queued, .history-list .hist-status.sent { background: var(--yellow-soft); color: var(--yellow); }
.history-list .hist-status.failed { background: var(--coral-soft); color: var(--coral); }
.history-list small { flex: 0 0 100%; color: var(--muted); font-size: 11px; }

/* Drop-off orders (front counter) */
.dropoff-section { margin-top: 28px; }
.dropoff-summary { margin: 18px 0; }

/* ---------- the drop-off KPI strip, at the widths the counter actually uses (2026-10-14) ----------
   FIVE cards, but the shared `.summary` rule steps down to TWO columns below 1180px, which is the
   wrong step for five of them: three rows. The strip was 376px of a 1024x768 tablet - the screen the
   counter uses - leaving exactly ONE ticket card visible below it.

   FIVE ACROSS WAS MEASURED AND REJECTED. It is the obvious answer and it is wrong: at 1024px the
   desktop sidebar still takes 260px, so five tiles are 130px wide, and `2 · KHR 7,285` and
   `KHR 175,200` protrude past their tile by 21px and 46px. `body` has `overflow-x: hidden`, so that
   does not look ugly - it silently shortens the figure. The same comment in the phone rules above
   already made this argument for a narrower card; this is the same failure, one breakpoint up.

   So the steps are set where the tiles have SLACK, not merely where they fit. Measured widths, all
   with zero overflow, on `_layout_probe.html` in this directory - which reproduces this strip's markup
   against this stylesheet, so re-run it if you move these numbers:
       5 across: 1400px -> 202px tiles (the width that quoted a 7-figure month would need is ~130px)
       3 across: 641-1399px -> 198px to 274px tiles
   THREE, not four: at 1024px four tiles are 166px and overflow by 10px, and at 660px four rows are
   TALLER than three (309px vs 249px), because five cards in four columns still need two rows.

   SCOPED to `.dropoff-summary`, never `.summary`: Overview's strip has four cards and is a different
   shape, so the shared rule is left exactly as it was. Phones keep the tuning they already have - two
   columns below 720px, one below 400px, both written for the 812px screen and left alone. */
@media (min-width: 1400px) {
  .dropoff-summary { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
@media (min-width: 641px) and (max-width: 1399px) {
  .dropoff-summary { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.dropoff-alert { display: flex; align-items: center; gap: 8px; margin: -8px 0 16px; padding: 10px 14px; border-radius: var(--radius-sm); color: var(--coral); background: var(--coral-soft); font-size: 13px; font-weight: 600; }
.dropoff-alert svg { width: 16px; height: 16px; flex: 0 0 16px; }
.dropoff-empty { display: grid; justify-items: center; gap: 6px; padding: 40px 24px; text-align: center; }
.dropoff-empty-icon { display: grid; place-items: center; width: 46px; height: 46px; margin-bottom: 4px; border-radius: 12px; color: var(--green); background: var(--green-soft); }
.dropoff-empty strong { font-size: 15px; }
/* The stage-heading rules (.dropoff-group, .dropoff-code, .dropoff-overdue) were deleted 2026-10-05.
   The board is COLUMNS of tickets now (`render/dropoff.js`), so nothing produces the old table group
   rows and the rules matched no element in any view - a selector that matches nothing is worse than
   no selector, because it reads as if it still governs something. The ticket code is `.ticket-code`
   (`styles.css` below, and `render/dropoff.js`), and overdue-ness is carried by `.ticket-due`.
   `.dropoff-actions` is NOT dead - it is produced on the ticket card - so it stays. */
.dropoff-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
/* The overflow dialog. The buttons keep the board's own styling, so a skip offered here looks exactly like
    the same skip offered on a card - it IS the same control, moved. 40px tall because this is the tablet
    target, and these are the controls that need a deliberate press. */
.dropoff-overflow-actions { gap: 8px; }
.dropoff-overflow-actions .simulate-button { min-height: 40px; padding: 0 14px; font-size: 13px; }
.dropoff-actions .simulate-button { padding: 0 10px; font-size: 12px; }
.dropoff-actions .simulate-button[data-dropoff-status="READY"] { border-color: var(--green); color: var(--green); }
.dropoff-actions .simulate-button.primary { border-color: var(--green); color: #fff; background: var(--green); }
.dropoff-actions .simulate-button.primary:hover { color: #fff; }

/* The customer type-ahead. It replaced a `<select>` holding every customer on the books. */
.dropoff-customer-results { display: grid; gap: 6px; margin: -6px 0 4px; padding: 8px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); max-height: 260px; overflow-y: auto; }
/* `hidden` is set in the markup, so each of these needs its own rule: a `display` value beats the
   user agent's `[hidden] { display: none }`, and the panel would sit there empty instead of gone. */
.dropoff-customer-results[hidden] { display: none; }
.dropoff-result { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-height: 44px; padding: 9px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); color: inherit; font: inherit; text-align: left; cursor: pointer; }
.dropoff-result:hover, .dropoff-result:focus-visible { border-color: var(--green); background: var(--green-soft); }
.dropoff-result-empty { margin: 0; padding: 10px 12px; color: var(--muted); font-size: 13px; }
.dropoff-picked { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: -6px 0 4px; padding: 10px 12px; border: 1px solid var(--green); border-radius: var(--radius-sm); background: var(--green-soft); }
.dropoff-picked[hidden] { display: none; }
/* The ABA reference box on the settle dialog is a `.dropoff-field` (a flex column), so it needs
   the same rule for the same reason - see the note above. */
.dropoff-field[hidden] { display: none; }
.dropoff-picked > span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

/* TABLET: the ticket board is the ONE table in the app that does not fit at tablet widths, so it
   reflows to labelled cards from 721px up to 1179px - the same treatment phones already get.
 *
 * Measured 2026-09-21 on the running dashboard, at 768 / 834 / 1024 / 1100px: the board's table has
 * an 820px FLOOR while its own container is only 674-786px wide, so 34-118px of it sat behind a
 * horizontal scroll. The clipped column is "Next" - the buttons that ADVANCE a ticket - so an
 * operator on a tablet had to scroll sideways to move a ticket forward, on the screen used all day.
 * The container also SHRINKS to 702px at exactly 1024px (iPad landscape), which is why 900px
 * measured fine and 1024px did not.
 *
 * A fixed table-layout (the treatment Overview and Machines get from `.workspace`) was tried first
 * and does remove the overflow, but it stacks the seven advance buttons in a ~110px gutter and
 * stretches each row to 146px. Cards put those buttons in a full-width wrapping row and label every
 * value, which is what a counter needs.
 *
 * SCOPED to `.dropoff-section` deliberately: Overview, Machines and Accounting ALL measure 0
 * overflow at these widths, so widening this to `.table-wrap` would change three screens that are
 * not broken. */
@media (min-width: 721px) and (max-width: 1179px) {
  .dropoff-section .table-wrap { overflow: visible; border: 0; background: transparent; box-shadow: none; }
  .dropoff-section .table-wrap > table,
  .dropoff-section .table-wrap tbody { display: block; width: 100%; min-width: 0; }
  .dropoff-section .table-wrap thead { display: none; }
  .dropoff-section .table-wrap tbody tr { display: block; margin: 0 0 14px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); overflow: hidden; }
  .dropoff-section .table-wrap tbody tr td { display: flex; flex-direction: column; align-items: stretch; gap: 5px; width: 100%; height: auto; min-height: 0; padding: 11px 16px; border: 0; border-bottom: 1px solid var(--line); }
  .dropoff-section .table-wrap tbody tr > td:last-child { border-bottom: 0; }
  .dropoff-section .table-wrap tbody tr td::before { content: attr(data-label); flex: none; color: var(--muted); font: 700 10px Bahnschrift, sans-serif; text-transform: uppercase; letter-spacing: .08em; }
  .dropoff-section .table-wrap tbody tr td:empty { display: none; }
  .dropoff-section .table-wrap tbody tr td > * { width: 100%; }
  .dropoff-section .amount-column { text-align: left; }
  /* The advance buttons are the primary action on this screen, so they get a finger-sized target
     here rather than the dense desktop padding. */
  .dropoff-section .dropoff-actions { gap: 8px; }
  .dropoff-section .dropoff-actions .simulate-button { min-height: 40px; padding: 0 14px; font-size: 13px; }
}

/* Pipeline status: the dot colour is the whole point of the column. */
.dropoff-status { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 700; }
.dropoff-status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--muted); }
.dropoff-status-received::before { background: var(--blue); }
.dropoff-status-washing::before, .dropoff-status-drying::before, .dropoff-status-folding::before { background: var(--yellow); }
.dropoff-status-ready::before { background: var(--green); box-shadow: 0 0 0 4px rgb(84 199 125 / 16%); }
.dropoff-status-collected::before { background: #9aa5a1; }
.dropoff-status-cancelled::before { background: var(--coral); }

.dropoff-pay { display: inline-block; padding: 3px 8px; border-radius: 999px; font-size: 11px; font-weight: 700; }
.dropoff-pay.unpaid { color: var(--coral); background: var(--coral-soft); }
.dropoff-pay.part { color: var(--yellow); background: var(--yellow-soft); }
.dropoff-pay.paid { color: var(--green); background: var(--green-soft); }

.dropoff-panels { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); gap: 18px; margin-top: 26px; }

/* ---------- the two folds under the board (2026-10-12) ----------
   The intake form and the reference panels are one tap away and BELOW the board, because the board is
   what the counter reads all day and these are used once per bag or once per customer. They are native
   <details> so the keyboard and the screen reader get the disclosure for free: no script decides whether
   the content is reachable, and `[open]` is the only state either the CSS or the JS has to know about. */
.dropoff-fold { margin-top: 18px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); }
.dropoff-fold > summary { display: flex; align-items: center; gap: 10px; padding: 15px 18px; cursor: pointer; list-style: none; }
.dropoff-fold > summary::-webkit-details-marker { display: none; }
.dropoff-fold > summary svg { width: 18px; height: 18px; flex: none; color: var(--muted); }
/* The chevron IS the affordance that the row opens, and it is the only part of the summary that changes
   with the state, so the words stay put when the panel opens. */
.dropoff-fold > summary::after { content: "▾"; margin-left: auto; color: var(--muted); }
.dropoff-fold[open] > summary::after { content: "▴"; }
.dropoff-fold-title { font: 700 15px Bahnschrift, sans-serif; }
.dropoff-fold-hint { color: var(--muted); font-size: 12.5px; }
.dropoff-fold-body { padding: 0 18px 18px; }
/* The intake and the panels each carry their own box, and a box inside a box reads as two panels rather
   than one. The fold owns the border now. */
.dropoff-fold-body .dropoff-intake { margin: 0; padding: 0; border: 0; background: transparent; box-shadow: none; }
.dropoff-fold-body .dropoff-panels { margin-top: 0; }
.dropoff-fold-body .dropoff-panel { box-shadow: none; }
/* The hint is the first thing to go: it is a nicety, and on a phone the title and the chevron are what
   the fold is actually read by. */
@media (max-width: 640px) {
  .dropoff-fold-hint { display: none; }
  .dropoff-fold > summary { padding: 13px 14px; }
  .dropoff-fold-body { padding: 0 14px 14px; }
}
.dropoff-panel { padding: 20px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); }
.dropoff-panel h3 { margin: 2px 0 0; font-size: 16px; }
.dropoff-cards { display: grid; gap: 10px; margin-top: 14px; max-height: 460px; overflow: auto; }
.dropoff-card { display: grid; gap: 8px; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); }
.dropoff-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.dropoff-card-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; color: var(--muted); font-size: 12px; font-weight: 600; }
.dropoff-card-actions { display: flex; gap: 6px; }
.dropoff-card-actions .simulate-button { padding: 0 10px; font-size: 12px; }
.dropoff-matched { color: var(--green); font-weight: 700; }
.dropoff-tag { display: inline-flex; align-items: center; gap: 4px; padding: 2px 7px; border-radius: 999px; color: var(--blue); background: var(--blue-soft); }
.dropoff-tag svg { width: 12px; height: 12px; }
.dropoff-price { color: var(--ink); font-weight: 700; }
/* A retired price stays ON the list, dimmed, with a way back. Retiring is the only way to take a
   price off the list (a hard delete is refused by the order_lines foreign key), so hiding the row
   would leave a mistyped price permanently unusable and no sign that anything had been retired.
   Dashed rather than only faded, because "faded" alone is easy to miss on a busy screen. */
.dropoff-card.is-retired { border-style: dashed; background: transparent; }
.dropoff-card.is-retired .dropoff-card-head strong,
.dropoff-card.is-retired .dropoff-price { opacity: 0.55; }
.dropoff-subhead { margin: 18px 0 0; color: var(--muted); font-size: 12px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }

/* Which services a price covers - on the price card and on the ticket board. Read-only
   badges, so a wrapped flex row is fine: the intrinsic-width trap that governs the form
   rows does not apply, because nothing here has to shrink. */
.dropoff-service { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 999px; color: var(--green); background: var(--green-soft); font-size: 11px; font-weight: 700; }
.dropoff-service-row { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }

/* The bilingual badge: an icon, then the English word above the Khmer one. Built by
   `bilingualBadge()` in render/dropoff.js and used on the price cards and (later) the intake cards,
   so the two surfaces cannot drift into different shapes.
   The two languages STACK rather than sit side by side: a long Khmer word would otherwise push the
   English out of the badge, and neither line is ever clipped with an ellipsis - a label that
   silently truncates is worse than no label for the reader this exists for.
   Nothing here has to shrink, so a wrapping flex row is safe (the intrinsic-width trap that governs
   the form rows does not apply). */
.dropoff-card-services { display: flex; flex-wrap: wrap; gap: 6px; }
.dropoff-badge { display: inline-flex; align-items: center; gap: 7px; padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); }
.dropoff-badge svg { width: 18px; height: 18px; flex: 0 0 auto; }
.dropoff-badge-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.dropoff-badge-en { font-size: 11px; font-weight: 700; }
.dropoff-badge-km { font-size: 12px; font-weight: 600; color: var(--muted); }
.dropoff-badge-service { border-color: transparent; color: var(--green); background: var(--green-soft); }
.dropoff-badge-service .dropoff-badge-en { color: var(--green); }
.dropoff-badge-unit { border-color: transparent; color: var(--blue); background: var(--blue-soft); }
.dropoff-badge-unit .dropoff-badge-en { color: var(--blue); }

/* Loyalty counters: how far a customer is from their next free service. Blue while they are
   still counting (deliberately NOT the green of the service ticks, so "Wash 7/10" is not
   mistaken for a tick), and solid green once a reward is actually waiting. Read-only chips
   plus an optional button, so a wrapped flex row is fine - nothing here has to shrink, which
   is what governs the form rows. */
.dropoff-loyalty { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; }
.dropoff-loyalty-count { padding: 2px 8px; border-radius: 999px; color: var(--blue); background: var(--blue-soft); font-size: 11px; font-weight: 700; }
.dropoff-loyalty-count.due { color: var(--surface); background: var(--green); }
.dropoff-loyalty .simulate-button { padding: 3px 10px; font-size: 12px; }
/* The intake dialog's slot for the same strip: takes no space until a customer is known. */
.dropoff-loyalty-note:empty { display: none; }
/* The reward on/off switch in Administration. */
.loyalty-toggle { display: inline-flex; align-items: center; gap: 8px; color: var(--ink); font-size: 13px; font-weight: 600; cursor: pointer; }
.loyalty-toggle input { width: auto; min-width: 0; accent-color: var(--green); }

/* Telegram counter link, under Customers. The readonly field is deliberately wide: the owner's
   usual job here is reading the link aloud or copying it onto a phone. */
.telegram-link-row input[readonly] { flex: 1 1 320px; min-width: 240px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; }
.telegram-link-row input[readonly]:focus { outline: 2px solid var(--blue); outline-offset: 1px; }
/* The sign preview is capped rather than shown at its A4 size, which would dwarf the panel. */
.telegram-sign { margin: 4px 0 8px; }
.telegram-sign img { display: block; width: 100%; max-width: 320px; height: auto; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); }

/* The service ticks in the price dialog. */
.dropoff-ticks { display: flex; flex-wrap: wrap; gap: 4px 18px; }
.dropoff-ticks label { display: inline-flex; align-items: center; gap: 8px; color: var(--ink); font-size: 13px; font-weight: 600; cursor: pointer; }
.dropoff-ticks input { width: auto; min-width: 0; margin: 0; accent-color: var(--green); }
/* Opts the tick captions out of `.dropoff-field > span`'s caption styling, which is for
   the field LABEL above the row, not for the words beside each box. */
.dropoff-ticks label > span { color: var(--ink); font-size: 13px; font-weight: 600; text-transform: none; letter-spacing: 0; }

/* The intake, as a section on the page rather than a dialog. It got the width cap from the dialog it
   used to be; as a section it takes the column it sits in, which is what lets the price cards and the
   line rows lay out at their intended size instead of being squeezed into 620px. */
.dropoff-intake { margin: 0 0 18px; padding: 20px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); }
/* `.admin-form` is a *wrapping ROW* (display:flex, flex-wrap:wrap). These dialogs are
   label-above-input forms, so the direction has to be flipped back to a column -
   otherwise every field tries to sit on one line, overflows the dialog and the last
   ones get clipped. Side-by-side fields are opt-in via `.dropoff-form-row`. */
.dropoff-form { flex-direction: column; align-items: stretch; gap: 12px; }
/* Buttons must not stretch to full width in a column flex container. */
.dropoff-form > .simulate-button { align-self: flex-start; }
.dropoff-field { display: grid; gap: 5px; min-width: 0; }
/* `.admin-form input, .admin-form select` sets `min-width: 180px; flex: 1 1 180px` for
   a HORIZONTAL form. Inherited into these column forms both are wrong: flex-basis is
   read as a HEIGHT on the main axis, and min-width:180px stops fields from shrinking,
   which is what pushed the intake dialog 200px past its own width. Neutralised here,
   then opt back in per group below (dropoff-form-row / dropoff-new-customer /
   dropoff-line each re-set flex for their own inputs). */
.dropoff-form input,
.dropoff-form select { min-width: 0; }
.dropoff-form input[type="checkbox"] { width: auto; }
/* A single-column grid will happily be blown out by a control's INTRINSIC width - a
   text/number input reports ~188px, a select reports its widest option - and neither
   `min-width: 0` nor `flex-basis: 0` talks it down. So every multi-control row below is
   a GRID with `minmax(0, …)` tracks: a `1fr` track with a 0 minimum cannot exceed its
   share, which makes the row's min-content exactly its fixed columns. Flex rows made of
   inputs are the thing that broke here; grid tracks cannot. */
.dropoff-field > input,
.dropoff-field > select { width: 100%; min-width: 0; }
.dropoff-field > span { color: var(--muted); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; }
.dropoff-form-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.dropoff-new-customer { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
/* Those three inputs are for a NEW customer only. They used to be on screen always, which
   pushed the dialog past the fold on a short window and invited staff to retype details
   for somebody already picked from the list. Shown only while the picker is on
   "New customer" - app.js toggles `.dropoff-new-customer-mode` on the form. */
.dropoff-form:not(.dropoff-new-customer-mode) .dropoff-new-customer,
.dropoff-form:not(.dropoff-new-customer-mode) #dropoff-new-hint { display: none; }
.dropoff-lines { display: grid; gap: 8px; }
/* A line is now TWO rows: the price cards own the full width, and the quantity, price, total and
   remove button sit underneath. The old single row was squeezed to give a <select> the room its
   label needed; the cards need that room for a different reason - each one carries a pictogram and
   the price at a size a reader can judge at arm's length, without reading a word. */
.dropoff-line { display: grid; grid-template-columns: 156px minmax(0, 1fr) 84px 34px; gap: 8px; align-items: center; }
.dropoff-line strong { text-align: right; font-size: 13px; }
.dropoff-line .simulate-button { width: 34px; padding: 0; }

/* One tap adds or removes a unit of whatever the price is counted in. 48px is a target a thumb can
   hit without looking, and the quantity is the number the counter changes on almost every bag - so it
   should never need the keyboard. The step size itself comes from the unit (see data/dropoff-ui.js). */
.dropoff-qty { display: grid; grid-template-columns: 48px minmax(0, 1fr) 48px; align-items: center; gap: 4px; }
.dropoff-step { display: inline-flex; align-items: center; justify-content: center; height: 48px; min-width: 48px; padding: 0; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); color: var(--ink); font-size: 22px; font-weight: 700; line-height: 1; cursor: pointer; }
.dropoff-step:hover { border-color: var(--muted); background: var(--line); }
.dropoff-step:active { transform: translateY(1px); }
/* At the floor, − is shown as unavailable rather than silently ignoring the tap. */
.dropoff-step:disabled { opacity: 0.35; cursor: default; }
.dropoff-step:disabled:hover { border-color: var(--line); background: var(--surface); }
.dropoff-qty input { text-align: center; }
/* The browser's own spinner arrows are HIDDEN on the intake's number boxes. They are about 17px wide
   and sit inside a 52px box, so a tap aimed at the middle of the number lands on the down-arrow and
   silently changes a weight - measured: clicking the centre of a box reading "1" made it 0.9, with
   nothing on screen to say why. The big +/- buttons are the intended control, and at 48px they are
   far easier to hit - which matters MORE now that the number pad has gone, because a centre-tap on a
   narrow box is the only way left to change a weight by accident.
   Not an accessibility loss: the arrow KEYS still step a focused number input, which is what a
   keyboard user actually reaches for. */
.dropoff-qty input[type="number"],
.dropoff-line [data-line-price] { appearance: textfield; -moz-appearance: textfield; }
.dropoff-qty input[type="number"]::-webkit-outer-spin-button,
.dropoff-qty input[type="number"]::-webkit-inner-spin-button,
.dropoff-line [data-line-price]::-webkit-outer-spin-button,
.dropoff-line [data-line-price]::-webkit-inner-spin-button { appearance: none; margin: 0; }

/* NO NUMBER PAD. It was here: always on screen, thumb-sized keys, a header naming the box it was
   typing into. The owner asked for it to go, so the rules went with the markup rather than being left
   behind as dead CSS that the next person has to prove is unused. The boxes are real inputs (the
   phone's own keyboard) and each quantity keeps its +/- steppers. */

/* ---- The ticket board as COLUMNS of compact cards -------------------------------
   A ticket used to be a 560px stack of seven labelled cells - measured on the running dashboard - and
   four of them came to 2378px of board. These rules build the same information in about 120px.

   The labels go, so position and COLOUR have to carry the meaning: money appears ONLY when it is owed
   (a settled ticket has no chip, because a chip with a number can only mean "left to collect"), and
   every chip keeps a full-sentence `title` for a screen reader and for anyone who does not read the
   shorthand.

   Columns come and go with the work, which is a deliberate trade-off rather than an oversight: always
   drawing all five would make horizontal POSITION a stable cue, at the cost of a third of a tablet's
   width spent on columns for stages nobody is working in. Position is not load-bearing here because
   every column head states its stage as text AND colour, and the column RANK never changes - a stage
   can appear or disappear, but it can never move left of a stage it used to sit right of.

   Scoped to `.dropoff-board` and its own class names on purpose, so none of this can reach the
   Overview/Machines/Accounting tables that share `.table-wrap`. */
/* The horizontal scroll lives on the PERSISTENT htmx target, NOT on the board it swaps. #dropoff-region
   carries hx-swap="innerHTML" and hx-trigger="every 30s", so everything inside it - including any
   scroll offset held by a child - is destroyed and rebuilt twice a minute. A board that owned its own
   scroll would therefore yank an operator parked on the Ready column back to the first column mid-task,
   every 30 seconds. The region outlives the swap, so the offset does too. Gated to the column band:
   below 900px the columns stack and there is nothing to scroll sideways. */
@media (min-width: 900px) { #dropoff-region { overflow-x: auto; } }
.dropoff-board { display: flex; gap: 12px; align-items: flex-start; padding-bottom: 6px; }
/* 248px is the card's floor, not a guess about the screen: below roughly 220px a customer name wraps
   and the card re-inflates, which is the exact cost this layout exists to remove. So the columns
   SCROLL rather than shrink. Rank is the server's: READY first, the bags a customer is standing at the
   counter for, and RECEIVED last - the same order the table read top to bottom, so the change of
   treatment did not move anything an operator had learned. */
.dropoff-column { flex: 0 0 248px; display: grid; gap: 8px; align-content: start; }
.dropoff-column-head { display: flex; align-items: baseline; gap: 8px; margin: 0; padding: 0 2px 6px; border-bottom: 1px solid var(--line); font-size: 12px; text-transform: uppercase; letter-spacing: .06em; }
.dropoff-column-head .secondary { color: var(--muted); font-weight: 700; }
.dropoff-column-cards { display: grid; gap: 8px; }

.dropoff-ticket { display: grid; gap: 6px; margin: 0; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); box-shadow: var(--shadow-sm); }
.dropoff-ticket p { display: flex; align-items: center; gap: 8px; min-width: 0; margin: 0; }
/* `flex: none` + nowrap on the CODE, not the name. Left to itself the code shrinks and wraps - "D-" on
   one line and "0042" on the next - which broke the head row and cost 15px of card height. The name is
   the thing that should give way, and it does, with an ellipsis. */
.ticket-code { flex: none; white-space: nowrap; font: 700 12px Bahnschrift, sans-serif; letter-spacing: .04em; color: var(--green); }
.ticket-name { min-width: 0; overflow: hidden; font-size: 14px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
.ticket-meta, .ticket-items { font-size: 12px; }
.ticket-items { color: var(--muted); }
.ticket-count { margin-left: auto; }
/* The ticket's PLAN - which programme it is washed on and how long it is dried - when it has one. Quiet,
   like the ticks it sits beside, and deliberately NOT a chip: it is a detail of the same fact they state,
   and a bordered pill here would compete with the money badge that carries the one number on this line.
   `min-width: 0` + ellipsis so a long pair gives way rather than pushing the count off the card. */
.ticket-plan { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ticket-due { color: var(--muted); font-variant-numeric: tabular-nums; }
.ticket-due.overdue { color: var(--danger, #c0392b); font-weight: 700; }
.ticket-due.none { opacity: .65; }
/* Money only when it is owed. `owed` is a BUTTON - the money line and the Pay action are the same fact,
   so one control keeps a frequent action at one tap without spending a row on it. */
.ticket-money { margin-left: auto; padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 700; }
.ticket-money.owed { border: 1px solid var(--danger, #c0392b); background: transparent; color: var(--danger, #c0392b); cursor: pointer; font-variant-numeric: tabular-nums; }
.ticket-money.other { background: var(--line); color: var(--muted); }
/* 40px tall, which is the target the tablet band used to enforce before this layout replaced it. The
   overflow sits at the far end with a gap: the dangerous controls live inside it, never beside Next. */
.ticket-next { flex: 1 1 auto; min-height: 40px; padding: 0 12px; border: 1px solid var(--green); border-radius: var(--radius-sm); background: var(--green); color: #fff; font-size: 13px; font-weight: 700; cursor: pointer; }
.ticket-next.done { display: inline-flex; align-items: center; justify-content: center; border-color: var(--line); background: transparent; color: var(--muted); cursor: default; }
.ticket-more { flex: 0 0 auto; min-width: 40px; min-height: 40px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); color: var(--ink); font-size: 16px; font-weight: 700; cursor: pointer; }

/* ---- The All view as a horizontal LIST --------------------------------------------
   The same ticket, the same facts, laid out ONCE PER LINE instead of stacked inside a 248px column. "All"
   is the BROWSING view - up to 200 tickets, the collected ones included - and there a stage is no longer a
   PLACE an operator can look at, it is one more field. So the row states it INLINE, with the same
   colour-coded dot a column head uses, and the fields run left to right in the order a counter reads them:
   who, which stage, when, what is in the bag, money, next action.

   The row WRAPS rather than scrolling sideways: a field pushed off the right edge is one nobody can read,
   and a tablet held upright at the counter is the common case. Groups wrap whole, so a wrapped row still
   reads as "who / when / what / what next" instead of shredding one label per line. */
.dropoff-list { display: grid; gap: 8px; }
.dropoff-list .dropoff-ticket { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; padding: 8px 12px; }
/* The fields sit in reading order from the LEFT edge, and the ITEMS cell takes the slack - which puts the
   one gap in a row where nobody is reading, just before the action, instead of between the name and the
   time. So the row fills its width the way a list does rather than ending in a cluster of fields. */
.dropoff-list .ticket-head { flex: 0 1 auto; }
.dropoff-list .ticket-items { flex: 1 1 auto; }
/* `flex: none` on the stage: it is a short, fixed cue, and letting it shrink would ellipsis away the one
   word that says which stage the ticket is in. */
.dropoff-list .ticket-head .dropoff-status { flex: none; }
.dropoff-list .ticket-actions { margin-left: auto; }
/* The next-step button STOPS stretching. In a card it fills the width; in a row that would push the ⋯ to
   the far edge of the screen, away from the action it belongs with. */
.dropoff-list .ticket-next { flex: 0 0 auto; min-width: 104px; }
/* The count follows the ticks here, rather than being pushed to the card's far edge - in a row the far
   edge is where the ACTION lives. */
.dropoff-list .ticket-count { margin-left: 0; }

/* ---- One ticket, in full ----------------------------------------------------------
   The dialog body is filled by the client from the ticket the server returns, so these rules are the
   whole of its appearance. It is a LEDGER, not a card: one row per line, the amount right-aligned so a
   column of figures can be read down, and the quantity/unit price in a lighter line beneath the label
   because the operator is looking for the WRONG row, not for the arithmetic. */
.dropoff-detail { display: grid; gap: 10px; max-height: 60vh; overflow-y: auto; }
.dropoff-detail-who { margin: 0; font-size: 12px; color: var(--muted); }
.dropoff-detail-lines { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }
.dropoff-detail-line { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); }
.dropoff-detail-what { display: grid; min-width: 0; gap: 2px; }
.dropoff-detail-what strong { overflow: hidden; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
.dropoff-detail-what small { color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
/* `margin-left: auto` rather than a fixed column: the amount sits against the right edge whether or not
   the row has a Remove button, so the figures line up across rows that differ. */
.dropoff-detail-amount { margin-left: auto; font-size: 13px; font-variant-numeric: tabular-nums; }
.dropoff-detail-plan, .dropoff-detail-notes { margin: 0; font-size: 12px; color: var(--muted); }
.dropoff-detail-money { margin: 0; font-size: 13px; font-variant-numeric: tabular-nums; }
.dropoff-detail-left { color: var(--danger, #c0392b); }

/* ONE template, two contexts: below 900px there is no width for columns, so the same columns stack and
   the stage headings become the group headings the board already had. No second renderer. */
@media (max-width: 899px) {
  .dropoff-board { display: grid; gap: 14px; }
  .dropoff-column { flex: none; }
}

/* The cards wrap rather than scroll: a price the operator cannot see is a price they cannot choose.
   150px minimum keeps a two-word item label on one or two lines instead of one word per line. */
.dropoff-picker { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); gap: 8px; }
.dropoff-pick { display: block; position: relative; cursor: pointer; }
/* The radio is hidden but NOT removed: it keeps focus order, arrow-key navigation and the
   screen-reader role. `opacity: 0` over a 1px box rather than `display: none`, which would take it
   out of the tab order entirely and make the group unreachable by keyboard. */
.dropoff-pick input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.dropoff-pick-card { display: flex; flex-direction: column; gap: 4px; min-height: 96px; padding: 10px; border: 2px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); }
.dropoff-pick-icons { display: flex; align-items: center; gap: 6px; color: var(--green); }
.dropoff-pick-icons svg { width: 26px; height: 26px; }
/* Wraps rather than clipping: an item label is the shop's own text and can be any length. */
.dropoff-pick-label { font-size: 13px; font-weight: 700; color: var(--ink); overflow-wrap: anywhere; }
.dropoff-pick-price { font-size: 13px; font-weight: 700; color: var(--ink); }
.dropoff-pick-per { font-weight: 600; color: var(--muted); }
.dropoff-pick-unit-km { font-size: 12px; color: var(--muted); }
/* The chosen card. A 2px border in both states, so selecting one cannot shift the layout. */
.dropoff-pick input:checked + .dropoff-pick-card { border-color: var(--green); background: var(--green-soft); }
/* The focus ring goes on the CARD: the real radio is 1px and invisible, so focusing it directly
   would show the operator nothing. */
.dropoff-pick input:focus-visible + .dropoff-pick-card { outline: 2px solid var(--blue); outline-offset: 2px; }
.dropoff-pick:hover .dropoff-pick-card { border-color: var(--muted); }

/* Below the dialog's own width the four columns stop fitting, so the total moves onto a third row
   rather than pushing the dialog sideways. The stepper KEEPS its 156px: shrinking it would shrink the
   buttons that have to stay hittable, so the price gives up its column instead. */
@media (max-width: 640px) {
  /* PRICE TILES GO TWO-UP ON A PHONE. `auto-fill minmax(150px, 1fr)` needs 300px for two
     columns and the picker only has ~287, so a phone got ONE per row: a 287px-wide tile
     holding about 90px of icons, three quarters of it empty, and seven prices ran to 835px.
     That is the "large gaps" on an iPhone X, and it is why the same page looks fine on a
     desktop. Two up restores the desktop proportion (~140px against ~150px) at half the height. */
  .dropoff-picker { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dropoff-pick-card { min-height: 88px; padding: 9px; }
  /* Backstop for a 280px screen (Galaxy Fold cover): a 92px tile cannot hold three 26px icons
     on one line, so they wrap instead of pushing the page sideways. */
  .dropoff-pick-icons { flex-wrap: wrap; }
  .dropoff-line { grid-template-columns: 156px minmax(0, 1fr) 34px; }
  .dropoff-line strong { grid-column: 1 / 3; grid-row: 3; text-align: left; }
  .dropoff-line > .simulate-button { grid-column: 3; grid-row: 2; justify-self: end; }
  .dropoff-new-customer { grid-template-columns: minmax(0, 1fr); }
}
/* On a phone the price takes its own row and the stepper sits beside the remove button. */
@media (max-width: 480px) {
  .dropoff-line { grid-template-columns: minmax(0, 1fr) 34px; }
  .dropoff-qty { grid-column: 1; grid-row: 2; max-width: 220px; }
  .dropoff-line > .simulate-button { grid-column: 2; grid-row: 2; justify-self: end; }
  .dropoff-line [data-line-price] { grid-column: 1 / -1; grid-row: 3; }
  .dropoff-line strong { grid-column: 1 / -1; grid-row: 4; text-align: left; }
  /* Promised/Counter side by side squeezes a shop name out of its own dropdown. */
  .dropoff-form-row { grid-template-columns: minmax(0, 1fr); }
}
.dropoff-check { display: flex; align-items: center; gap: 8px; color: var(--ink); font-size: 13px; font-weight: 600; }
.dropoff-total { display: flex; align-items: baseline; justify-content: space-between; padding: 10px 0 0; border-top: 1px solid var(--line); }
.dropoff-total span { color: var(--muted); font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; }
.dropoff-total strong { font: 700 22px Bahnschrift, sans-serif; }
.dropoff-intake-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 6px; }
/* The intake is part of the page, so it does not scroll on its own the way the dialog did - but on a
   short window the page does, and the primary button would otherwise start life below the fold.
   Pinning the action row keeps "Take in the bag" reachable without hunting for it, and the background
   is what hides the content scrolling behind the row. */
.dropoff-intake-actions { position: sticky; bottom: 0; background: var(--surface); }

[data-theme="dark"] .dropoff-panel,
[data-theme="dark"] .dropoff-card { background: var(--surface); }
[data-theme="dark"] .dropoff-actions .simulate-button { background: var(--surface); }
[data-theme="dark"] .dropoff-pay.paid { color: #7fdcae; background: #16352a; }
[data-theme="dark"] .dropoff-pay.unpaid { color: #f0a795; background: #3a2019; }
[data-theme="dark"] .dropoff-pay.part { color: #e6c76a; background: #352c10; }

/* Dark-mode contrast fixes */
[data-theme="dark"] { color-scheme: dark; }
[data-theme="dark"] .login-card input,
[data-theme="dark"] .admin-form input,
[data-theme="dark"] .admin-form select { background: var(--surface); color: var(--ink); }
[data-theme="dark"] .login-card h1,
[data-theme="dark"] .login-card p { color: var(--ink); }
[data-theme="dark"] .simulate-button { color: var(--ink); }
[data-theme="dark"] .group-label,
[data-theme="dark"] .test-details > summary { color: var(--muted); }
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }

/* =====================================================================
   UNIFIED BUTTON SYSTEM (consistent size / radius / intent)
   ===================================================================== */
.btn-ctrl, .btn-test, .simulate-button, .install-button, .pay-button, .chip {
  border-radius: var(--radius-sm);
  font-weight: 600;
  letter-spacing: 0;
  transition: background .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease;
}
.btn-ctrl, .btn-test, .simulate-button, .install-button { height: 38px; font-size: 13px; }
.btn-ctrl svg, .btn-test svg, .simulate-button svg, .install-button svg { width: 15px; height: 15px; }

/* Primary intent: form submits + explicit primaries */
.simulate-button[type="submit"],
.simulate-button.primary {
  background: linear-gradient(180deg, var(--green), var(--green-strong));
  border-color: transparent;
  color: #fff;
}
.simulate-button[type="submit"]:hover,
.simulate-button.primary:hover { filter: brightness(1.06); color: #fff; }

/* Danger intent */
.simulate-button[data-remove-user],
.simulate-button[data-device-status][data-next-status="DISABLED"],
.simulate-button.danger-ghost {
  background: var(--surface);
  border-color: #ecc7be;
  color: var(--coral);
  box-shadow: none;
}
.simulate-button[data-remove-user]:hover,
.simulate-button[data-device-status][data-next-status="DISABLED"]:hover,
.simulate-button.danger-ghost:hover { background: var(--coral-soft); border-color: var(--coral); color: var(--coral); }

/* Secondary intent (everything else stays neutral) */
.btn-ctrl:disabled, .btn-test:disabled, .simulate-button:disabled, .install-button:disabled { opacity: .45; cursor: not-allowed; }

/* Theme-aware control buttons (were hardcoded white -> invisible text in dark) */
.btn-ctrl { background: var(--surface); color: var(--ink); border-color: var(--line); }
.btn-ctrl:hover { background: var(--green-soft); border-color: var(--green); color: var(--green); }
.btn-ctrl.danger { background: var(--surface); color: var(--coral); border-color: var(--line); }
.btn-ctrl.danger:hover { background: var(--coral-soft); border-color: var(--coral); color: var(--coral); }
[data-theme="dark"] .btn-ctrl { border-color: var(--line-strong); }
[data-theme="dark"] .btn-ctrl:hover { background: #16352a; }
[data-theme="dark"] .btn-ctrl.danger { border-color: #5a3a33; }
[data-theme="dark"] .btn-test { border-color: var(--line-strong); }
[data-theme="dark"] .btn-ctrl:disabled, [data-theme="dark"] .btn-test:disabled { opacity: .5; }


