/* Sized for a 65-year-old holding a phone in a shop, not for a designer's
   screenshot. Body text is 18px, buttons are 20px and at least 60px tall,
   contrast is near-maximum, and nothing important is conveyed by colour
   alone — every red line also carries a symbol and a word. */

/* The palette lives in brand.css, which loads first and defines every
   token this file consumes. It used to be declared here as well, and two
   competing `:root` blocks meant whichever stylesheet happened to load
   last decided what colour the app was — which is how the brand's dark
   default silently lost to the old light one.

   What stays here is the sizing above: 18px body, 20px buttons, 60px tap
   targets. The brand supplies the voice, this file supplies the scale. */

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", "PingFang SC",
               "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-size: 18px;
  line-height: 1.45;
  color: var(--ink);
  background: var(--bg-soft);
  /* Room for the phone's home indicator so the last button is never cut off. */
  padding-bottom: env(safe-area-inset-bottom);
}

/* ── Chrome ──────────────────────────────────────────────────────────── */

#topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  padding-top: calc(8px + env(safe-area-inset-top));
  background: var(--topbar-bg);
  color: var(--topbar-ink);
}

#title {
  flex: 1;
  min-width: 0;
  margin: 0;
  /* 18px, not 20px: the mark now sits in this bar too, and a title that
     ellipsises to "T…" tells nobody anything. */
  font-size: 18px;
  font-weight: 600;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.icon-btn {
  min-width: 48px;
  min-height: 48px;
  font-size: 24px;
  border: 0;
  border-radius: var(--radius);
  background: var(--on-brand-wash);
  color: inherit;
  cursor: pointer;
}
.icon-btn:active { background: var(--on-brand-wash-active); }
.icon-btn[hidden] { display: none; }

.policy-warning {
  position: sticky;
  top: calc(64px + env(safe-area-inset-top));
  z-index: 9;
  max-width: 720px;
  margin: 0 auto;
  padding: 9px 14px;
  color: var(--warn);
  background: var(--warn-bg);
  border: 1px solid var(--warn);
  border-top: 0;
  border-radius: 0 0 var(--radius) var(--radius);
  font-size: 15px;
  font-weight: 700;
  text-align: center;
}
.policy-warning[hidden] { display: none; }

main { padding: 12px; max-width: 720px; margin: 0 auto; }

.desktop-nav { display: none; }
.desktop-nav[hidden] { display: none; }

.pad { padding: 16px; }

/* ── Sign-in identity ────────────────────────────────────────────────── */

.login-shell { max-width: 440px; margin: 5vh auto 0; }
.login-brand {
  display: flex;
  align-items: center;
  gap: 16px;
  margin: 0 4px 18px;
}
.login-logo {
  width: 72px;
  height: 72px;
  border-radius: 16px;
  flex: 0 0 auto;
}
.login-brand-name { margin: 0; font-size: 25px; line-height: 1.15; }
.login-product { margin: 5px 0 0; font-size: 16px; }

/* ── Type ────────────────────────────────────────────────────────────── */

h2 { font-size: 22px; margin: 4px 0 12px; }
h3 { font-size: 19px; margin: 18px 0 8px; }

.cn { display: block; }
.en { display: block; font-size: 0.78em; opacity: 0.75; font-weight: 400; }

.muted { color: var(--ink-soft); }
.small { font-size: 15px; }
.right { text-align: right; }
.mono { font-variant-numeric: tabular-nums; }
/* Shrinks rather than wrapping. At a flat 30px, "RM 1,231.60" beside its
   label breaks after "RM" on a 320px screen, and a money figure split across
   two lines is briefly unreadable as a number — which is the one thing this
   style exists to prevent. */
.big-money {
  font-size: clamp(22px, 7.2vw, 30px);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── Home grid ───────────────────────────────────────────────────────── */

.grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}

.tile {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 4px;
  min-height: 104px;
  padding: 12px 8px;
  font-size: 19px;
  font-weight: 600;
  text-align: center;
  color: var(--ink);
  background: var(--bg);
  border: 2px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
}
.tile:active { background: var(--bg-soft); }
.tile .emoji { font-size: 30px; line-height: 1; }

/* Count of things that need attention, on the tile itself. Sized to be
   readable at arm's length by someone who is not looking for it — this is a
   number that has to be noticed without being sought. `position: relative`
   goes on the tile so the badge hangs off its own corner. */
.tile { position: relative; }
.tile .badge {
  position: absolute;
  top: -10px;
  right: -6px;
  min-width: 34px;
  padding: 4px 9px;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--on-danger);
  background: var(--danger);
  border: 2px solid var(--bg);
  border-radius: 999px;
}
.tile .badge[hidden] { display: none; }

/* ── Cards and lists ─────────────────────────────────────────────────── */

.card {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px;
  margin-bottom: 12px;
}

.row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 6px 0;
}
.row + .row { border-top: 1px solid var(--line); }

/* A row whose number can be opened. Kept visually identical to `.row` so a
   card still reads as one table, then marked with a chevron — these sit
   beside rows that do nothing, and without a mark there is no way to tell
   which figure has records behind it. */
.row-tap {
  width: 100%;
  font: inherit;
  color: var(--ink);
  text-align: left;
  background: none;
  border: 0;
  /* Comfortably tappable without making a card of six rows scroll. */
  min-height: 44px;
  cursor: pointer;
}
.row-tap:active { background: var(--bg-soft); }
.row-tap-value { display: flex; align-items: baseline; gap: 6px; }
.row-tap .chev { font-size: 22px; line-height: 1; color: var(--brand); }

/* The headline row of a card carries `big-money`, and at 320px the chevron
   was enough extra width to push the label into wrapping *inside* a Chinese
   word — "人家欠我" on one line and "们" on the next. Chinese has no spaces,
   so the browser will break anywhere, and RM 1,086.00 is not even a large
   number: RM 111,086.00 would have broken it without the chevron at all.
   Stacking the label above the figure removes the competition for width, so
   it holds at any magnitude. */
.row-tap.headline {
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
}
.row-tap.headline .row-tap-value { justify-content: space-between; }

.list { list-style: none; margin: 0; padding: 0; }

.list-item {
  display: block;
  width: 100%;
  min-height: var(--tap);
  padding: 12px 14px;
  margin-bottom: 8px;
  font-size: 18px;
  text-align: left;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
}
.list-item:active { background: var(--bg-soft); }
.list-item strong { display: block; font-size: 19px; }
/* A second line of small print under the facts. Two inline spans in a row run
   together — "3 张发票 invoices还没输入开销" — because the only child forced
   onto its own line here is `strong`. Scoped rather than applied to every
   span, which would change 57 other rows that deliberately sit inline. */
.list-item .row-note { display: block; margin-top: 2px; }

/* ── Buttons ─────────────────────────────────────────────────────────── */

.btn {
  display: block;
  width: 100%;
  min-height: var(--tap);
  padding: 14px;
  margin-top: 12px;
  font-size: 20px;
  font-weight: 600;
  font-family: inherit;
  color: var(--brand-ink);
  background: var(--brand);
  border: 0;
  border-radius: var(--radius);
  cursor: pointer;
}
.btn:active { filter: brightness(1.2); }
.btn[disabled] { opacity: 0.45; cursor: not-allowed; }

.btn.secondary { color: var(--ink); background: var(--bg); border: 2px solid var(--line); }
.btn.danger { background: var(--danger); }
.btn.compact { min-height: 44px; padding: 8px 12px; font-size: 16px; }
a.btn { text-align: center; text-decoration: none; }

.btn-row { display: flex; gap: 10px; }
.btn-row .btn { flex: 1; }

.chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
/* An author `display` beats the browser's own `[hidden] { display: none }`,
   so a hidden chip row stays on screen. This is the fifth element in this
   file to need the same line — `.note`, `.icon-btn`, `.policy-warning` and
   the tile badge got there first. It showed up as the custom date pickers
   sitting under the period chips while a period chip was selected. */
