/* DADCARE My Business — vendored stylesheet.
   No CDN and no build step: WhiteNoise compresses and fingerprints this file,
   so first paint needs one small CSS request and no framework download. */

/* ── Theme tokens ──────────────────────────────────────────────────────────
   LIGHT IS THE DEFAULT for every user. The device's dark-mode setting is
   deliberately NOT consulted: a new visitor starts light and only becomes dark
   by explicitly choosing it, which the server writes onto <html data-theme>.
   Every colour below is a semantic token, so a component is themed once here
   rather than restated per page. */

/* ── DADCARE design tokens ─────────────────────────────────────────────────
   One palette for every surface — Stock, Sales, Expenses, Employees, Reports,
   Products, Marketplace and future Mini Apps all read these variables, so the
   visual language stays consistent without any page defining its own colours.

   LIGHT IS THE DEFAULT. The device's dark-mode setting is deliberately not
   consulted: a visitor starts light and becomes dark only by choosing it.

   Colour never carries meaning on its own. Status is always accompanied by a
   word ("Low", "Out of stock", "Active"), navigation by a label, and the
   current tab by aria-current, so the interface still reads without colour. */

:root {
  /* Surfaces: clean near-white page, white cards, subtle borders. */
  --bg: #f5f6f8;
  --surface: #ffffff;
  --surface-2: #eef0f4;
  --border: #e4e7ec;

  /* Type: dark navy primary, muted grey secondary. */
  --text: #151a2d;
  --muted: #646d80;

  /* Primary accent: indigo/violet. Selected navigation, primary buttons, the
     Stock summary, active chips and report actions all use this. */
  --brand: #4f46e5;
  --brand-dark: #4338ca;
  --brand-soft: #e8e9fd;
  --on-brand: #ffffff;          /* text that sits on a filled brand surface */

  /* Semantic status. Green means positive only, red means error or
     destructive only, amber means caution only. */
  --ok: #047857;
  --ok-soft: #d9f3e6;
  --danger: #c11c1c;
  --danger-soft: #fde4e4;
  --warn: #a04a08;
  --warn-soft: #fdf0d5;

  /* Mini App identities. Visual identity only: these encode nothing about
     permission, availability or any other backend state. */
  --a-business: #0f766e;    --a-business-soft: #d9f2ee;
  --a-marketplace: #c2410c; --a-marketplace-soft: #fdeddc;
  --a-school: #4338ca;      --a-school-soft: #e4e6fb;
  --a-pharmacy: #be123c;    --a-pharmacy-soft: #fce7ef;
  --a-gym: #6d28d9;         --a-gym-soft: #ece4fb;
  --a-app: #475467;         --a-app-soft: #eceff3;

  /* My Business operation tiles: soft pastel container, saturated glyph. */
  --o-sale: #047857;        --o-sale-soft: #d9f3e6;
  --o-products: #4f46e5;    --o-products-soft: #e6e7fd;
  --o-stock: #b45309;       --o-stock-soft: #fdefd8;
  --o-alert: #b45309;       --o-alert-soft: #fdf0d5;
  --o-purchasing: #0f766e;  --o-purchasing-soft: #d9f2ee;
  --o-customers: #1d4ed8;   --o-customers-soft: #dde8fe;
  --o-suppliers: #6d28d9;   --o-suppliers-soft: #ece4fb;
  --o-expenses: #be123c;    --o-expenses-soft: #fde6ec;
  --o-categories: #475467;  --o-categories-soft: #eceff3;
  --o-reports: #0369a1;     --o-reports-soft: #dbeefc;
  --o-online: #c2410c;      --o-online-soft: #fdeddc;

  /* Stakeholder application review. The palette's existing blue (the same
     value --o-customers carries in light mode), given its own semantic name
     rather than borrowed from an operation tile: a review card is not a tile,
     and a tile's colour must stay free to change without moving this one.
     Used for one thing — the accent strip across the top of the review card —
     and it carries no meaning about status, permission or outcome. */
  --review-accent: #1d4ed8;
  --review-accent-soft: #dde8fe;

  --overlay: rgba(0, 0, 0, .62);
  /* The same ground with nothing behind it to see. A camera panel showing a
     live preview can be translucent; one reporting that there is no preview
     must not let the page read through the message. */
  --overlay-solid: #101114;
  --on-overlay: #ffffff;

  /* Receipt paper. Deliberately identical in both themes: a receipt is a
     printed object, and dark-grey paper reads as a web card rather than
     something torn off a till. Declared here so the palette stays in one
     place, not so it can be themed. */
  --paper: #ffffff;
  --paper-ink: #14161c;
  --paper-muted: #5b6270;
  --paper-rule: rgb(0 0 0 / .22);
  --paper-edge: rgb(0 0 0 / .08);
  --paper-alert: #8a1c1c;
  --paper-alert-soft: #fdecec;

  --radius: 12px;
  --shadow: 0 1px 2px rgba(16, 24, 40, .05), 0 1px 3px rgba(16, 24, 40, .08);
  --tap: 44px;
}

/* Dark mode keeps the same identity and the same meanings; only the surfaces
   and the lightness of each hue change so contrast survives. */
:root[data-theme="dark"] {
  --bg: #10131c;
  --surface: #171b28;
  --surface-2: #1f2432;
  --border: #2a3040;

  --text: #e8eaf2;
  --muted: #98a1b8;

  --brand: #8b8cf7;
  --brand-dark: #a9aafb;
  --brand-soft: #23264a;
  --on-brand: #10131c;          /* dark type on the lighter dark-mode brand */

  --ok: #34d399;
  --ok-soft: #10291f;
  --danger: #f87171;
  --danger-soft: #2e1518;
  --warn: #fbbf24;
  --warn-soft: #2c2010;

  --a-business: #2dd4bf;    --a-business-soft: #10302c;
  --a-marketplace: #fdb022; --a-marketplace-soft: #33240d;
  --a-school: #8098f9;      --a-school-soft: #1b1f3d;
  --a-pharmacy: #fd6f8e;    --a-pharmacy-soft: #3a1522;
  --a-gym: #b18cf5;         --a-gym-soft: #271a3d;
  --a-app: #98a2b3;         --a-app-soft: #242a32;

  --o-sale: #34d399;        --o-sale-soft: #10291f;
  --o-products: #a5b4fc;    --o-products-soft: #1e2143;
  --o-stock: #fbbf24;       --o-stock-soft: #2c2010;
  --o-alert: #fbbf24;       --o-alert-soft: #2c2010;
  --o-purchasing: #2dd4bf;  --o-purchasing-soft: #10302c;
  --o-customers: #7fabfb;   --o-customers-soft: #16223d;
  --o-suppliers: #b18cf5;   --o-suppliers-soft: #271a3d;
  --o-expenses: #fb7185;    --o-expenses-soft: #34141d;
  --o-categories: #98a2b3;  --o-categories-soft: #242a32;
  --o-reports: #56b6ea;     --o-reports-soft: #0d2733;
  --o-online: #fdb022;      --o-online-soft: #33240d;

  --review-accent: #7fabfb;
  --review-accent-soft: #16223d;

  /* Paper does not invert. Restated here so the two themes declare the same
     token names, and so the intent is explicit rather than an omission. */
  --paper: #ffffff;
  --paper-ink: #14161c;

  /* A camera panel is dark in either theme — it exists to get out of the way
     of a viewfinder. Restated for the same reason paper is: the two themes
     declare the same names, and the sameness is a decision. */
  --overlay: rgba(0, 0, 0, .62);
  --overlay-solid: #101114;
  --on-overlay: #ffffff;
  --paper-muted: #5b6270;
  --paper-rule: rgb(0 0 0 / .22);
  --paper-edge: rgb(0 0 0 / .08);
  --paper-alert: #8a1c1c;
  --paper-alert-soft: #fdecec;
}