.chip-row[hidden] { display: none; }

/* Invoice filters describe two separate questions. Grouping them prevents
   "All dates" from reading as though it also switched off "Outstanding". */
.invoice-filters {
  display: grid;
  gap: 8px;
  margin: 8px 0 10px;
}
.invoice-filter-group {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.invoice-filter-group legend {
  padding: 0;
  color: var(--ink-soft);
  font-size: 14px;
  font-weight: 600;
}
.invoice-filter-group .chip-row { margin: 3px 0 0; }

/* These wrappers provide stable laptop rows below, while `contents` keeps the
   established stacked, large-target record cards unchanged on phones. They
   are shared by invoices, debtors, supplier payments and stock: every one of
   those records has a split identity/headline row followed by a variable
   number of facts or warnings, then zero or more actions. */
.record-card-meta,
.record-card-actions { display: contents; }
.record-card-meta > .row:first-child { border-top: 1px solid var(--line); }
.record-headline-label { display: none; }

/* Uniform records keep their large-target cards/timeline on phones. Their
   structured comparison table is enabled only inside the laptop breakpoint. */
.desktop-table-wrap { display: none; }

.chip {
  min-width: 62px;
  min-height: 52px;
  padding: 8px 14px;
  font-size: 19px;
  font-family: inherit;
  color: var(--ink);
  background: var(--bg);
  border: 2px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
}
.chip[aria-pressed="true"] { color: var(--brand-ink); background: var(--brand); border-color: var(--brand); }

/* A filter that is reached rarely, sized to match `.btn.tight` so the quiet
   controls on a screen agree with each other. It stops at 48px rather than
   shrinking further: this is a toggle two people in their sixties have to hit
   on a phone, and below that it stops being reliably tappable. Smaller in
   weight, not in reach. */
/* One open invoice inside the payment form: what it owes, and beneath it
   what this payment does to it. The rule separates them so three invoices do
   not read as six loose rows. */
.invoice-line + .invoice-line {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}
.invoice-line .row.small { opacity: .85; }

.chip.tight {
  min-height: 48px;
  padding: 8px 12px;
  font-size: 15px;
}

.customer-directory-count { margin: 12px 0 6px; }
.customer-result-count { min-height: 20px; margin: 10px 0 4px; }

.alphabet-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
  margin: 6px 0 10px;
}

.alphabet-chip {
  min-width: 0;
  min-height: 52px;
  padding: 5px 2px;
  font-family: inherit;
  color: var(--ink);
  background: var(--bg);
  border: 2px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
}
.alphabet-chip[aria-pressed="true"] {
  color: var(--brand-ink);
  background: var(--brand);
  border-color: var(--brand);
}
.alphabet-chip:disabled { opacity: 0.35; cursor: not-allowed; }
.alphabet-letter { display: block; font-size: 16px; font-weight: 700; line-height: 1.1; }
.alphabet-count { display: block; margin-top: 3px; font-size: 11px; line-height: 1; opacity: 0.78; }

/* ── Forms ───────────────────────────────────────────────────────────── */

label {
  display: block;
  margin: 14px 0 6px;
  color: var(--ink);
  font-weight: 700;
}

input:not([type="checkbox"]):not([type="radio"]),
select,
textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 12px;
  font-size: 19px;
  font-family: inherit;
  color: var(--ink);
  caret-color: var(--brand);
  background: var(--field-bg);
  border: 2px solid var(--field-border);
  border-radius: 10px;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .22);
  transition: border-color 120ms linear, box-shadow 120ms linear, background 120ms linear;
}

input::placeholder,
textarea::placeholder {
  color: var(--field-placeholder);
  opacity: 1;
}

select { cursor: pointer; }
textarea { resize: vertical; }

input:not([type="checkbox"]):not([type="radio"]):not(:disabled):not([readonly]):hover,
select:not(:disabled):hover,
textarea:not(:disabled):not([readonly]):hover {
  border-color: var(--field-border-hover);
}

input:not([type="checkbox"]):not([type="radio"]):disabled,
select:disabled,
textarea:disabled {
  color: var(--ink-soft);
  background: var(--field-disabled-bg);
  border-color: var(--line);
  box-shadow: none;
  cursor: not-allowed;
  opacity: 1;
}

input[readonly],
textarea[readonly] {
  color: var(--ink-soft);
  background: var(--field-disabled-bg);
  border-color: var(--line);
  box-shadow: none;
}

input[type="checkbox"],
input[type="radio"] { accent-color: var(--brand); }

input:user-invalid,
select:user-invalid,
textarea:user-invalid,
[aria-invalid="true"] {
  border-color: var(--danger);
}

.compact-field-grid { display: grid; gap: 0 12px; }
.compact-field-grid label { min-width: 0; }
.compact-field-grid label > span { display: block; margin-bottom: 5px; font-size: 15px; }
.check-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 500;
}
.check-row input[type="checkbox"] { width: 28px; min-height: 28px; flex: 0 0 auto; }

.page-heading,
.list-heading {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
}
.page-heading h2 { margin-bottom: 2px; }
.page-heading p { margin: 0 0 12px; }
.list-heading strong { min-width: 0; }

.data-card {
  padding: 14px;
  margin-bottom: 12px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.sales-line-grid {
  display: grid;
  gap: 4px 12px;
  padding: 12px 0;
  border-top: 1px solid var(--line);
}
.sales-line-grid:first-of-type { border-top: 0; }
.sales-line-grid > div span { display: block; }
.release-line {
  display: grid;
  gap: 8px;
  padding: 12px 0;
  border-top: 1px solid var(--line);
}
.release-line > div span { display: block; }
.release-line input { margin: 0; }

/* A confirmation the founder has to read, so the words sit beside the box
   rather than under it, and the whole row is the tap target. */
.checkline {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 0;
}
.checkline input[type="checkbox"] {
  width: 22px;
  height: 22px;
  margin: 2px 0 0;
  flex: none;
}
.checkline span { font-weight: 600; }

/* The amend line gets a class of its own rather than borrowing
   `.release-line`. That one is already given two columns further down this
   file, inside another 1024px block — equal specificity, later wins — so a
   four-column rule here would be silently overridden and the row would wrap.
   It also belongs to the release form, which has one input, not three. */
.amend-line {
  display: grid;
  gap: 8px;
  padding: 12px 0;
  border-top: 1px solid var(--line);
}
.amend-line > div span { display: block; }
.amend-line input { margin: 0; }
.line-value { margin-top: 8px; font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }
.future-order-workspace { display: grid; gap: 12px; }
.order-entry-workspace { display: grid; gap: 12px; }
.order-product-panel,
.order-basket-panel { min-width: 0; }
.order-search {
  position: relative;
}
.order-search-input { padding-right: 60px; }
/* Chromium supplies its own tiny search cancel control, but it is not
   consistently present in installed PWAs and is almost invisible in dark
   mode. The explicit button is the same on laptop and phone. */
.order-search-input::-webkit-search-cancel-button { appearance: none; }
.order-search-clear {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 48px;
  height: 48px;
  padding: 0;
  color: var(--ink);
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 8px;
  font: inherit;
  font-size: 28px;
  line-height: 1;
  cursor: pointer;
}
.order-search-clear[hidden] { display: none; }
.order-search-results:empty { display: none; }
.order-add-title { margin-top: 0; overflow-wrap: anywhere; }
.order-add-fields { display: grid; gap: 0 10px; }
.order-add-field { min-width: 0; }
.order-stock-note,
.order-pack-note,
.order-price-note { margin: 5px 0; }
.order-repeat-action { display: grid; gap: 8px; margin-bottom: 12px; }
.order-repeat-copy > * { display: block; }
.order-repeat-copy > span { margin-top: 3px; }
.order-repeat-button { margin-top: 0; border-color: var(--brand); }
.basket-money-grid {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
  font-variant-numeric: tabular-nums;
}
.basket-price-control,
.basket-line-total {
  display: grid;
  gap: 2px;
}
.basket-name > strong,
.basket-name > span { display: block; }
.basket-control-label { display: block; margin-bottom: 4px; }
.basket-line-total { justify-items: end; text-align: right; }
.basket-line-total strong { white-space: nowrap; }
.basket-money-label { line-height: 1.1; white-space: nowrap; }
/* The price box on a basket line. Narrower than the quantity box and right
   aligned, because money is read down a column and quantities are not.
   `.edited` marks a price the founder typed rather than the customer's own
   rate — without it a negotiated price and a resolved one look identical, and
   the difference is what the floor warning is about. */
.price-field {
  display: grid;
  grid-template-columns: minmax(96px, 1fr);
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.price-box {
  width: 100%;
  min-width: 0;
  min-height: var(--tap);
  padding: 8px 10px;
  font-size: 19px;
  font-weight: 700;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: var(--field-bg);
  border: 2px solid var(--field-border);
  border-radius: 8px;
}
.price-box.edited {
  border-color: var(--brand);
  color: var(--brand);
}

/* ── Purchases and accounts payable ─────────────────────────────────── */
.purchase-overview-grid { display: grid; gap: 12px; }
.purchase-list-heading { margin: 18px 0 8px; }
.purchase-list-heading h3,
.purchase-list-heading p { margin: 0; }
.purchase-list-heading p { margin-top: 3px; }
.purchase-row-actions,
.purchase-record-actions,
.supplier-primary-actions {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
}
.purchase-row-actions .btn,
.purchase-record-actions .btn,
.supplier-primary-actions .btn { margin-top: 0; }
.purchase-bill-card .card-head { align-items: flex-start; }
.purchase-bill-card .card-head > :first-child { min-width: 0; overflow-wrap: anywhere; }
.purchase-bill-card .big-money { white-space: nowrap; }
.purchase-description { padding: 7px 0; border-top: 1px solid var(--line); }
.purchase-description .cn,
.purchase-description .en { color: var(--ink-soft); font-size: 13px; }
.purchase-description p { margin: 3px 0 0; overflow-wrap: anywhere; }
.supplier-heading,
.record-detail-heading,
.supplier-payment-heading {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
}
.supplier-heading h2,
.supplier-heading p,
.record-detail-heading h2,
.record-detail-heading p,
.supplier-payment-heading h2 { margin-top: 0; margin-bottom: 4px; }
.supplier-heading > div,
.record-detail-heading > div,
.supplier-payment-heading h2 { min-width: 0; overflow-wrap: anywhere; }
.supplier-call { width: auto; margin-top: 0; white-space: nowrap; }
.supplier-section-tabs { margin: 14px 0 4px; }
.supplier-section-title { margin-bottom: 6px; }
.purchase-actions-cell { white-space: nowrap; }
.purchase-actions-cell .btn { margin-top: 0; }
.supplier-payment-heading .pill { flex: 0 0 auto; }
.supplier-payment-layout,
.supplier-payment-fields,
.supplier-bill-fields { display: grid; gap: 12px; }
.supplier-payment-form-card .field > label { margin-top: 0; }
.supplier-payment-form-card .btn { margin-top: 10px; }
.supplier-payment-reference-help { margin: 8px 0 0; }
.goods-in-workspace { display: grid; gap: 12px; }
.goods-in-bill-heading { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line); }
.goods-in-current > h3,
.goods-in-context > h3:first-child,
.goods-in-finder > h3:first-child { margin-top: 0; }
.goods-in-line-name { min-width: 0; }
.goods-in-line-name span { min-width: 0; overflow-wrap: anywhere; }
.goods-in-line-cost .cn,
.goods-in-line-cost .en { display: inline; }

@media (max-width: 520px) {
  /* Long supplier names, bill references and five-figure amounts must shrink
     inside the phone viewport. Flex items otherwise retain their min-content
     width and push the whole page sideways. */
  .supplier-heading,
  .record-detail-heading,
  .supplier-payment-heading { flex-wrap: wrap; }
  .supplier-payment-heading .pill { max-width: 100%; white-space: normal; }
  .supplier-payable-head,
  .supplier-payment-workspace > .card:first-of-type > .row {
    align-items: stretch;
    flex-direction: column;
  }
  .supplier-payable-head .big-money,
  .supplier-payment-workspace > .card:first-of-type .big-money { align-self: flex-end; }
  .correction-history .row { flex-wrap: wrap; }
  .correction-history .row > .right {
    max-width: 100%;
    margin-left: auto;
    overflow-wrap: anywhere;
  }
}

.order-detail-line {
  display: grid;
  gap: 4px 12px;
  padding: 11px 0;
  border-top: 1px solid var(--line);
}
.order-detail-line:first-of-type { border-top: 0; }
.order-detail-line > div span { display: block; }
.correction-history {
  margin-top: 12px;
  padding: 10px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 10px;
}
.correction-history > span { display: block; }
.correction-panel { margin-top: 12px; border-top: 1px solid var(--line); }
.correction-panel summary {
  padding: 12px 0;
  font-weight: 700;
  cursor: pointer;
}
.sticky-summary { align-self: start; }
.screen-actions .btn { margin-top: 0; }

/* Business activity */
.home-overview { display: grid; gap: 12px; }
.activity-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: 10px 18px;
  margin-bottom: 8px;
}
.activity-toolbar strong { display: block; font-size: 19px; }
.activity-subtitle { display: block; margin-top: 2px; }
.activity-date { display: flex; align-items: center; gap: 8px; }
.activity-date input { min-width: 160px; }
.activity-date .btn { width: auto; margin: 0; }
.activity-count { align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
.activity-count .chip-row { flex: 1 1 520px; margin: 0; }
.activity-count > .small { white-space: nowrap; }
.activity-timeline { position: relative; list-style: none; margin: 0; padding: 0; }
.activity-card { padding-top: 0; padding-bottom: 0; }
.activity-item { position: relative; margin: 0; border-top: 1px solid var(--line); }
.activity-item:first-child { border-top: 0; }
.activity-link {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 8px 12px;
  width: 100%;
  min-height: 68px;
  padding: 9px 4px;
  font: inherit;
  color: inherit;
  text-align: left;
  background: transparent;
  border: 0;
}
button.activity-link { cursor: pointer; }
button.activity-link:active { background: var(--bg-soft); }
.activity-dot {
  width: 10px;
  height: 10px;
  border: 2px solid currentColor;
  border-radius: 50%;
  color: var(--brand);
  background: currentColor;
  box-shadow: 0 0 0 3px var(--bg);
}
.activity-sales { color: var(--brand); }
.activity-money { color: var(--ok); }
.activity-stock { color: var(--warn); }
.activity-purchasing { color: var(--tp-flare); }
.activity-customers { color: var(--ink-soft); }
.activity-approvals { color: var(--danger); }
.activity-settings { color: var(--tp-mute); }
.activity-copy { display: block; min-width: 0; }
.activity-title,
.activity-cn,
.activity-meta { display: block; }
.activity-title { font-weight: 700; overflow-wrap: anywhere; }
.activity-cn { margin-top: 1px; font-size: 14px; color: var(--ink-soft); }
.activity-meta {
  margin-top: 3px;
  font-size: 13px;
  color: var(--ink-soft);
  overflow-wrap: anywhere;
}
.activity-amount { font-weight: 700; white-space: nowrap; }
.activity-open { font-size: 25px; line-height: 1; color: var(--brand); }
.activity-preview .activity-link { min-height: 56px; padding: 6px 2px; }
.activity-preview .activity-cn,
.activity-preview .activity-copy > .small { display: none; }
.activity-preview .activity-meta { margin-top: 1px; }
.link-button {
  min-height: 36px;
  padding: 4px 7px;
  font: inherit;
  font-size: 14px;
  color: var(--brand);
  background: transparent;
  border: 0;
  cursor: pointer;
}
.link-button:hover { text-decoration: underline; }

@media (max-width: 520px) {
  .activity-link { grid-template-columns: 14px minmax(0, 1fr) auto; }
  .activity-amount { grid-column: 2; }
  .activity-open { grid-column: 3; grid-row: 1 / span 2; }
}

/* ── Laptop workspace ──────────────────────────────────────────────────
   The phone remains the large-target, single-column interface. At laptop
   width the same routes become an operations console: persistent navigation,
   denser controls, more columns, and room for working context beside forms. */
@media (min-width: 1024px) {
  /* ── One record, one row ────────────────────────────────────────────────
     A card that describes a single record — a basket line, an invoice, a
     supplier bill — stacks its parts on a phone because 390px leaves no
     choice. On a laptop that same stack turns a five-item order into five
     screens of scrolling, with most of a 1440px viewport empty to the right.

     Here those children lay out along one line and wrap only when they must.
     The header keeps its own full-width line so the customer and the amount
     stay the anchor; everything after it — totals, dates, buttons — flows
     beside it. Nothing about the markup or the phone layout changes. */
  .card.compact {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 22px;
  }
  /* The head keeps its own line and its own spacing: the customer belongs on
     the left and the amount on the right, which is where the eye goes first
     down a list of invoices. Only the detail rows below it become inline. */
  .card.compact > .card-head { flex: 1 0 100%; }
  /* Controls sit at the end of the line, away from the figures. */
  .card.compact > .btn-row:last-child,
  .card.compact > .btn:last-child,
  .card.compact > a.btn:last-child { margin-left: auto; }

  /* `.row` is a space-between pair sized to the card. Inside a compact card
     it becomes a label-and-value pair sized to its own content, so several
     sit on one line instead of one each. */
  .card.compact > .row {
    width: auto;
    padding: 0;
    gap: 8px;
    justify-content: flex-start;
  }
  .card.compact > .row > .right { text-align: left; }
  /* The head is also a `.row`, and the rule above would pull its amount in
     beside the name. Named with both classes so it outranks that, and keeps
     the customer left and the money hard right where the eye looks first. */
  .card.compact > .row.card-head { justify-content: space-between; }
  .card.compact > .row.card-head > .right { text-align: right; }

  /* Buttons are full-width blocks on a phone. Beside a record they are
     controls, not calls to action. */
  .card.compact > .btn,
  .card.compact > a.btn,
  .card.compact > .btn-row {
    width: auto;
    margin: 0;
    flex: 0 0 auto;
  }
  .card.compact > .btn-row { gap: 8px; }
  .card.compact > .btn-row .btn { flex: 0 0 auto; width: auto; }
  .card.compact > .note { flex: 1 0 100%; margin: 0; }
  /* The goods on an order card are a list, not a field: they keep their own
     full-width block under the inline facts. */
  .card.compact > .goods { flex: 1 0 100%; margin: 4px 0 0; }
  .card.compact > h3 { margin: 0; }

  /* Split-headline record cards have variable financial facts and warnings.
     Treating every child as an independent flex item moved the headline
     figure whenever another fact appeared, and warnings split the buttons
     into apparently unrelated rows. Keep three explicit bands instead:
     identity + headline, compact context, then actions. */
  .card.record-card { padding: 10px 12px; }
  .record-card > .card-head {
    padding: 0 0 5px;
    border-bottom: 1px solid var(--line);
  }
  .record-headline {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex: 0 0 auto;
  }
  .record-headline-label {
    display: block;
    color: var(--ink-soft);
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
  }
  .record-card-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 5px 14px;
    padding-top: 6px;
  }
  .record-card-meta:empty { display: none; }
  .record-card-meta > .row {
    width: auto;
    gap: 6px;
    padding: 2px 0;
    border: 0;
  }
  .record-card-meta > .row > .right { text-align: left; }
  .record-card-meta > p { flex: 0 1 auto; margin: 0; }
  .record-card-meta > .note {
    flex: 0 1 auto;
    padding: 4px 8px;
    margin: 0;
    border-left-width: 4px;
    border-radius: 8px;
    font-size: 13px;
  }
  .record-card-meta > .invoice-withdrawn { flex: 1 0 100%; }
  .record-card-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 7px;
    padding-top: 7px;
    margin-top: 6px;
    border-top: 1px solid var(--line);
  }
  .record-card-actions > .btn {
    flex: 0 0 auto;
    width: auto;
    min-height: 40px;
    padding: 7px 10px;
    margin: 0;
    font-size: 14px;
  }
  /* Opening the credit-note form replaces its button with a wrapper. It is a
     form, not another toolbar item, and therefore takes the whole row. */
  .record-card-actions > div { flex: 1 0 100%; }

  /* The order-detail header is a facts strip on a laptop: Status,
     Fulfilment, Date, Lines and Total read across the screen instead of using
     five full-width rows. At phone widths this class has no layout rules, so
     the familiar stacked summary and its larger scan targets remain intact. */
  .card.order-summary {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(105px, 1fr));
    align-items: stretch;
    row-gap: 8px;
  }
  .card.order-summary > .row {
    min-width: 0;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 2px;
    padding: 2px 12px;
    border-top: 0;
    border-left: 1px solid var(--line);
  }
  .card.order-summary > .row:first-child {
    padding-left: 2px;
    border-left: 0;
  }
  .card.order-summary > .row > :first-child {
    color: var(--ink-soft);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.2;
  }
  .card.order-summary > .row > .right {
    width: 100%;
    color: var(--ink);
    text-align: left;
    white-space: normal;
  }
  .card.order-summary > .row > .big-money {
    font-size: 22px;
    white-space: nowrap;
  }

  /* Date range and payment status are independent filters. They share a row
     on a laptop, but the divider makes it clear that "All dates" does not mean
     "All invoices". */
  .invoice-filters {
    grid-template-columns: minmax(0, 1fr) max-content;
    align-items: start;
    gap: 14px;
    margin: 6px 0 8px;
  }
  .invoice-filter-status {
    padding-left: 14px;
    border-left: 1px solid var(--line);
  }

  /* Uniform drill-down and activity records are comparison work on a laptop:
     document numbers, parties, dates and amounts align in real columns.
     Action-heavy invoice/order cards remain cards. */
  .drill-mobile-list,
  .activity-mobile-timeline { display: none; }
  .desktop-table-wrap {
    display: block;
    width: 100%;
    overflow-x: auto;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius);
  }
  .desktop-table {
    width: 100%;
    min-width: 700px;
    border-collapse: collapse;
    font-size: 14px;
    line-height: 1.3;
  }
  .desktop-table th,
  .desktop-table td {
    padding: 8px 10px;
    text-align: left;
    vertical-align: middle;
    border-bottom: 1px solid var(--line);
  }
  .desktop-table th {
    color: var(--ink-soft);
    background: var(--bg-soft);
    font-size: 12px;
    font-weight: 700;
    white-space: nowrap;
  }
  .desktop-table tbody tr:last-child td { border-bottom: 0; }
  .desktop-table tbody tr:hover td,
  .desktop-table tbody tr:focus-within td { background: var(--bg-soft); }
  .desktop-table .date-cell,
  .desktop-table .reference-cell,
  .desktop-table .money-cell { white-space: nowrap; }
  .desktop-table .party-cell { min-width: 190px; font-weight: 600; }
  .desktop-table .details-cell { min-width: 170px; }
  .desktop-table .money-cell { text-align: right; font-weight: 700; }
  .desktop-table .details-cell.danger,
  .desktop-table .money-cell.danger { color: var(--danger); }
  .desktop-table a {
    color: var(--brand);
    font-weight: 700;
    text-underline-offset: 2px;
  }
  .desktop-table a.btn {
    color: var(--brand-ink);
    text-decoration: none;
  }
  .desktop-table a.btn.secondary { color: var(--ink); }

  /* Purchases is comparison work on a laptop: due dates, supplier invoice
     numbers and amounts line up as a table. Phones retain the larger cards.
     Actions stay in their own compact track rather than turning each record
     into a second row of full-width buttons. */
  .purchase-mobile-list { display: none; }
  .purchase-overview-grid {
    grid-template-columns: minmax(320px, .9fr) minmax(360px, 1.1fr);
    align-items: stretch;
  }
  .purchase-overview-grid > .card { margin-bottom: 0; }
  .purchase-overview-grid .note { padding: 8px 10px; font-size: 13px; }
  .purchase-table { min-width: 820px; }
  .purchase-table .purchase-actions-cell { width: 210px; }
  .purchase-actions-cell .purchase-row-actions {
    display: flex;
    align-items: center;
    gap: 6px;
  }
  .purchase-actions-cell .btn {
    width: auto;
    min-width: 82px;
    padding: 7px 10px;
    font-size: 13px;
    white-space: nowrap;
  }
  .supplier-summary-card {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0;
    padding: 10px 14px;
  }
  .supplier-summary-card > .row {
    display: grid;
    align-content: start;
    gap: 2px;
    padding: 2px 14px;
    border-top: 0;
    border-left: 1px solid var(--line);
  }
  .supplier-summary-card > .row:first-child {
    padding-left: 2px;
    border-left: 0;
  }
  .supplier-summary-card > .row > :first-child {
    color: var(--ink-soft);
    font-size: 12px;
    font-weight: 600;
  }
  .supplier-summary-card > .row > .right,
  .supplier-summary-card > .row > .big-money { text-align: left; }
  .supplier-primary-actions,
  .purchase-record-actions {
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    margin: 10px 0 12px;
  }
  .supplier-section-tabs { margin-top: 8px; }
  .supplier-section-tabs .chip { min-height: 44px; padding: 5px 12px; font-size: 15px; }
  .record-detail-heading { align-items: center; }
  .supplier-payment-layout {
    grid-template-columns: minmax(300px, .8fr) minmax(430px, 1.2fr);
    align-items: start;
  }
  .supplier-payment-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .supplier-bill-form { max-width: 980px; }
  .supplier-bill-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .supplier-payment-method { grid-column: 1 / -1; }
  .supplier-payment-method .chip-row { margin-bottom: 0; }
  .supplier-payment-method .chip { min-height: 44px; padding: 5px 10px; font-size: 15px; }
  .goods-in-workspace {
    grid-template-columns: minmax(300px, .85fr) minmax(380px, 1.15fr);
    align-items: start;
  }
  .goods-in-context { padding: 12px; }
  .goods-in-context label { margin-top: 8px; }
  .goods-in-context input {
    min-height: 44px;
    margin-top: 4px;
    padding-top: 7px;
    padding-bottom: 7px;
  }
  .goods-in-context .chip {
    min-height: 40px;
    padding-top: 5px;
    padding-bottom: 5px;
  }
  .goods-in-context .goods-in-bill-heading {
    margin-top: 12px;
    padding-top: 10px;
  }
  .goods-in-context .goods-in-bill-heading + p {
    margin: 6px 0;
    font-size: 12px;
    line-height: 1.25;
  }
  .goods-in-current { grid-column: 1 / -1; }
  .card.goods-in-line {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(110px, 140px);
    align-items: center;
    gap: 6px 12px;
    padding: 9px 12px;
  }
  .goods-in-line-name { grid-column: 1; grid-row: 1; padding: 0; }
  .goods-in-line-remove {
    grid-column: 2;
    grid-row: 1;
    width: auto;
    min-height: 38px;
    margin: 0;
    padding: 6px 10px;
    font-size: 13px;
  }
  .goods-in-line-quantity-label { grid-column: 1; grid-row: 2; margin: 0; }
  .card.goods-in-line > input { grid-column: 2; grid-row: 2; min-height: 40px; margin: 0; }
  .goods-in-line-cost { grid-column: 1 / -1; grid-row: 3; color: var(--ink-soft); font-size: 13px; }

  .activity-table { min-width: 760px; }
  .activity-table .details-cell { min-width: 180px; }
  .activity-category-cell { min-width: 100px; }
  .activity-category {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .activity-category .activity-dot { flex: 0 0 auto; }
  .activity-category-en,
  .activity-category-cn,
  .activity-title-cell > a,
  .activity-title-cell > span,
  .activity-reference,
  .activity-table-detail { display: block; }
  .activity-category-en,
  .activity-title-cell > a,
  .activity-title-cell > span:first-child { font-weight: 700; }
  .activity-category-cn,
  .activity-table-cn,
  .activity-table-detail {
    margin-top: 1px;
    color: var(--ink-soft);
    font-size: 12px;
  }
  .activity-title-cell { min-width: 160px; }
  .activity-actor-cell { min-width: 100px; }

  /* ── The A–Z filter ─────────────────────────────────────────────────────
     Twenty-eight chips at seven across and 52px tall is about 230px of a
     screen before a single customer is listed. That height is a tap target
     for a phone and the reason it exists; a mouse does not need it.

     `auto-fit` rather than a fixed column count, because this picker appears
     both full width and inside the narrow middle panel of the order
     workspace — it fills whatever width it is given instead of being told a
     number that is wrong in one of the two places. */
  .alphabet-grid {
    grid-template-columns: repeat(auto-fit, minmax(44px, 1fr));
    gap: 4px;
    margin: 4px 0 8px;
  }
  .alphabet-chip { min-height: 36px; padding: 3px 2px; border-width: 1px; }
  .alphabet-letter { font-size: 14px; }
  .alphabet-count { margin-top: 0; font-size: 10px; }

  /* A basket line has two stable rows. Identity and Remove own the first;
     quantity and money own the second. Nothing competes with the quantity
     input for leftover flex width, so 100, 1,000 and 10,000 remain legible. */
  .card.basket-line {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 6px;
    padding: 10px 14px;
    margin-bottom: 8px;
  }
  .card.basket-line .basket-name {
    grid-column: 1;
    grid-row: 1;
    min-width: 0;
  }
  .card.basket-line .basket-remove {
    grid-column: 2;
    grid-row: 1;
    align-self: start;
    width: auto;
    min-height: 38px;
    margin: 0;
    padding: 6px 10px;
    font-size: 13px;
  }
  .card.basket-line .basket-controls {
    grid-column: 1 / -1;
    grid-row: 2;
    display: grid;
    grid-template-columns: minmax(180px, .9fr) minmax(225px, 1.1fr);
    align-items: end;
    gap: 10px;
  }
  .card.basket-line .basket-control-label,
  .card.basket-line .basket-money-label { font-size: 11px; }
  .card.basket-line .qty-stepper {
    display: grid;
    grid-template-columns: 42px minmax(90px, 1fr) 42px;
    gap: 6px;
    margin: 0;
  }
  .card.basket-line .qty-step {
    width: 100%;
    min-height: 40px;
  }
  .card.basket-line .qty-box {
    width: 100%;
    min-width: 90px;
    min-height: 40px;
    padding: 5px 7px;
    font-size: 18px;
  }
  .card.basket-line .price-box {
    min-height: 40px;
    padding: 6px 8px;
    font-size: 16px;
  }
  .card.basket-line .price-field { grid-template-columns: minmax(88px, 1fr); }
  .card.basket-line .basket-money-grid {
    display: grid;
    grid-template-columns: minmax(96px, .8fr) minmax(115px, 1fr);
    align-items: end;
    gap: 10px;
    margin: 0;
    padding: 0;
    border-top: 0;
  }

  body.authenticated {
    font-size: 16px;
    padding-left: 224px;
  }

  body.authenticated .desktop-nav {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 30;
    display: flex;
    width: 224px;
    flex-direction: column;
    padding: 12px 10px;
    color: var(--topbar-ink);
    background: var(--topbar-bg);
    border-right: 1px solid var(--on-brand-wash);
    overflow-y: auto;
  }

  .desktop-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px;
    color: inherit;
    text-align: left;
    background: transparent;
    border: 0;
    border-radius: var(--radius);
    cursor: pointer;
  }
  .desktop-brand:hover { background: var(--on-brand-wash); }
  .desktop-brand img { object-fit: contain; }
  .desktop-brand strong,
  .desktop-brand small { display: block; }
  .desktop-brand strong { font-size: 15px; }
  .desktop-brand small { margin-top: 2px; font-size: 11px; opacity: .72; }

  #desktop-nav-links { margin-top: 8px; }
  .desktop-nav-group { margin: 0 0 8px; }
  .desktop-nav-group h2 {
    margin: 0 8px 5px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    opacity: .55;
  }
  .desktop-nav-link {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    min-height: 34px;
    padding: 6px 9px;
    color: inherit;
    text-decoration: none;
    border-radius: 9px;
  }
  .desktop-nav-link small { font-size: 12px; opacity: .63; }
  .desktop-nav-link:hover { background: var(--on-brand-wash); }
  .desktop-nav-link[aria-current="page"] {
    color: var(--brand-ink);
    font-weight: 700;
    background: var(--brand);
  }
  .desktop-nav-link[aria-current="page"] small { opacity: .8; }
  .desktop-user {
    display: flex;
    flex-direction: column;
    margin-top: auto;
    padding: 12px 10px 2px;
    border-top: 1px solid var(--on-brand-wash);
  }
  .desktop-user span { font-size: 12px; opacity: .65; text-transform: capitalize; }

  body.authenticated #topbar { min-height: 52px; padding: 5px 18px; padding-top: 5px; }
  body.authenticated #topbar .brandmark { display: none; }
  body.authenticated #title { font-size: 18px; text-align: left; }
  body.authenticated .icon-btn { min-width: 40px; min-height: 40px; font-size: 21px; }
  body.authenticated .policy-warning {
    top: 52px;
    max-width: none;
    margin: 0 18px;
    border-radius: 0 0 var(--radius) var(--radius);
  }
  body.authenticated main {
    width: auto;
    max-width: 1720px;
    margin: 0 auto;
    padding: 12px 18px 28px;
  }

  body.authenticated .grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 8px;
  }
  body.authenticated .tile { min-height: 78px; padding: 8px 6px; font-size: 15px; }
  body.authenticated .tile .emoji { font-size: 22px; }
  body.authenticated .card { padding: 10px; margin-bottom: 8px; }
  body.authenticated .btn {
    min-height: 44px;
    padding: 9px 12px;
    font-size: 16px;
  }
  body.authenticated input,
  body.authenticated select,
  body.authenticated textarea {
    min-height: 44px;
    padding: 8px 10px;
    font-size: 16px;
  }
  body.authenticated label { margin: 7px 0 3px; }
  body.authenticated .chip { min-height: 38px; padding: 5px 10px; font-size: 14px; }
  body.authenticated .chip-row { gap: 6px; margin: 5px 0; }
  body.authenticated .list-item {
    min-height: 44px;
    padding: 8px 10px;
    margin-bottom: 6px;
    font-size: 16px;
  }
  body.authenticated .list-item strong { font-size: 16px; }
  body.authenticated h2 { margin: 2px 0 8px; }
  body.authenticated h3 { margin: 12px 0 6px; }
  body.authenticated .row { padding: 4px 0; }
  body.authenticated .note { padding: 9px 10px; margin: 6px 0; }

  body.authenticated .home-overview.has-activity {
    grid-template-columns: minmax(280px, .72fr) minmax(440px, 1.28fr);
    align-items: start;
    gap: 10px;
  }
  body.authenticated .home-overview > * { min-width: 0; }
  body.authenticated .activity-card { padding-top: 0; padding-bottom: 0; }
  body.authenticated .activity-link { min-height: 54px; padding: 6px 4px; }
  body.authenticated .activity-toolbar { margin-bottom: 5px; }
  body.authenticated .activity-date input { min-height: 38px; }

  /* The left order-entry column is a picker, not a form page. Keep the
     selected item, quantity, selling price and Add action visible together
     inside a 768px laptop viewport. Phones retain the larger stacked fields
     and tap targets above. */
  .order-product-panel > h3 { margin: 0 0 6px; }
  .order-search-input { min-height: 40px; padding: 6px 44px 6px 9px; }
  .order-search-clear {
    top: 3px;
    right: 3px;
    width: 34px;
    height: 34px;
    font-size: 22px;
  }
  .order-search-results.showing-products {
    max-height: calc(100vh - 250px);
    margin-top: 6px;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .order-add-card { padding: 8px; margin: 6px 0 0; }
  .order-add-card .order-add-title { margin: 0 0 3px; font-size: 15px; line-height: 1.25; }
  .order-add-card .order-stock-note,
  .order-add-card .order-pack-note,
  .order-add-card .order-price-note { margin: 2px 0; font-size: 12px; line-height: 1.25; }
  .order-add-card .note { padding: 5px 7px; margin: 3px 0; font-size: 12px; }
  .order-add-card .order-quantity-chips {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 4px;
    margin: 4px 0;
  }
  .order-add-card .order-quantity-chips .chip {
    min-width: 0;
    min-height: 34px;
    padding: 3px 2px;
    font-size: 13px;
  }
  .order-add-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .order-add-card .order-add-field { margin: 3px 0 0; font-size: 12px; }
  .order-add-card .order-add-field > span { display: block; margin-bottom: 2px; }
  .order-add-card .order-add-field input {
    min-height: 38px;
    padding: 5px 7px;
    font-size: 15px;
  }
  .order-add-card .order-add-button { min-height: 40px; margin-top: 5px; padding: 6px 9px; }
  .order-repeat-action {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 16px;
  }
  .order-repeat-action .order-repeat-button { width: auto; min-width: 250px; }

  .compact-field-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .future-order-workspace {
    grid-template-columns: minmax(260px, .8fr) minmax(390px, 1.4fr) minmax(260px, .8fr);
    align-items: start;
  }
  .order-entry-workspace {
    grid-template-columns: minmax(230px, .75fr) minmax(450px, 1.25fr);
    align-items: start;
  }
  .order-entry-workspace > * { min-width: 0; }
  .order-entry-workspace > .sticky-summary {
    grid-column: 1 / -1;
    position: static;
  }
  .order-detail-line {
    grid-template-columns: minmax(260px, 1.5fr) minmax(140px, .75fr) minmax(120px, .6fr) minmax(150px, .75fr);
    align-items: center;
  }
  .future-order-workspace > * { min-width: 0; }
  .sticky-summary { position: sticky; top: 64px; }
  .release-line { grid-template-columns: minmax(0, 1fr) 160px; align-items: center; }
  /* Goods, quantity, price, remove — one row. On a phone it stays stacked:
     three controls across 390px is how a price gets typed into a quantity
     box. */
  .amend-line { grid-template-columns: minmax(0, 1fr) 96px 110px auto; align-items: center; }
  .sales-line-grid {
    grid-template-columns: minmax(220px, 1.6fr) repeat(4, minmax(120px, .8fr));
    align-items: center;
  }
  .screen-actions { display: flex; justify-content: flex-end; }
  .screen-actions .btn { width: auto; min-width: 220px; }
}