* { box-sizing: border-box; }

/* `hidden` means hidden.
 *
 * The browser's own stylesheet says `[hidden] { display: none }`, but a
 * browser stylesheet loses to an author one whatever the specificity — so any
 * rule of ours that sets `display` on an element silently defeats it. That is
 * not a corner case here: `.btn` sets `display: inline-flex`, so every button
 * and label carrying the attribute stayed on screen, and `.mkslide-play` sets
 * `display: flex`, so the viewer's centre Play control was always showing even
 * while playback was working perfectly.
 *
 * The symptoms looked like separate bugs in separate features — a Play badge
 * over a playing video, "Open camera" and "Use your device camera" offered at
 * the same time when the code makes them mutually exclusive, a "Load more"
 * button on an exhausted catalogue. They were one missing line.
 *
 * `!important` is doing real work rather than papering over a specificity
 * fight: `[hidden]` and `.btn` have equal specificity, so without it the class
 * rule wins on order alone. An element that must stay laid out while invisible
 * wants `visibility` or `opacity`; `hidden` is for things that are not there. */
[hidden] { display: none !important; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
  padding-bottom: 76px; /* room for the mobile tab bar */
}

a { color: var(--brand); text-decoration: none; }

/* ── Layout ─────────────────────────────────────────────────────────────── */

.topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 16px;
  /* Installed app in landscape on a notched phone: keep clear of the notch.
     A browser without max()/env() keeps the 16px above. */
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.topbar h1 { font-size: 17px; margin: 0; font-weight: 650; }
.topbar .spacer { flex: 1; }

.biz {
  display: flex; align-items: center; gap: 8px;
  max-width: 55%;
}
.biz select {
  max-width: 100%;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  border-radius: 8px;
  padding: 7px 10px;
  font-size: 14px;
  min-height: 36px;
}

.wrap {
  padding: 16px; max-width: 1100px; margin: 0 auto;
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
}

/* ── Cards & grid ───────────────────────────────────────────────────────── */

.grid { display: grid; gap: 12px; }
.grid.stats { grid-template-columns: repeat(2, 1fr); }
@media (min-width: 720px) {
  .grid.stats { grid-template-columns: repeat(4, 1fr); }
  .grid.cols2 { grid-template-columns: repeat(2, 1fr); }
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  box-shadow: var(--shadow);
}
.card h2 { font-size: 14px; margin: 0 0 10px; font-weight: 650; }

.stat .label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
.stat .value { font-size: 24px; font-weight: 700; margin-top: 6px; line-height: 1.2; }
.stat .value.small { font-size: 18px; }

/* ── Lists ──────────────────────────────────────────────────────────────── */

.list { display: flex; flex-direction: column; gap: 8px; }
.row {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
  min-height: var(--tap);
}
.row .grow { flex: 1; min-width: 0; }
/* A row that is a link (Products, Receipts) is still a row: its name and
   amount are body text and its meta stays muted. Without this every word in
   it inherited the global link colour. Ordinary links are unchanged. */
a.row { color: var(--text); }
.row .name { font-weight: 600; }
.row .meta { color: var(--muted); font-size: 13px; margin-top: 2px; }
.row .amount { font-weight: 700; white-space: nowrap; }

.thumb {
  width: 44px; height: 44px; border-radius: 8px; object-fit: cover;
  background: var(--bg); border: 1px solid var(--border); flex: none;
}

/* ── Badges ─────────────────────────────────────────────────────────────── */

.badge {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
.badge.low  { background: var(--warn-soft); color: var(--warn); }
.badge.out  { background: var(--danger-soft); color: var(--danger); }
.badge.ok   { background: var(--ok-soft); color: var(--ok); }
.badge.info { background: var(--brand-soft); color: var(--brand-dark); }

/* ── Controls ───────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--tap); padding: 10px 16px;
  border-radius: 10px; border: 1px solid var(--brand);
  background: var(--brand); color: var(--on-brand);
  font-size: 15px; font-weight: 600; cursor: pointer;
}
.btn:active { background: var(--brand-dark); }
.btn.secondary { background: var(--surface); color: var(--text); border-color: var(--border); }
.btn.danger { background: var(--danger); border-color: var(--danger); }
.btn.block { width: 100%; }
.btn[disabled] { opacity: .55; cursor: not-allowed; }

.field { margin-bottom: 12px; }
.field label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; }
/* Text-entry controls fill the row and carry a 44px tap target. A radio or a
   checkbox is not one of those: it is a small mark beside its words, and
   stretching one to the full width of the form turned "Retail" and
   "Wholesale" into two enormous circles. */
.field input:not([type="radio"]):not([type="checkbox"]),
.field select, .field textarea {
  width: 100%; min-height: var(--tap);
  padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); color: var(--text);
  font-size: 16px; /* >=16px stops iOS zoom-on-focus */
}
.field textarea { min-height: 88px; }
.field-row { display: grid; gap: 12px; grid-template-columns: 1fr 1fr; }
/* A grid item will not shrink below its content (two native date inputs
   overflowed a 360px screen); let it, and stack the pair where two no longer
   fit legibly. */
.field-row > * { min-width: 0; }
@media (max-width: 380px) { .field-row { grid-template-columns: 1fr; } }

/* A group of fields, or of choices, under one heading.
   The browser's own fieldset chrome is a bordered box with the legend cut
   into its top edge — treatment that belongs to no other control on these
   screens. The border goes, and the legend is styled as the field label it
   reads as, so a group sits among ordinary fields without announcing itself
   as a different kind of thing. */
fieldset.field { border: 0; padding: 0; margin-inline: 0; min-width: 0; }
fieldset.field > legend {
  display: block; float: none; padding: 0;
  font-size: 13px; color: var(--muted); margin-bottom: 6px;
}
/* One option to a row: the mark beside its words, the whole row tappable.
   The words are body text rather than the muted 13px a field label uses —
   this is what is being chosen, not a caption above it.
   Named `optionrow`, not `choice`: `.choice` is already the bordered card
   row used by Add business, Staff and onboarding, and borrowing it here
   wrapped each radio in a card it was never meant to have. */
.field .optionrow {
  display: flex; align-items: center; gap: 10px;
  min-height: var(--tap); margin-bottom: 0;
  font-size: 16px; color: var(--text); cursor: pointer;
}
.field .optionrow input[type="radio"],
.field .optionrow input[type="checkbox"] {
  flex: none; width: 20px; height: 20px; margin: 0;
  accent-color: var(--brand);
}