@media (min-width: 1280px) {
  body.authenticated .grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }

  .goods-in-workspace {
    grid-template-columns: minmax(260px, .8fr) minmax(300px, .9fr) minmax(390px, 1.3fr);
  }
  .goods-in-current { grid-column: auto; }

  .order-entry-workspace {
    grid-template-columns: minmax(240px, .7fr) minmax(470px, 1.4fr) minmax(280px, .9fr);
  }
  .order-entry-workspace > .sticky-summary {
    grid-column: auto;
    position: sticky;
  }

  /* A wide laptop can keep record context and its toolbar on one lower row.
     Facts may wrap inside the left column, but they can no longer displace the
     identity or headline figure above, nor scatter individual actions. */
  body.authenticated .card.record-card {
    display: grid;
    grid-template-columns: minmax(320px, 1fr) max-content;
    column-gap: 12px;
  }
  body.authenticated .record-card > .card-head { grid-column: 1 / -1; }
  body.authenticated .record-card-meta { grid-column: 1; align-self: center; }
  body.authenticated .record-card-actions {
    grid-column: 2;
    align-self: start;
    padding-top: 6px;
    margin-top: 0;
    border-top: 0;
  }

  /* Plain order-card headings can share the line with their facts and action.
     Restrict this to `h3`: a split `.row.card-head` owns a headline amount and
     must stay in the stable record-card bands above. */
  body.authenticated .card.compact > h3.card-head { flex: 1 1 320px; }
}

@media (min-width: 1440px) {
  /* Six launch points fit comfortably in the landscape workspace once the
     persistent sidebar is accounted for, removing another full home-screen
     row without squeezing the bilingual labels. */
  body.authenticated .grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

.gate-toggle {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px 14px;
  align-items: center;
  margin: 0;
  padding: 12px 0;
  border-top: 1px solid var(--line);
}
.gate-toggle:first-of-type { border-top: 0; }
.gate-toggle input[type="checkbox"] {
  width: 28px;
  min-height: 28px;
  margin: 0;
  accent-color: var(--brand);
}
.gate-toggle .small { grid-column: 1; margin: 0; }
/* A password box and the button that reveals it. The button sits inside the
   box's right edge, and the box is padded so the text never runs under it —
   a password that disappears behind the very control meant to show it is a
   particular kind of insult. */
.reveal-wrap { position: relative; display: block; }
.reveal-wrap input { padding-right: calc(var(--tap) + 4px); }
.reveal {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  /* Full-height tap target: this is pressed by people who find small keys
     hard, which is the whole reason it exists. */
  width: var(--tap);
  height: calc(var(--tap) - 12px);
  padding: 0;
  font-size: 24px;
  line-height: 1;
  background: transparent;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
}
.reveal:active { background: var(--bg-soft); }

input:focus,
select:focus,
textarea:focus {
  border-color: var(--brand);
  outline: 3px solid var(--brand);
  outline-offset: 1px;
  box-shadow: 0 0 0 1px var(--field-bg);
}

button:focus-visible, a:focus-visible {
  outline: 3px solid var(--brand);
  outline-offset: 3px;
}

/* ── Status lines ────────────────────────────────────────────────────── */

.note {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  padding: 12px;
  margin: 8px 0;
  border-radius: var(--radius);
  border-left: 6px solid;
}

/* The mark is a label, not prose. As a plain flex child it shrank below its
   own content and broke mid-word: on a 320px phone "🟠 注意 CHECK" came out
   as "🟠 注" above "意 CHECK", which is exactly the line a founder is meant
   to read fastest. */
.note > :first-child { flex: 0 0 auto; }

/* And once the mark holds its width there may be too little left for the
   message, so rather than squeezing it into a column a few characters wide,
   it drops to its own full-width line. Side by side when there is room,
   stacked when there is not. */
.note > :last-child { flex: 1 1 11rem; }
.note.blocker { color: var(--danger); background: var(--danger-bg); border-color: var(--danger); }
.note.warning { color: var(--warn);   background: var(--warn-bg);   border-color: var(--warn); }
.note.info    { color: var(--ink);    background: var(--bg-soft);   border-color: var(--ink-soft); }
.note.ok      { color: var(--ok);     background: var(--ok-bg);     border-color: var(--ok); }
/* An author `display` beats the browser's own `[hidden] { display: none }`,
   so a hidden note kept rendering as an empty coloured bar. On the sign-in
   screen that was a red stripe under the password box announcing an error
   that had not happened. */
.note[hidden] { display: none; }

.pill {
  display: inline-block;
  padding: 3px 10px;
  font-size: 14px;
  font-weight: 600;
  border-radius: 999px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
}
.pill.bad { color: var(--danger); background: var(--danger-bg); border-color: var(--danger); }
.pill.good { color: var(--ok); background: var(--ok-bg); border-color: var(--ok); }

/* Section headings inside a long list — "Overdue", "Today", "Tomorrow".
   The rule above the heading is what stops one pile running into the next
   on a phone, where the whole list is one narrow column. */
.group-heading {
  margin-top: 24px;
  padding-top: 12px;
  border-top: 2px solid var(--line);
  align-items: center;
}
.group-heading h3 { margin: 0; }

/* ── Temporary delivery forms ────────────────────────────────────────── */

/* A count, not a sentence. "How many sheets" wants two digits, and the global
   field rule stretches it to the full column — 1084px on a laptop, which reads
   as a much bigger answer than it is.

   `input[type="number"].form-count` is deliberate. The global rule is
   `input:not([type="checkbox"]):not([type="radio"])`, whose two attribute
   selectors give it (0,2,1); a bare `.form-count` is (0,1,0) and loses
   silently. Matching that specificity and sitting later in the file is what
   makes this apply at all. */
input[type="number"].form-count {
  width: 8ch;
  min-width: 8ch;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* The number range is what the founder matches against the paper in their
   hand, so it is the identity of the card and sized to be found down a list. */
.temp-form-number { font-size: 20px; font-weight: 700; }

/* The next number to be printed: the one fact checked against the pad in the
   car before printing more, so it is set to be read at a glance. */
.next-number-row { align-items: baseline; }
.next-number { font-size: 22px; font-weight: 700; }

/* The print panel is a two-digit answer and one button; on a laptop it has no
   business spanning the full width the way a table of records does. */
@media (min-width: 1024px) {
  .temp-form-print { max-width: 560px; }
}

/* ── Toast ───────────────────────────────────────────────────────────── */

#toast {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: 50;
  padding: 16px;
  font-size: 18px;
  color: var(--toast-ink);
  background: var(--toast-bg);
  border-radius: var(--radius);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3);
}
#toast[hidden] { display: none; }
#toast.bad { color: var(--on-danger); background: var(--danger); }