.search { margin-bottom: 12px; }

/* ── Feedback ───────────────────────────────────────────────────────────── */

.alert { padding: 10px 12px; border-radius: 10px; font-size: 14px; margin-bottom: 12px; }
.alert.error { background: var(--danger-soft); color: var(--danger); }
.alert.ok { background: var(--ok-soft); color: var(--ok); }
.empty { text-align: center; color: var(--muted); padding: 28px 12px; }
.skeleton {
  height: 62px; border-radius: 10px; margin-bottom: 8px;
  background: linear-gradient(90deg, var(--border) 25%, var(--bg) 37%, var(--border) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.2s ease-in-out infinite;
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) { .skeleton { animation: none; } }

/* ── Bottom navigation (mobile-first) ───────────────────────────────────── */

.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  display: flex; justify-content: space-around;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 6px 4px calc(6px + env(safe-area-inset-bottom));
  padding-left: max(4px, env(safe-area-inset-left));
  padding-right: max(4px, env(safe-area-inset-right));
}
.tabbar a {
  flex: 1; text-align: center; color: var(--muted);
  font-size: 11px; padding: 6px 2px; border-radius: 8px;
  min-height: var(--tap); display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 3px;
}
.tabbar a.active { color: var(--brand); background: var(--brand-soft); font-weight: 650; }

.more-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
@media (min-width: 720px) { .more-grid { grid-template-columns: repeat(3, 1fr); } }
.more-grid a {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; padding: 16px; text-align: center;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); color: var(--text); font-weight: 600;
  min-height: var(--tap);
}

/* ── POS ────────────────────────────────────────────────────────────────── */

.cart-total {
  position: sticky; bottom: 76px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 12px; box-shadow: var(--shadow);
}
/* Inside a sheet there is no tab bar to clear: the sheet covers it and scrolls
   by itself. With the page's 76px offset the panel pinned above its own place
   at the end of the sheet, so the last cart line stayed under it however far
   the sheet was scrolled. */
.sheet .cart-total { bottom: 0; }
.qty { display: flex; align-items: center; gap: 8px; }
.qty button {
  width: 36px; height: 36px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--bg);
  color: var(--text); font-size: 18px; cursor: pointer;
}

table.report { width: 100%; border-collapse: collapse; font-size: 14px; }
table.report th, table.report td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--border); }
table.report td.num, table.report th.num { text-align: right; }
.scroll-x { overflow-x: auto; }


/* Icons inside My Business menus and the tab bar (see templates/ui/_icon_sprite.html). */
.tabbar a .ic { width: 22px; height: 22px; }
.more-grid a .ic { width: 26px; height: 26px; color: var(--brand); }


/* ── Accent tiles ──────────────────────────────────────────────────────────
   The icon chip that fronts an accented row. It outlived the Dashboard's
   quick-action grid — the Products low-stock summary and the More menu both
   use it, and the Dashboard's attention rows use it now — so the chip and the
   accents below it stay while the grid that introduced them has gone.
   Colour is identity only; what a user may do is decided server-side. */
.op-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; border-radius: 16px; flex: none;
  background: var(--a-app-soft); color: var(--a-app);
}
.op-ico .ic { width: 26px; height: 26px; }

.o-sale       { background: var(--o-sale-soft);       color: var(--o-sale); }
.o-products   { background: var(--o-products-soft);   color: var(--o-products); }
.o-stock      { background: var(--o-stock-soft);      color: var(--o-stock); }
.o-alert      { background: var(--o-alert-soft);      color: var(--o-alert); }
.o-purchasing { background: var(--o-purchasing-soft); color: var(--o-purchasing); }
.o-customers  { background: var(--o-customers-soft);  color: var(--o-customers); }
.o-suppliers  { background: var(--o-suppliers-soft);  color: var(--o-suppliers); }
.o-expenses   { background: var(--o-expenses-soft);   color: var(--o-expenses); }
.o-categories { background: var(--o-categories-soft); color: var(--o-categories); }
.o-reports    { background: var(--o-reports-soft);    color: var(--o-reports); }
.o-online     { background: var(--o-online-soft);     color: var(--o-online); }
/* Two states rather than modules, from the existing status tokens: something
   that needs a person to review it, and something that blocks the work. */
.o-warn       { background: var(--warn-soft);         color: var(--warn); }
.o-danger     { background: var(--danger-soft);       color: var(--danger); }

/* ── Receipt sheet ─────────────────────────────────────────────────────────
   A narrow, thermal-style sheet that prints alone: the print rules below drop
   navigation, buttons and every other piece of application chrome. */
.receipt-sheet {
  /* Paper is white in both themes: a receipt is a printed object, and a
     dark-grey one reads as a web card rather than something torn off a till. */
  background: var(--paper); color: var(--paper-ink);
  border: 1px solid var(--paper-edge);
  border-radius: 4px;
  box-shadow: var(--shadow), 0 8px 24px rgb(0 0 0 / .10);
  padding: 20px 18px; max-width: 340px; margin: 0 auto;
  font-variant-numeric: tabular-nums;
  font-size: 13px; line-height: 1.45;
  position: relative;
}
/* A serrated edge top and bottom, the way till roll tears. */
.receipt-sheet::before, .receipt-sheet::after {
  content: ""; position: absolute; left: 0; right: 0; height: 6px;
  background: repeating-linear-gradient(135deg, var(--paper) 0 6px,
              transparent 6px 12px);
}
.receipt-sheet::before { top: -3px; }
.receipt-sheet::after { bottom: -3px; }
.rt-head { text-align: center; padding-bottom: 12px;
  border-bottom: 1px dashed var(--paper-rule); }
.rt-logo { width: 64px; height: 64px; object-fit: contain; margin: 0 auto 8px; display: block; }
.rt-name { font-size: 16px; font-weight: 700; letter-spacing: .01em;
  text-transform: uppercase; }
.rt-sub { font-size: 11.5px; color: var(--paper-muted); margin-top: 2px; }
/* Says what the sheet is. A receipt that travels as a picture arrives with no
   page around it to explain itself. */
.rt-cap { margin-top: 10px; font-size: 10px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase; color: var(--paper-muted); }
/* A sale that was voided must not read as a valid receipt once it is shared.
   Bordered as well as filled, so it survives a printer that drops backgrounds. */
.rt-band { margin: 10px 0 0; padding: 8px 10px; text-align: center;
  font-weight: 700; font-size: 13px; color: var(--paper-alert);
  background: var(--paper-alert-soft); border: 1px solid var(--paper-alert);
  border-radius: 3px;
  -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.rt-meta { padding: 10px 0; border-bottom: 1px dashed var(--paper-rule); }
.rt-line { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; padding: 2px 0; }
.rt-line .l { color: var(--paper-muted); }
.rt-line.strong { font-size: 17px; font-weight: 700; padding-top: 8px;
  margin-top: 6px; border-top: 1px solid var(--paper-rule); }
.rt-line.strong .l { color: var(--paper-ink); letter-spacing: .04em; }
.rt-items { padding: 10px 0; border-bottom: 1px dashed var(--paper-rule); }
.rt-item { display: grid; grid-template-columns: 1fr auto; gap: 0 12px;
  padding: 5px 0; break-inside: avoid; page-break-inside: avoid; }
/* Placed explicitly: description on the left, amount on the right. */
.rt-item .n { grid-column: 1; grid-row: 1; font-weight: 600; font-size: 14px; }
.rt-item .q { grid-column: 1; grid-row: 2; font-size: 11.5px;
  color: var(--paper-muted); }
.rt-item .a { grid-column: 2; grid-row: 1 / span 2; align-self: center; font-weight: 600; }
.rt-totals { padding: 10px 0 2px; }
.rt-tender { padding: 8px 0 2px; }
.rt-foot { text-align: center; font-size: 11.5px; color: var(--paper-muted);
  padding-top: 12px; border-top: 1px dashed var(--paper-rule); }
/* Attribution, under the shop's own words rather than instead of them. Small
   and quiet on purpose: it must not compete with the name or the total. */
.rt-powered { text-align: center; font-size: 9.5px; letter-spacing: .06em;
  color: var(--paper-muted); opacity: .8; padding-top: 6px; }

.receipt-actions { display: flex; gap: 8px; flex-wrap: wrap; margin: 16px auto 0;
  max-width: 340px; }
.receipt-actions .btn { flex: 1 1 46%; min-height: 42px; font-size: 14px; }

@media print {
  /* Only the receipt prints: no navigation, no buttons, no chrome. */
  .topbar, .tabbar, .globalnav, .receipt-actions, .no-print,
  #flash, .biz, header, nav { display: none !important; }
  body { background: #fff; color: #000; padding: 0; margin: 0; }
  .wrap { padding: 0; max-width: none; }
  .receipt-sheet {
    border: 0; border-radius: 0; box-shadow: none;
    max-width: 80mm; width: 80mm; padding: 0; margin: 0;
    background: #fff; color: #000;
  }
  .receipt-sheet::before, .receipt-sheet::after { display: none; }
  .rt-sub, .rt-line .l, .rt-item .q, .rt-foot, .rt-cap, .rt-powered { color: #333; }
  .rt-head, .rt-meta, .rt-totals, .rt-tender, .rt-band, .rt-foot, .rt-powered {
    break-inside: avoid; page-break-inside: avoid;
  }
  .rt-items { orphans: 2; widows: 2; }
  @page { margin: 6mm; }
}

/* ── Stock overview ────────────────────────────────────────────────────────
   Quantity is operational; value is commercially sensitive and the server
   decides whether it is sent at all. */
.stock-value {
  display: flex; align-items: center; gap: 12px; justify-content: space-between;
  margin-bottom: 12px;
}
.sv-label { font-size: 12px; color: var(--muted); text-transform: uppercase;
  letter-spacing: .05em; }
.sv-amount { font-size: 24px; font-weight: 700; line-height: 1.2; margin-top: 2px;
  font-variant-numeric: tabular-nums; }
.stock-value .btn { min-height: 38px; padding: 8px 12px; font-size: 13px; flex: none; }

.scroll-x.chips { overflow-x: auto; flex-wrap: nowrap; padding-bottom: 4px; }
.scroll-x.chips .chip { flex: none; }

/* Bottom sheet for Generate report */
.sheet {
  position: fixed; inset: 0; z-index: 60; display: flex; align-items: flex-end;
  background: rgba(16, 24, 40, .45);
}
.sheet[hidden] { display: none; }
.sheet-body {
  background: var(--surface); width: 100%; overflow-y: auto;
  /* dvh follows the browser chrome and on-screen keyboard; vh is the fallback. */
  max-height: 82vh; max-height: 82dvh;
  border-radius: 18px 18px 0 0; padding: 18px 16px calc(18px + env(safe-area-inset-bottom));
  box-shadow: var(--shadow);
}
.sheet-body h2 { margin: 0 0 12px; font-size: 17px; }
.sheet-body .row { width: 100%; cursor: pointer; font: inherit; color: inherit;
  background: transparent; border: 0; border-bottom: 1px solid var(--border); }
@media (min-width: 640px) {
  .sheet { align-items: center; justify-content: center; }
  .sheet-body { max-width: 420px; border-radius: 18px; }
}

/* Tablet and desktop widen the product grid; the data and rules are identical. */
@media (min-width: 900px) {
  .stock-value { max-width: 420px; }
}

/* ── Business identity ─────────────────────────────────────────────────────
   A cover sits above the identity bar so the page previews what a shopper sees
   on the storefront. Both are optional; neither leaves a gap when unset. */
.id-cover {
  height: 118px; margin: -16px -14px 0; overflow: hidden;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.id-cover:empty { height: 0; border: 0; }
.id-bar { display: flex; align-items: center; gap: 12px; padding-top: 14px; }
.id-media { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
.btn.sm { min-height: 38px; padding: 8px 12px; font-size: 14px; cursor: pointer; }

/* ── Add Business chooser ──────────────────────────────────────────────────
   Two routes into an existing flow: found a business, or accept an invitation
   to one. Neither changes what the destination does. */
.choice {
  display: flex; align-items: center; gap: 12px; margin-top: 10px; padding: 14px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); color: var(--text);
}
.choice .grow { display: flex; flex-direction: column; gap: 2px; }
.choice .name { font-weight: 600; }
.choice .meta { color: var(--muted); font-size: 13px; }

/* ── Product photos ────────────────────────────────────────────────────────
   Catalogue presentation for the product as it is now. A receipt already
   written is never rebuilt from these. */
.media-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
@media (min-width: 640px) { .media-grid { grid-template-columns: repeat(4, 1fr); } }
.media-cell {
  position: relative; aspect-ratio: 1; border-radius: 12px; overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--border);
}
.media-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
.media-x {
  position: absolute; top: 4px; right: 4px; width: 28px; height: 28px;
  border: 0; border-radius: 999px; cursor: pointer; line-height: 1;
  background: var(--overlay); color: var(--on-overlay); font-size: 18px;
}

/* ── Invitation sharing ────────────────────────────────────────────────────
   The code is issued and revocable server-side. Sharing hands a message to
   another app; DADCARE never holds anyone's account there. */
.code-box {
  margin-top: 10px; padding: 14px; text-align: center;
  background: var(--brand-soft); border-radius: var(--radius);
}
.code-box .code {
  font-size: 22px; font-weight: 700; letter-spacing: 2px;
  color: var(--brand-dark); font-variant-numeric: tabular-nums;
  user-select: all; word-break: break-all;
}
.share-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.share-row .btn { flex: 1 1 130px; min-height: 40px; font-size: 14px; }

/* A .choice rendered as a button must read like the anchor version: full
   width, left aligned, and dimmed rather than hidden when unavailable. */
button.choice {
  width: 100%; text-align: left; font: inherit; cursor: pointer;
}
button.choice:disabled { opacity: .55; cursor: default; }
.invite-lead { font-size: 16px; line-height: 1.5; margin: 0 0 12px; }

/* ── Quantity entry ────────────────────────────────────────────────────────
   Typing a quantity beats tapping a stepper twenty times. The steppers stay
   for small nudges; the field is the primary control. */
.qty input {
  width: 64px; height: 36px; text-align: center;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); color: var(--text);
  font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums;
  -moz-appearance: textfield;
}
.qty input::-webkit-outer-spin-button,
.qty input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Stock adjustment moves stock by an amount; a physical count sets a total.
   The two are different operations and this screen is the first one. */
.adjust-row {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 12px 14px; margin-bottom: 10px;
}
.adjust-head .name { font-weight: 600; }
.adjust-head .meta { color: var(--muted); font-size: 13px; margin-top: 2px; }
.adjust-entry { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
.adjust-entry input {
  flex: 1 1 80px; min-width: 0; height: 42px; text-align: center;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg); color: var(--text);
  font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums;
  -moz-appearance: textfield;
}
.adjust-entry input::-webkit-outer-spin-button,
.adjust-entry input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.adjust-entry .btn { flex: 0 0 auto; min-height: 42px; padding: 8px 14px; font-size: 14px; }
.adjust-preview { color: var(--muted); font-size: 13px; margin-top: 8px; }

/* ── Grouped settings menu ─────────────────────────────────────────────────
   A settings list rather than an icon dashboard: one row per action, icon
   left, label, chevron right. Grouping and visibility both come from the
   declarative index in operations.py — the template renders what it is given
   and decides nothing about permissions. */
.menu-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
}
.menu-row {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 14px; min-height: var(--tap);
  color: var(--text); border-bottom: 1px solid var(--border);
}
.menu-row:last-child { border-bottom: 0; }
@media (hover: hover) { .menu-row:hover { background: var(--surface-2); } }
.menu-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border-radius: 10px; flex: none;
}
/* The neutral default only. :where() gives it no specificity, so an item's
   own identity class (.o-products, .o-customers, ...) always wins whatever
   the order of the two rules. Declared here, after the .o-* rules, as a plain
   .menu-ico it overrode every one of them and the whole More menu was grey. */