.spinner { padding: 30px; text-align: center; color: var(--ink-soft); }

@media (prefers-reduced-motion: no-preference) {
  .list-item, .tile, .btn { transition: background 90ms linear; }
}

/* ── Charts ──────────────────────────────────────────────────────────────
   Hand-drawn inline SVG; see js/charts.js for why there is no library. The
   rule these all follow: the shape is a summary, never the only place a
   number appears, because a bar on a 320px screen cannot be read to the
   ringgit and nobody should have to try. */

.chart { margin: 6px 0 2px; }
.chart svg { display: block; width: 100%; }
.chart-scale {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin-top: 2px;
}

.ranked { display: flex; flex-direction: column; gap: 12px; }
.ranked-row { display: flex; flex-direction: column; gap: 4px; }
.ranked-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
/* The name gives way before the number does: a truncated customer name is
   still recognisable, a truncated amount is misinformation. */
.ranked-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 17px;
}
.ranked-value { flex: 0 0 auto; font-size: 17px; font-weight: 700; }
.ranked-track {
  height: 10px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 999px;
  overflow: hidden;
}
.ranked-fill { height: 100%; background: var(--brand); border-radius: 999px; }
.ranked-fill.bad { background: var(--danger); }
.ranked-sub { margin-top: 1px; }