:where(.menu-ico) { background: var(--a-app-soft); color: var(--a-app); }
.menu-ico .ic { width: 20px; height: 20px; }
.menu-label { flex: 1; font-weight: 500; font-size: 15px; }
.menu-chevron { width: 18px; height: 18px; flex: none; color: var(--muted); }

/* ── Access control (Employee management) ──────────────────────────────────
   A permission is a decision, so it is drawn as a decision: a labelled switch
   with an unambiguous on and off, sized for a thumb. Colour never carries the
   state alone — the control is a real checkbox underneath, so assistive
   technology reads "on"/"off" without seeing the track at all. */
.perm-row {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 14px; border-bottom: 1px solid var(--border);
  min-height: var(--tap);
}
.perm-row:last-child { border-bottom: 0; }
.perm-text { flex: 1; min-width: 0; }
.perm-text .name { font-weight: 500; font-size: 15px; }
.perm-text .meta { color: var(--muted); font-size: 13px; margin-top: 2px; }
.perm-text .src { color: var(--muted); font-size: 12px; margin-top: 3px; }

.switch { position: relative; flex: none; width: 50px; height: 30px; }
.switch input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: pointer;
}
.switch .track {
  display: block; width: 100%; height: 100%; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border);
  transition: background .15s ease, border-color .15s ease;
  pointer-events: none;
}
.switch .track::after {
  content: ""; position: absolute; top: 4px; left: 4px;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--border);
  transition: transform .15s ease;
}
.switch input:checked + .track {
  background: var(--brand); border-color: var(--brand);
}
.switch input:checked + .track::after { transform: translateX(20px); }
.switch input:focus-visible + .track { outline: 2px solid var(--brand); outline-offset: 2px; }
.switch input:disabled { cursor: default; }
.switch input:disabled + .track { opacity: .5; }

.emp-head { display: flex; align-items: center; gap: 12px; }
.emp-head .grow { flex: 1; min-width: 0; }
.emp-filters { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0 4px; }
.emp-count { color: var(--muted); font-size: 14px; margin-top: 2px; }
.danger-zone { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border); }


/* ── Reports ───────────────────────────────────────────────────────────────
   Scoped to the Reports page with an `rp-` prefix. Nothing shared is
   redefined: `.card`, `.stat`, `.grid.stats`, `.chip`, `.notice`, `.alert`,
   `.scroll-x` and `table.report` are used exactly as every other screen uses
   them, because a page-level override is how a design system stops being one.

   It sits ABOVE the Dashboard section below, deliberately. That section's own
   structural test reads "everything from its heading to the end of the file"
   as the Dashboard's rules, so a block appended at the end of this file is
   read as Dashboard CSS and fails a contract it was never part of. */

/* The page's real heading, plus the two facts every figure below depends on:
   whose shop this is, and which days are being described. */
.rp-head { margin: 0 0 12px; }
.rp-title { font-size: 20px; font-weight: 700; margin: 0; line-height: 1.25; }
.rp-biz { margin: 2px 0 0; color: var(--muted); font-size: 13px; }
.rp-period { margin: 8px 0 0; display: flex; flex-wrap: wrap; gap: 4px 8px;
             align-items: baseline; }
.rp-period-label {
  font-size: 11px; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em;
}
.rp-period-dates { font-weight: 650; font-size: 14px; }

/* Period chips. `.chip` is 40px tall, which is under the 44px this shell
   holds touch targets to; raised here for these four controls only, exactly
   as the product form raises `.btn.sm` for its own screen. The row scrolls
   sideways rather than wrapping, so four choices stay one row at 360px and
   the page itself never scrolls horizontally. */
.rp-periods {
  display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none;
  margin: 0 -16px; padding: 2px 16px 6px;
  -webkit-overflow-scrolling: touch; scroll-snap-type: x proximity;
}
.rp-periods::-webkit-scrollbar { display: none; }
/* `.chip` in the shell sets `min-height: 40px`, at equal specificity and
   later in the cascade, so the height is claimed here through the row that
   owns these four controls. Nothing outside `.rp-periods` is affected. */
.rp-periods .rp-chip {
  flex: none; white-space: nowrap; scroll-snap-align: start;
  min-height: var(--tap);
}
.rp-periods .rp-chip:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}

.rp-custom { margin-top: 12px; }
.rp-figs { margin-top: 12px; }

/* The figure grid is a definition list, so each amount is programmatically
   tied to the words that name it. The browser's own <dl>/<dd> margins are
   removed; `.label` and `.value` keep doing the styling they already do. */
.rp-grid, .rp-grid dt, .rp-grid dd { margin: 0; }
.rp-sub { margin-top: 6px; }
/* A sentence standing where an amount would be. It is not an amount, so it is
   not set at amount size. Qualified by the grid because `.stat .value` is the
   more specific selector and would otherwise keep the figure type scale for a
   sentence three times as long as any amount. */
.rp-grid .value.rp-unknown {
  font-size: 14px; font-weight: 600; line-height: 1.35;
}

.rp-empty { padding: 16px 12px; }

/* Failure. Distinct from the provisional `.notice` above it: one is a broken
   request, the other is a shop with cost prices still to fill in. */
.rp-fail { margin-top: 12px; }
.rp-fail-msg { margin: 0 0 8px; }
.rp-fail .btn { width: 100%; }

/* The one review action. A link styled as a button, at the 44px target size
   the period chips already meet, full width so a long Kiswahili label wraps
   inside it rather than pushing the page sideways. */
.rp-action { margin: 12px 0 0; }
.rp-action .btn { width: 100%; min-height: 44px; text-align: center;
  white-space: normal; }

.rp-support { margin-top: 16px; }
.rp-support .stat .value { margin-top: 0; }


/* ── Product labels ────────────────────────────────────────────────────────
   Dadcare barcode labels (templates/shop/product_labels.html). Thermal: one
   50 × 25 mm label per page. A4: a 3 × 8 grid. The page size itself is set by
   the template, per paper. Bars are SVG, so they stay sharp at any printer
   resolution. */
.labels { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.labels .label {
  background: #fff; color: #000; border: 1px dashed var(--line, #ccc);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  overflow: hidden; text-align: center; padding: 1mm; box-sizing: border-box;
}
.labels.paper-thermal .label { width: 50mm; height: 25mm; }
.labels.paper-a4 .label { width: 64mm; height: 33.9mm; }
.labels .label-bars svg { display: block; max-width: 100%; height: auto; }
.labels .label-code { font: 600 9pt/1.1 ui-monospace, monospace; letter-spacing: .04em; }
.labels .label-name, .labels .label-price {
  font-size: 8pt; line-height: 1.1; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
@media print {
  .labels { gap: 0; margin: 0; }
  .labels .label { border: 0; break-inside: avoid; page-break-inside: avoid; }
  .labels.paper-thermal .label { break-after: page; page-break-after: always; }
  .labels.paper-thermal .label:last-child { break-after: auto; page-break-after: auto; }
}

/* ── My Business dashboard ─────────────────────────────────────────────────
   The workspace landing surface, in one fixed order: context, today, what
   needs attention, one next action, products sold today. Every rule here is
   structural — spacing, stacking and tap area. The colour system, the type
   scale and the icon set are the shared ones above and are not redefined. */

.dash-sec { margin-top: 16px; }
.dash-head {
  font-size: 14px; font-weight: 650; margin: 0 0 10px;
}

/* Business context. The block is always rendered, so a business that has
   uploaded neither a cover nor a logo still says whose figures these are.
   `.bizbanner` and its cover/scrim rules live in shell.css; these three add
   the date line and stop the browser's own paragraph margins from opening a
   gap inside the band. */
.bizbanner-text { min-width: 0; flex: 1; }
.bizbanner-name, .bizbanner-date { margin: 0; }
.bizbanner-date { font-size: 13px; opacity: .85; margin-top: 2px; }

/* Needs attention.

   A list, because it is one — a screen reader announces how many things are
   waiting before reading the first. Each row states what happened, why it
   matters and the one place that resolves it, and the state is carried by
   those words: the accent chip is decorative and hidden from assistive
   technology, so the rows read correctly in monochrome and aloud. */
.attn { list-style: none; margin: 0; padding: 0; display: flex;
        flex-direction: column; gap: 8px; }
.attn-link {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 12px;
  min-height: var(--tap); color: var(--text);
}
.attn-row.primary .attn-link { border-color: var(--brand); }
@media (hover: hover) { .attn-link:hover { background: var(--surface-2); } }
.attn-link:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.attn-ico { width: 40px; height: 40px; border-radius: 12px; }
.attn-ico .ic { width: 20px; height: 20px; }
.attn-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.attn-title { font-weight: 600; }
.attn-why { color: var(--muted); font-size: 13px; }
.attn-act {
  flex: none; font-size: 13px; font-weight: 600; color: var(--brand);
  display: inline-flex; align-items: center; min-height: var(--tap);
}
.attn-row.primary .attn-act { color: var(--brand-dark); }

/* The rotating box. Every slide sits in the same grid cell, so the box is as
   tall as its tallest slide in the reader's language and the page never jumps
   as it turns. The slides not on show are hidden from sight, from the tab
   order and from assistive technology, never removed. */
.attn.rot { display: grid; gap: 0; }
.attn.rot > .attn-row { grid-area: 1 / 1; min-width: 0;
                        transition: opacity 200ms ease; }
.attn.rot > .attn-row.is-off { visibility: hidden; opacity: 0; }
/* A notification slide is the Inbox's read form; its button looks exactly
   like the other slides' links. */
.attn-form { margin: 0; }
button.attn-link { width: 100%; font: inherit; text-align: left; cursor: pointer; }
.attn-ctl { display: flex; align-items: center; justify-content: center;
            gap: 4px; margin-top: 6px; flex-wrap: wrap; min-width: 0; }
.attn-btn {
  min-height: var(--tap); min-width: var(--tap); padding: 0 10px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 1px solid var(--border); border-radius: 10px;
  color: var(--text); font: inherit; font-size: 13px; cursor: pointer;
}
.attn-btn .ic { width: 18px; height: 18px; }
.attn-next .ic { transform: scaleX(-1); }
.attn-btn:focus-visible, .attn-dot:focus-visible, .attn-all summary:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px; }
.attn-dots { display: inline-flex; flex-wrap: wrap; justify-content: center; min-width: 0; }
.attn-dot {
  width: var(--tap); height: var(--tap); padding: 0; border: 0; background: none;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.attn-dot::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%;
  border: 1px solid var(--muted); background: transparent;
}
.attn-dot.is-on::before { background: var(--brand); border-color: var(--brand); }
.attn-pos { color: var(--muted); font-size: 13px; padding: 0 4px; }
.attn-summary { margin: 0 0 6px; font-weight: 600; }
.attn-all { margin-top: 8px; }
.attn-all summary { min-height: var(--tap); display: flex; align-items: center;
                    cursor: pointer; color: var(--brand); font-weight: 600; }
.attn-all .attn { margin-top: 6px; }
.attn-more-links { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 6px 0 0; }
.attn-more-links a { min-height: var(--tap); display: inline-flex; align-items: center; }
@media (prefers-reduced-motion: reduce) {
  .attn.rot > .attn-row { transition: none; }
}

/* Products sold today. The rows are the shared `.list`/`.row` component and
   are not restyled; this one rule stops a product name with no spaces in it
   from pushing the line total off a 320px screen. The shared `.row .name` is
   deliberately not widened — it is on the products, receipts and till screens
   too, and this section was not given those. */
#soldToday .row .name { overflow-wrap: anywhere; }

/* A section that could not answer, rather than one with nothing in it. It
   reads as information, not as an error the person caused. */
.notice {
  color: var(--muted); font-size: 13px; margin: 10px 0 0;
  padding: 10px 12px; border-radius: 10px; background: var(--surface-2);
}

/* Narrow phones: the action label drops under the wording rather than
   squeezing the count onto two lines beside it. */
@media (max-width: 400px) {
  .attn-link { align-items: flex-start; flex-wrap: wrap; }
  .attn-act { width: 100%; padding-left: 52px; }
}

/* ── Touch targets ─────────────────────────────────────────────────────────
   On a touch screen every action meets the 44px tap target. Some page scripts
   size small row buttons inline (34–38px: cart Edit/Remove, expense Void,
   staff Add/Revoke); !important lifts only their height, only on coarse
   pointers. A mouse keeps today's compact sizes. */
@media (pointer: coarse) {
  .btn { min-height: var(--tap) !important; }
  .biz select { min-height: var(--tap); }
  .qty button { width: var(--tap); height: var(--tap); }
  /* The photo remove mark stays small over its thumbnail; its tap area grows. */
  .media-x::after { content: ""; position: absolute; inset: -8px; }
}

/* ── Keyboard focus ────────────────────────────────────────────────────────
   One visible ring for every interactive element, in the brand token so it
   follows the theme. :where() keeps it at zero specificity, so a component
   with its own focus style (switches, form summary, report chips) keeps it. */
:where(.btn, a.row, button.row, button.card, .chip, .tabbar a, .menu-row,
       .more-grid a, .choice, .attn-link, .workbar-act, .mother-return):focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}