.progress-wrap { margin: 10px 0 4px; display: flex; flex-direction: column; gap: 6px; }
.progress-track {
  height: 18px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 999px;
  overflow: hidden;
}
.progress-fill { height: 100%; background: var(--brand); border-radius: 999px; }

.big-money.bad { color: var(--danger); }

/* Inline danger text, for the auditor's screens where a count being non-zero
   is itself the finding — a failed sign-in tally, a refused request. Distinct
   from `.note.blocker`, which is a whole box and would drown a list. */
.small.danger { color: var(--danger); }

/* Long unbroken tokens must wrap rather than widen the page. API paths like
   `/api/customers/{id}/migration-card` have no spaces, and a browser will
   happily push the viewport out to fit one — which on a 320px screen means
   every other screen scrolls sideways too. `anywhere` rather than
   `break-word` because the latter still refuses to split a single long
   token when it is the only thing on the line. */
.list-item strong,
.list-item span,
.ranked-name,
.ranked-sub {
  overflow-wrap: anywhere;
}

/* Short explanatory lists on the auditor screens. */
ul.plain { margin: 6px 0 0; padding-left: 1.1rem; display: flex; flex-direction: column; gap: 8px; }
ul.plain li { line-height: 1.45; }

/* ── The call list ────────────────────────────────────────────────────────
   One person per block: who, why, and the two or three ways to reach them.
   A row rather than a `.list-item` because the whole thing is not one tap —
   the name opens the customer, the buttons do different things, and a nested
   tappable inside a tappable is how a thumb ends up on the wrong one. */