:where(.field input, .field select, .field textarea, .biz select):focus-visible {
  outline: 2px solid var(--brand); outline-offset: 1px;
}

/* ── Colour by function ────────────────────────────────────────────────────
   Colour on the boxes that already exist, by what each one is for or what
   state it reports. Nothing here changes a layout, a size, a gap, a font, an
   icon, a word or a behaviour: every rule sets only background-color,
   border-color or color, and only from the tokens above, so both themes and
   the tokens' own contrast pairs come with it. No rule touches display,
   visibility or opacity, so hidden and inactive elements — the slides the
   carousel is not showing, anything with `hidden` — stay exactly as hidden,
   inert and out of the tab order as they were.

   Secondary text inside a tinted box takes the box's own accent rather than
   --muted: the grey drops below 4.5:1 on the light tints, the accents do not.

   The boxes are found by what they already contain (`:has(#sToday)`), so no
   markup changes. A browser without :has() support simply keeps the neutral
   look these rules sit on top of — nothing breaks and nothing moves. */

/* Today (Dashboard) and the period figures (Reports). */
.card.stat:has(#sToday),
.card.stat:has(#figSales) { background-color: var(--o-sale-soft); }
.card.stat:has(#sToday) .label,
.card.stat:has(#figSales) .label,
.card.stat:has(#figSales) .hint { color: var(--o-sale); }

.card.stat:has(#sGross),
.card.stat:has(#figGross) { background-color: var(--o-reports-soft); }
.card.stat:has(#sGross) .label,
.card.stat:has(#sGross) .hint,
.card.stat:has(#figGross) .label { color: var(--o-reports); }

.card.stat:has(#sExp),
.card.stat:has(#figExpenses) { background-color: var(--o-expenses-soft); }
.card.stat:has(#sExp) .label,
.card.stat:has(#figExpenses) .label { color: var(--o-expenses); }

.card.stat:has(#figNet) { background-color: var(--o-products-soft); }
.card.stat:has(#figNet) .label { color: var(--o-products); }

/* Stock value: Reports' card and the Products summary, whichever of stock or
   selling value it is showing. */
.card.stat:has(#figStock),
#valuation { background-color: var(--o-products-soft); }
.card.stat:has(#figStock) .hint,
#valuation .sv-label,
#valuation .hint { color: var(--o-products); }

/* Products: the two existing summary cards, by the state they report. */
#lowStock { background-color: var(--warn-soft); }
#lowStock .meta { color: var(--warn); }
#uncollectedLink { background-color: var(--o-customers-soft); }
#uncollectedLink .meta { color: var(--o-customers); }
/* Their icon chips take the plain surface, as on the attention rows, so the
   chip does not vanish into the card's own tint. */
#lowStock .op-ico,
#uncollectedLink .op-ico { background-color: var(--surface); }

/* Needs attention, slide by slide and in View all, by the condition the slide
   reports. The icon chip already names it (o-products a team message,
   o-expenses a voided sale, o-warn a sale to review, o-customers goods to
   collect, o-danger nothing left on the shelf); the row takes the same tint,
   its border the accent, and its reason line the accent. */
.attn-row:has(.attn-ico.o-products) .attn-link {
  background-color: var(--o-products-soft); border-color: var(--o-products); }
.attn-row:has(.attn-ico.o-products) .attn-why { color: var(--o-products); }
.attn-row:has(.attn-ico.o-expenses) .attn-link {
  background-color: var(--o-expenses-soft); border-color: var(--o-expenses); }
.attn-row:has(.attn-ico.o-expenses) .attn-why { color: var(--o-expenses); }
.attn-row:has(.attn-ico.o-warn) .attn-link {
  background-color: var(--warn-soft); border-color: var(--warn); }
.attn-row:has(.attn-ico.o-warn) .attn-why { color: var(--warn); }
.attn-row:has(.attn-ico.o-customers) .attn-link {
  background-color: var(--o-customers-soft); border-color: var(--o-customers); }
.attn-row:has(.attn-ico.o-customers) .attn-why { color: var(--o-customers); }
.attn-row:has(.attn-ico.o-danger) .attn-link {
  background-color: var(--danger-soft); border-color: var(--danger); }
.attn-row:has(.attn-ico.o-danger) .attn-why { color: var(--danger); }
/* On a tinted row the icon chip would vanish into the same tint, so it takes
   the plain surface instead; its accent icon keeps the colour. */
.attn-row:has(.attn-ico) .attn-ico { background-color: var(--surface); }
/* The tint replaces the plain hover background, so hover is shown on the
   border instead. */
@media (hover: hover) {
  .attn-row:has(.attn-ico) .attn-link:hover { border-color: var(--brand); }
}

/* The business banner without a cover photograph. */
.bizbanner.nocover { background-color: var(--brand-soft); }

/* Products sold today: what each line came to. */
#soldToday .row { border-color: var(--o-sale-soft); }
#soldToday .row .amount { color: var(--o-sale); }

/* The Dashboard's own warnings: a refresh that failed, a check that could
   not run, a business switched elsewhere, a section that could not load. */
#staleNote,
#attnIncomplete,
#attnSwitched,
#todayFailed,
#soldToday .notice { background-color: var(--warn-soft); color: var(--warn); }

/* And its all-clear. */
#attnEmpty { color: var(--ok); }


/* ── Add / Edit product form ───────────────────────────────────────────────
   Scoped to .pf-form and its siblings on shop/product_form.html. Every rule
   below carries a pf- prefix or sits under .pf-form, so no existing component
   changes shape on any other page, and every colour is a token so the screen
   follows the theme rather than inventing one.

   One column, always. The shared .field-row is a two-across grid with no
   breakpoint, which put five field pairs side by side at 360px and crowded
   the longer Kiswahili labels. This form does not use it: fields stack, and
   there is no width at which they stop stacking. */

.pf-form .field { margin-bottom: 14px; }

/* The switch the edit gate throws. A fieldset carries no box of its own here;
   it exists so every control inside can be made unavailable at once, which is
   what `disabled` on a fieldset means natively. While it is off the fields are
   dimmed so the screen agrees with the keyboard. */
.pf-lock { border: 0; padding: 0; margin: 0; min-width: 0; }
.pf-lock[disabled] { opacity: .55; }

.pf-loading {
  margin: 0 0 12px; padding: 12px 14px; border-radius: 12px;
  background: var(--bg); color: var(--muted);
  font-size: 14px; line-height: 1.45;
}

.pf-req { color: var(--muted); font-weight: 400; }
.pf-opt { color: var(--muted); font-weight: 400; font-size: 12px; margin-left: 6px; }
.pf-unit { color: var(--muted); font-weight: 400; }

.pf-guide {
  margin: 0 0 12px; padding: 12px 14px; border-radius: 12px;
  background: var(--brand-soft); color: var(--text);
  font-size: 14px; line-height: 1.45;
}

/* Errors are text beside their own field, never colour alone. */
.pf-err { margin: 6px 0 0; font-size: 13px; line-height: 1.4; color: var(--danger); }
.pf-form [aria-invalid="true"] { border-color: var(--danger); }

.pf-summary {
  margin: 0 0 12px; padding: 12px 14px; border-radius: 12px;
  background: var(--danger-soft); color: var(--danger);
  font-size: 14px; line-height: 1.45;
}
.pf-summary:focus-visible { outline: 2px solid var(--danger); outline-offset: 2px; }

.pf-fail {
  margin-top: 8px; padding: 12px 14px; border-radius: 12px;
  background: var(--danger-soft); color: var(--danger);
}
.pf-fail p { margin: 0 0 8px; font-size: 14px; line-height: 1.4; }

/* Collapsed groups. details/summary keeps the open state in the element and
   reachable from the keyboard without any script. */
.pf-sec {
  border: 1px solid var(--border); border-radius: 12px;
  margin-bottom: 12px; background: var(--surface);
}
.pf-sec > summary {
  min-height: 44px; display: flex; align-items: center; flex-wrap: wrap;
  padding: 12px 14px; cursor: pointer; font-weight: 600; font-size: 14px;
  list-style: none;
}
.pf-sec > summary::-webkit-details-marker { display: none; }
.pf-sec > summary::after {
  content: "+"; margin-left: auto; font-weight: 400; font-size: 18px;
  color: var(--muted);
}
.pf-sec[open] > summary::after { content: "\2212"; }
.pf-sec > summary:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.pf-sec-body { padding: 0 14px 12px; }

/* The low-stock warning: a switch and, only while it is on, its level. */
/* The switch and its words are one target: pressing either toggles it, so the
   label spans the whole 44px band rather than sitting as a 22px line inside
   it. Chromium measures what a finger actually meets, and a centred label in
   a tall row leaves only its own height to press. */
.pf-toggle { display: flex; align-items: stretch; gap: 10px; min-height: 44px; }
.pf-toggle input[type="checkbox"] {
  width: 24px; height: 24px; margin: 0; flex: none; align-self: center;
}
.pf-toggle label {
  margin: 0; font-size: 14px; color: inherit; cursor: pointer;
  display: flex; align-items: center; min-height: 44px; flex: 1;
}
.pf-lowstock .hint { margin: 0 0 8px 32px; }
.pf-lowstock .pf-level { margin-left: 32px; }

/* The shared .btn.sm is 38px tall, which is below the target size this
   screen is held to. Raised inside this form and its failure states only —
   every other page keeps the size it already had. */
.pf-form .btn.sm,
.pf-fail .btn.sm { min-height: 44px; }

.pf-newcat { margin-top: 8px; min-height: 44px; }
.pf-newcat-wrap {
  margin-top: 10px; padding: 12px; border-radius: 12px; background: var(--bg);
}
.pf-newcat-wrap label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; }
.pf-newcat-wrap input {
  width: 100%; box-sizing: border-box; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: 10px; font-size: 16px;
}
.pf-newcat-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.pf-newcat-actions .btn { min-height: 44px; flex: 1 1 auto; }

/* ── Stakeholder application review card ───────────────────────────────────
   Scoped to the Owner review sheet on shop/stakeholder_applications.html.
   Every rule below is under .review-card, so nothing here reaches any other
   page and no existing component changes shape. The shell itself — .sheet,
   .sheet-body, .btn, .hint, .row — is untouched.

   What this fixes: the review sheet rendered one long browser-default <dl>
   with no styling at all (`.kv` was never defined for the My Business shell,
   only inside the investor-facing stakeholders/ templates), so an Owner on a
   phone read the evidence as an unformatted dt/dd dump. The six groups below
   are the six questions a reviewer actually answers, in the order they are
   answered. */

.review-card { position: relative; }

/* The accent strip. It cancels the sheet's own padding so it sits flush at
   the very top of the card, and repeats the sheet's top corner radius so the
   card keeps its rounded edge in both the mobile (bottom-sheet) and the
   >=640px (centred dialog) layouts. Decorative only — aria-hidden in the
   markup, and it encodes nothing about status. */
.review-strip {
  height: 5px;
  margin: -18px -16px 16px;
  border-radius: 18px 18px 0 0;
  background: var(--review-accent);
}

.review-card h2 { margin: 0 0 2px; }
.review-card .review-note { margin: 0 0 4px; }

.review-sec {
  padding: 12px 0 0;
  border-top: 1px solid var(--border);
  margin-top: 12px;
}
/* The first group sits directly under the title; a rule there would read as a
   second strip immediately below the accent one. */
.review-sec:first-of-type { border-top: 0; margin-top: 6px; }

.review-sec-h {
  margin: 0 0 8px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--muted);
}

/* Label above value, not beside it. Side-by-side on a narrow Android screen
   either truncates the label or squeezes the value into a column a few
   characters wide; stacking keeps a long applicant name and a long reference
   readable at any width.

   The grid items are the label/value wrappers the page builds, not the dt and
   dd themselves — so when the list becomes two columns below, each value
   stays under its own label instead of drifting into the next cell. */
.review-kv { margin: 0; display: grid; gap: 12px; }
.review-kv > div { min-width: 0; }
.review-kv dt {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
}
.review-kv dd {
  margin: 2px 0 0;
  font-size: 15px;
  line-height: 1.35;
  overflow-wrap: anywhere;
}
/* Two side-by-side pairs once there is room for both to breathe. */
@media (min-width: 420px) {
  .review-kv { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Evidence blocks: a soft card inside the section, so "there is a document to
   open" is visibly a different kind of thing from "here is a stored value". */
.review-evidence {
  margin-top: 12px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface-2);
}
.review-evidence .hint { margin: 0 0 10px; }
.review-evidence .btn { width: 100%; }

/* Actions stack full width on a phone and never overlap: each control is its
   own grid row at the shell's own tap height. */
.review-actions { display: grid; gap: 8px; margin-top: 0; }
.review-actions .btn { width: 100%; }
@media (min-width: 420px) {
  .review-actions {
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
  }
}

.review-reject { margin-top: 12px; }
.review-reject textarea { width: 100%; }