.call-row { padding: 12px 0; border-top: 1px solid var(--line); }
.call-row:first-of-type { border-top: 0; }
.call-name {
  display: block;
  width: 100%;
  padding: 0 0 2px;
  font-size: 19px;
  font-weight: 700;
  text-align: left;
  color: var(--ink);
  background: none;
  border: 0;
  cursor: pointer;
  overflow-wrap: anywhere;
}
/* One line, shared evenly. Left to wrap, three buttons became three rows and
   a single person occupied 250px. */
.call-actions { display: flex; gap: 6px; margin-top: 10px; }
/* Smaller than a primary action but still a comfortable target: these are
   pressed by people who find small keys hard, so height is held at 48px even
   though the width is shared. */
.btn.tight {
  flex: 1 1 0;
  min-width: 0;
  min-height: 48px;
  padding: 10px 6px;
  font-size: 15px;
  white-space: nowrap;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* A month label above the pair of figures it belongs to. Splitting billed and
   banked into two tappable rows made "2026-08 开出去 billed" too long for
   320px; naming the month once fixes that and reads better besides. */
.pad-top { margin-top: 10px; font-weight: 600; }

/* Quantity on a basket line: step it, or type it.
   Sized so the buttons clear the 60px tap target on either side of a box wide
   enough for four digits — correcting a mis-keyed quantity happens on a phone,
   in a shop, usually in a hurry. */
.qty-stepper {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
}
.qty-step {
  width: 64px;
  margin-top: 0;
  padding: 0;
  font-size: 26px;
  line-height: 1;
  flex: 0 0 auto;
}
.qty-step:disabled { opacity: 0.4; }
.qty-box {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--tap);
  padding: 8px 10px;
  font-size: 22px;
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: var(--field-bg);
  border: 2px solid var(--field-border);
  border-radius: var(--radius);
}
.qty-box,
.price-box { appearance: textfield; }
.qty-box::-webkit-inner-spin-button,
.qty-box::-webkit-outer-spin-button,
.price-box::-webkit-inner-spin-button,
.price-box::-webkit-outer-spin-button {
  margin: 0;
  appearance: none;
}

/* ══ Two Pack brand layer ══════════════════════════════════════════════
   Tokens live in brand.css. This is only what the brand *adds* — the
   framing devices that make a screen recognisably this company.

   Restraint is the point. The kit ships a hex lattice, scanlines, glass
   gradients, glows and 7–11 second ambient animations. On a screen that is
   mostly a list of money on a cheap Android phone, those cost paint time
   and buy texture nobody reads. Two devices carry the brand instead: the
   corner bracket and the fading orange rule.
   ═══════════════════════════════════════════════════════════════════════ */

/* The mark carries its own dark outline, so it needs no plate behind it. */
.icon-btn.brandmark {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 6px;
  background: none;
}
.icon-btn.brandmark img { height: 30px; width: auto; display: block; }
.icon-btn.brandmark:active { background: var(--on-brand-wash-active); }

/* The top bar becomes the deepest surface rather than a green band: on the
   brand's dark canvas the chrome should recede and the content should be
   the brightest thing on screen. */
#topbar { border-bottom: 1px solid var(--tp-line); }

#title {
  font-family: var(--tp-font-display);
  font-weight: 600;
  letter-spacing: .02em;
  text-align: left;
}

/* ── The signature framing device ────────────────────────────────────
   Two L-shapes at opposite corners instead of a heavy border. Applied to
   the one card on a screen that carries its headline figure, never to
   every card — the kit's own rule about the accent applies to framing too.
   Drawn with gradients on a pseudo-element so it costs no extra DOM. */
.card.bracketed {
  position: relative;
  border-color: var(--tp-line-strong);
}
.card.bracketed::before,
.card.bracketed::after {
  content: '';
  position: absolute;
  width: 14px;
  height: 14px;
  border: 2px solid var(--tp-flare);
  pointer-events: none;
}
.card.bracketed::before {
  top: -1px; left: -1px;
  border-right: 0; border-bottom: 0;
  border-top-left-radius: var(--radius);
}
.card.bracketed::after {
  bottom: -1px; right: -1px;
  border-left: 0; border-top: 0;
  border-bottom-right-radius: var(--radius);
}

/* ── The signature divider ───────────────────────────────────────────
   A label with an orange rule running out of it and fading to nothing. */
.rule {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 22px 0 10px;
}
.rule > span:first-child {
  font-family: var(--tp-font-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .16em;
  font-size: 15px;
  color: var(--tp-mute);
  white-space: nowrap;
}
.rule::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: linear-gradient(90deg, var(--tp-flare), transparent);
}

/* ── Controls ────────────────────────────────────────────────────────
   Orange is rationed. It marks the primary action on a screen and the
   active chip, and nothing else — the kit's rule is that if everything
   glows orange then nothing does, and in this app almost every row is
   tappable. Secondary buttons and list rows stay structural. */
.btn {
  font-family: var(--tp-font-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .12em;
  border-radius: 4px;
}
/* The Chinese half of a button must not be letterspaced or uppercased —
   `bilingual()` puts it in `.cn`, so undo it there. */
.btn .cn {
  text-transform: none;
  letter-spacing: normal;
  font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", system-ui, sans-serif;
}

.chip { border-radius: 4px; }
.chip[aria-pressed="true"] {
  background: var(--tp-flare);
  color: var(--tp-on-flare);
  border-color: var(--tp-flare);
}

/* Tiles: the mark of a card, with the emoji left alone. Hover lifts and
   glows cyan, per the kit — no bounce, no spin. */
.tile {
  border-width: 1px;
  border-color: var(--tp-line);
  transition: transform var(--tp-dur-fast) var(--tp-ease),
              box-shadow var(--tp-dur-fast) var(--tp-ease);
}
.tile:active {
  transform: translateY(1px);
  box-shadow: 0 0 18px rgba(46, 166, 255, .30);
}

/* Figures. The single strongest carrier of this brand in this app, and it
   needed no markup change — every number was already `.mono`. */
.big-money { color: var(--tp-flare); }
:root[data-theme="light"] .big-money,
:root:not([data-theme="dark"]) .big-money { color: var(--tp-flare-deep); }

/* Ambient motion is deliberately absent, but anything that does move must
   still respect the setting. */
@media (prefers-reduced-motion: reduce) {
  .tile { transition: none; }
}

/* Sign-in: the stacked mark centred, with room to be itself. */
.login-brand {
  flex-direction: column;
  gap: 10px;
  text-align: center;
}
.login-logo {
  width: 150px;
  height: auto;
  border-radius: 0;
}
.login-product { margin: 0; }

/* A money figure must never break across lines: "RM" over "68.00" stops
   reading as a number, which is the whole reason `.big-money` already
   forbids it. The label beside it wraps instead — a wrapped label is still
   a label. */
.row-tap-value .right,
.row .right.mono { white-space: nowrap; }

/* The goods on an order card. A row per line, SKU left, quantity right, so a
   founder scanning the Orders tile reads what was ordered instead of a count
   of how many kinds of thing it was. */
.goods {
  margin: 8px 0;
  padding: 8px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.goods-line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 3px 0;
}
.goods-sku { font-weight: 600; }
.goods-qty { white-space: nowrap; color: var(--tp-flare); }
:root[data-theme="light"] .goods-qty,
:root[data-theme="system"] .goods-qty { color: var(--tp-flare-deep); }
