/* LUNOX ZP pilot — light, KumoDoc-inspired theme. Brand #470054. */
:root {
  --brand: #470054;
  --brand-dark: #33003d;
  --brand-light: #6a2178;
  --brand-tint: #f5ecf7;
  --brand-tint-2: #ede0f1;
  --bg: #f4f2f7;
  --card: #ffffff;
  --text: #2a1f2e;
  --muted: #7a6c80;
  --line: #e9e2ec;
  --ok: #1a7a3c;      --ok-bg: #e3f4e8;
  --warn: #9a5b00;    --warn-bg: #fdf0dc;
  --bad: #b3261e;     --bad-bg: #fde7e5;
  --info: #0b5fa5;    --info-bg: #e3effa;
  --neutral-bg: #efeaf2;
  --shadow: 0 2px 12px rgba(71, 0, 84, .07);
  --shadow-lift: 0 8px 24px rgba(71, 0, 84, .13);
  /* mattress material colours (always paired with a printed name) */
  --mat-latex: #f6f0da;
  --mat-memory: #d4ecd0;
  --mat-soft: #f8e9a8;
  --mat-medium: #e0d2f0;
  --mat-hard: #d2e5f7;
  --radius: 16px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font: 15px/1.5 "Segoe UI", system-ui, sans-serif;
  background: var(--bg); color: var(--text);
  min-height: 100vh;
}
a { color: var(--brand); }
h1 { font-size: 1.4rem; margin: 0 0 .8rem; font-weight: 700; }
h2 { font-size: 1.05rem; margin: 1rem 0 .5rem; font-weight: 700; }
h3 { font-size: .95rem; margin: .75rem 0 .35rem; font-weight: 700; }

/* ---- layout: light sidebar, airy content ---- */
.shell { display: flex; min-height: 100vh; }
.sidebar {
  width: 232px; flex: 0 0 232px; background: var(--card); color: var(--text);
  display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh;
  overflow-y: auto; border-right: 1px solid var(--line);
}
.sidebar .logo { padding: 18px 16px 12px; }
.sidebar .logo .logo-mark { display: none; }
.login-brand {
  font-size: 1.05rem; font-weight: 800; letter-spacing: .42em;
  color: var(--brand, #470054); margin: 6px 0 2px;
}
.sidebar .brandname {
  font-size: .72rem; font-weight: 800; letter-spacing: .34em;
  color: var(--brand, #470054); opacity: .85; margin: 4px 0 2px; padding-left: 2px;
}
.login-brand {
  font-size: 1.05rem; font-weight: 800; letter-spacing: .42em;
  color: var(--brand, #470054); margin: 6px 0 2px;
}
.sidebar .brandname { display: none; }
/* The rail toggle sits at the foot of the menu, out of the way of the links. */
.rail-btn {
  align-self: flex-end; margin: 0 10px 12px;
  width: 26px; height: 26px; min-height: 0; padding: 0; border-radius: 50%;
  background: var(--brand-tint); color: var(--brand); border: 1px solid var(--line);
  font-size: .9rem; line-height: 1; box-shadow: none; flex: none;
}
.rail-btn:hover { background: var(--brand-tint-2); box-shadow: none; }

/* ---- collapsed rail (Dr Daniel, 2026-08-24) ----
   Icons only, with the labels arriving on hover. The sidebar goes FIXED and the
   page is offset by the rail's width, so opening the flyout draws over the
   content instead of shoving it — a menu that moves the screen every time the
   mouse drifts past is worse than one that is simply wide. */
.shell.rail .sidebar {
  position: fixed; top: 0; left: 0; height: 100vh; z-index: 40;
  width: var(--rail-w, 64px); overflow-x: hidden;
  transition: width .16s ease, box-shadow .16s ease;
}
.shell.rail .main { margin-left: var(--rail-w, 64px); }
.shell.rail .sidebar:hover,
.shell.rail .sidebar:focus-within {
  width: 232px;
  box-shadow: 14px 0 34px rgba(43, 15, 51, .16);
}
/* Collapsed: the wordmark gives way to the round mark, and the branch line and
   footer note have nowhere to go. */
.shell.rail .sidebar .logo { padding: 14px 0 10px; text-align: center; }
.shell.rail .sidebar .logo img { display: none; }
.shell.rail .sidebar .logo .logo-mark {
  display: inline-block; width: 34px; height: 34px; margin: 0 auto;
}
.shell.rail .sidebar .logo .sub,
.shell.rail .sidebar .foot { opacity: 0; pointer-events: none; }
.shell.rail .sidebar nav { padding: 6px 8px; }
.shell.rail .rail-btn { align-self: center; margin: 0 0 12px; }
.shell.rail .sidebar:hover .rail-btn,
.shell.rail .sidebar:focus-within .rail-btn { align-self: flex-end; margin: 0 10px 12px; }
.shell.rail .sidebar nav a { justify-content: center; padding: 10px 0; }
.shell.rail .sidebar nav a .lbl {
  opacity: 0; width: 0; overflow: hidden; white-space: nowrap;
  transition: opacity .12s ease;
}
/* …and back again when the flyout opens. */
.shell.rail .sidebar:hover .logo,
.shell.rail .sidebar:focus-within .logo { padding: 18px 16px 12px; text-align: left; }
.shell.rail .sidebar:hover .logo img,
.shell.rail .sidebar:focus-within .logo img { display: block; }
.shell.rail .sidebar:hover .logo .logo-mark,
.shell.rail .sidebar:focus-within .logo .logo-mark { display: none; }
.shell.rail .sidebar:hover .logo .sub,
.shell.rail .sidebar:focus-within .logo .sub,
.shell.rail .sidebar:hover .foot,
.shell.rail .sidebar:focus-within .foot { opacity: 1; pointer-events: auto; }
.shell.rail .sidebar:hover nav { padding: 6px 10px 10px; }
.shell.rail .sidebar:hover nav a,
.shell.rail .sidebar:focus-within nav a { justify-content: flex-start; padding: 9px 12px; }
.shell.rail .sidebar:hover nav a .lbl,
.shell.rail .sidebar:focus-within nav a .lbl { opacity: 1; width: auto; }

/* On a phone the shell already stacks; a fixed rail there would just cover the
   page, so the collapse is desktop-only. */
@media (max-width: 900px) {
  .shell.rail .sidebar { position: sticky; width: auto; }
  .shell.rail .main { margin-left: 0; }
  .rail-btn { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .shell.rail .sidebar, .shell.rail .sidebar nav a .lbl { transition: none; }
}
/* aspect-ratio reserves the logo's box BEFORE the file arrives (1770x543 as
   drawn). Without it the unloaded <img> lays out its alt text instead, the
   menu is briefly ~200px taller, and anything measured against it in that
   moment — the restored scroll offset — lands in the wrong place. */
.sidebar .logo img { display: block; width: 100%; max-width: 168px; height: auto; aspect-ratio: 1770 / 543; }
.sidebar .logo .sub {
  font-size: .68rem; color: var(--muted); letter-spacing: .1em; margin-top: 8px;
  font-weight: 600;
}
.sidebar nav { flex: 1; padding: 6px 10px 10px; }
.sidebar nav a {
  display: flex; gap: 11px; align-items: center; color: var(--muted); text-decoration: none;
  padding: 9px 12px; font-size: .9rem; border-radius: 10px; margin: 1px 0; font-weight: 500;
  transition: background .12s, color .12s;
}
.sidebar nav a:hover { background: var(--brand-tint); color: var(--brand); }
.sidebar nav a.active { background: var(--brand); color: #fff; font-weight: 600; box-shadow: var(--shadow); }
/* An icon standing in for what used to be an emoji (2026-08-28). It sits
   INLINE with its label, so it scales with whatever type surrounds it and
   lands on the baseline instead of shoving the line box around.
   The svg is given a real size HERE rather than `width:100%` on a nested
   span: a span inside an unsized parent has no width of its own to resolve
   against, so the svg fell back to its intrinsic default and the icons went
   all over the place the first time this shipped. */
.ic {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 1.15em; height: 1.15em;
  vertical-align: -0.17em;
}
.ic > span { display: contents; }
.ic svg { width: 1.15em; height: 1.15em; display: block; }
/* No margin on the icon: buttons are flex rows with their own gap:7px, and an
   extra margin just made that 8.8px. Inline uses (a table cell, a sentence)
   get their space from the label text itself. */

/* The sidebar keeps its own fixed size: those icons sit in a rail, not in a
   sentence, and must not shrink with the label text. */
.sidebar nav a .ic { width: 21px; height: 21px; }
.sidebar nav a .ic svg { width: 21px; height: 21px; }
.sidebar .foot {
  padding: 10px 16px 14px; font-size: .7rem; color: var(--muted);
  border-top: 1px solid var(--line);
}

/* ---- the sidebar wears the branch's colour (2026-08-28) ----------------
   Same four colours as the branch switcher's chips, so the two read as one
   idea: ZP brand purple, 8TR teal, RU amber, CS blue. Only the SIDEBAR is
   re-tinted — the rest of the app stays brand purple, because a whole screen
   changing colour on a branch switch is alarming rather than informative.
   ZP needs no rule: it is the brand, and the default. */
/* No coloured edge: a 2px rule in the branch colour read as a black line down
   the screen at ZP, whose brand purple is nearly black (Dr Daniel, 2026-08-28).
   The branch already shows in the active nav item, the hover tint and the lit
   chip, which is enough of a cue without drawing a bar beside the content. */
body[data-branch="8TR"] .sidebar {
  --brand: #0e7490; --brand-light: #1596b5;
  --brand-tint: #e6f4f8; --brand-tint-2: #d3ebf2;
}
body[data-branch="RU"] .sidebar {
  /* Deep rose (Dr Daniel, 2026-09-06: the brown was "so ugly"). */
  --brand: #a13d63; --brand-light: #c9628e;
  --brand-tint: #f9edf3; --brand-tint-2: #f3dbe7;
}
body[data-branch="CS"] .sidebar {
  --brand: #0b5fa5; --brand-light: #1a7bc9;
  --brand-tint: #e8f1fa; --brand-tint-2: #d5e5f5;
}

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar {
  background: var(--card); border-bottom: 1px solid var(--line);
  padding: 10px 22px; display: flex; align-items: center; gap: 14px;
  position: sticky; top: 0; z-index: 10;
}
.topbar .pagetitle { font-weight: 700; font-size: 1.05rem; }
.topbar .who {
  margin-left: auto; display: flex; align-items: center; gap: 9px;
  background: var(--bg); border-radius: 999px; padding: 4px 14px 4px 4px;
  font-size: .82rem; color: var(--muted);
}
.topbar .avatar {
  width: 30px; height: 30px; border-radius: 50%; background: var(--brand); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: .85rem; flex: none;
}
.topbar .iconbtn {
  width: 38px; height: 38px; min-height: 0; padding: 0; border-radius: 50%;
  background: var(--brand-tint); color: var(--brand); border: none; flex: none;
}
.topbar .iconbtn:hover { background: var(--brand-tint-2); }
.topbar .iconbtn svg { width: 19px; height: 19px; }
/* The topbar has to survive a 375px phone (Dr Daniel, 2026-08-29: the name chip
   and the log-out button ran off the right edge). Flex items refuse to shrink
   below their content by default, so every one of them is given permission to,
   and the least useful text is dropped first: the page title ellipses, then the
   role goes, then the name — leaving the avatar and the log-out button, which
   never shrink because they are the two things you always need. The environment
   tag keeps its place in the queue ahead of the title -- it may only ever
   truncate, never disappear, because nobody may miss that they are on a copy;
   its full text stays reachable as a tooltip. */
.topbar > * { min-width: 0; }
.topbar .pagetitle { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.envtag {
  background: var(--bad-bg, #fde8e6); color: var(--bad, #a3352c); font-weight: 700;
  padding: 4px 12px; border-radius: 999px; font-size: .8rem; letter-spacing: .03em;
  white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
.topbar .who { min-width: 0; }
.topbar .who-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar .who-role { white-space: nowrap; flex: none; }
.topbar .who-role::before { content: '\00b7'; margin: 0 5px; }
@media (max-width: 900px) {
  .topbar { padding: 8px 14px; gap: 10px; }
  .topbar .who-role { display: none; }
}
@media (max-width: 560px) {
  .topbar .who { padding: 3px; }
  .topbar .who-name { display: none; }
  /* The sidebar already shows which page is open, so the title is the one
     thing here that carries no information a phone user does not have. */
  .topbar .pagetitle { display: none; }
}

/* The page sits in the middle of whatever room it has, and takes more of it
   on a wide monitor (Dr Daniel, 2026-09-13: "a wide monitor, the right side
   of the screen is empty"). The 1320px cap hugged the LEFT edge, so a 2560px
   screen showed a third of itself blank; boards and registers here are
   tables, and a table with room to breathe beats a fixed measure. The
   calendar has always gone full-bleed — this brings the rest in line. */
.content { padding: 20px 22px; max-width: 1320px; width: 100%; margin-inline: auto; }
@media (min-width: 1500px) { .content { max-width: 1760px; } }
@media (min-width: 2000px) { .content { max-width: none; } }
/* The calendar page trims its top padding (Dr Daniel, 2026-09-06: "These
   up-down gap make it narrower") — the frozen toolbar carries its own. */
.content:has(.cal-full) { padding-top: 4px; }
/* The factory portal hugs the top the same way (Dr Daniel, 2026-09-06:
   "The gap above factory portal row make it smallest"). */
.content:has(.fo-filters) { padding-top: 2px; }

/* ---- components ---- */
.card {
  background: var(--card); border: none; border-radius: var(--radius);
  padding: 16px 18px; margin-bottom: 14px; box-shadow: var(--shadow);
}
.grid { display: grid; gap: 12px; }
/* Wider tiles and a figure that can shrink (2026-08-28): a year-to-date of
   RM5.2M is eight characters more than the tile was built for, and the old
   fixed 1.5rem pushed it straight out of the card. */
.kpis { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
/* Two rows with a shape of their own (Dr Daniel, 2026-09-14: "1,2,3... one
   row / abc... one row"): the money across the top, the day's traffic under
   it. They keep their rows on a wide screen and wrap on a narrow one rather
   than squeezing six tiles into a phone. */
/* Four money chips a row, two rows of them (Dr Daniel, 2026-09-14: "no need
   squeeze all of them in one row dy, but the same category shall be same
   size") — every chip in a row the same width and the same height, which a
   grid of equal tracks gives for nothing. Eight across only on a screen wide
   enough that a chip is still wider than the longest ringgit figure. */
.kpis-money { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.kpis-day { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (min-width: 1900px) { .kpis-money { grid-template-columns: repeat(8, minmax(0, 1fr)); } }
/* The money keeps ONE row down to a 1100px screen and the day's traffic keeps
   ONE row down to 1300 (Dr Daniel, 2026-09-14: "make them in one row too").
   That is only possible because each chip is built to its own content: the
   money chips are the wide ones — a figure of eleven characters that must
   never break mid-number — and the counts are the narrow ones. */
/* ONE shape for both rows (Dr Daniel, 2026-09-14: "I want the chips and gaps
   to be like this!! First row also wan same thickness as this!!!"). Same
   padding, same icon, same three single lines — the only way two independent
   grids come out the same thickness. Each selector names the chip as well as
   the row: .kpi-fun sizes the icon and the figure at the same specificity and
   further down the file, so a plainer selector here would lose to it in
   silence. */
.kpis-money, .kpis-day { gap: 14px; margin-bottom: 14px; }
.kpis-money > .kpi, .kpis-day > .kpi { margin-bottom: 0; }
/* Taller chips with the words closer together (Dr Daniel, 2026-09-14: "make
   it taller and slighly shorter... the gap btween the word's rows make it
   narrower") — the height comes from the padding, not from air between the
   lines. */
.kpis-money .kpi-fun, .kpis-day .kpi-fun { padding: 15px 12px; gap: 10px; }
.kpis-money .kpi-fun .v, .kpis-day .kpi-fun .v { line-height: 1.1; }
.kpis-money .kpi-fun .l, .kpis-day .kpi-fun .l { margin-top: 0; line-height: 1.2; }
.kpis-money .kpi-cmp, .kpis-money .kpi-spent,
.kpis-day .kpi-spent, .kpis-day .kpi-cmp { margin-top: 1px; }
.kpis-money .kpi-fun .kpi-ic, .kpis-day .kpi-fun .kpi-ic {
  font-size: 1.02rem; width: 31px; height: 31px; flex: 0 0 31px; border-radius: 10px;
}
.kpis-money .kpi-fun .v, .kpis-day .kpi-fun .v { font-size: 1.28rem; }
.kpis-money .kpi-fun .l, .kpis-day .kpi-fun .l { font-size: .71rem; margin-top: 1px; }
/* Six money chips of about a sixth of the page: below 1700 that is under
   240px, and "RM16,281.20" has to fit beside its icon inside it. */
@media (max-width: 1700px) {
  .kpis-money .kpi-fun .v, .kpis-day .kpi-fun .v { font-size: 1.18rem; }
}
@media (max-width: 1300px) {
  /* Narrower chips, but no shorter: the padding that makes them tall is the
     last thing a smaller screen should take away (Dr Daniel, 2026-09-14). */
  .kpis-money .kpi-fun, .kpis-day .kpi-fun { padding: 15px 10px; gap: 9px; }
  .kpis-money .kpi-fun .kpi-ic, .kpis-day .kpi-fun .kpi-ic {
    font-size: .94rem; width: 27px; height: 27px; flex: 0 0 27px; border-radius: 9px;
  }
  .kpis-money .kpi-fun .v, .kpis-day .kpi-fun .v { font-size: 1.12rem; }
  .kpis-money .kpi-fun .l, .kpis-day .kpi-fun .l { font-size: .67rem; }
  .kpis-money .kpi-cmp, .kpis-money .kpi-spent, .kpis-day .kpi-spent { font-size: .64rem; }
}
/* The label is one line, always: what will not fit is cut and said in full on
   hover. The line under it may take two — "Showed up 9 · RM4,580.00" does not
   fit a seventh of the screen in one — but never three, so both rows come out
   the same thickness. */
.kpis-money .kpi-fun .l, .kpis-day .kpi-fun .l {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.kpis-money .kpi-cmp, .kpis-money .kpi-spent, .kpis-day .kpi-spent, .kpis-day .kpi-cmp {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; white-space: normal; line-height: 1.25;
  /* Room for two lines whether or not this one needs them: a row whose chips
     reserve the same space as the other row's is the same thickness as it at
     every width, not only at the one it was measured on. */
  min-height: 2.5em;
}
/* Everything in a chip reads from one left margin (Dr Daniel, 2026-09-14,
   after seeing both: "just revert back ba, make them aligned to the left") —
   figure, label and the line under it, under the mark at the chip's edge. */
.kpis-money .kpi-fun > div, .kpis-day .kpi-fun > div { min-width: 0; }
/* Six money tiles fold 6 -> 3 -> 2 -> 1, so a row is never part empty
   (Dr Daniel, 2026-09-14: "this view is so messy") — the old seven broke
   into 4 + 3 and left a hole the width of a tile on the right. The five
   count tiles are slighter, so they hold their single row much further
   down; when they do fold, an odd one out takes the whole row rather than
   sitting beside a gap. */
@media (max-width: 1100px) {
  .kpis-money { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Five is prime, so a plain fold leaves a hole: below 1100px the row becomes
   six invisible tracks and the chips take two each on the first row and three
   each on the second — 3 + 2, both rows edge to edge. */
@media (max-width: 1100px) {
  .kpis-day { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .kpis-day > .kpi { grid-column: span 2; }
  .kpis-day > .kpi:nth-child(n+4) { grid-column: span 3; }
}
@media (max-width: 900px) {
  .kpis-money, .kpis-day { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kpis-day > .kpi, .kpis-day > .kpi:nth-child(n+4) { grid-column: auto; }
  .kpis-day > .kpi:last-child:nth-child(odd) { grid-column: 1 / -1; }
}
@media (max-width: 560px) {
  .kpis-money, .kpis-day { grid-template-columns: minmax(0, 1fr); }
  .kpis-day > .kpi { grid-column: auto; }
}
/* Today's figure reads like every other chip's; yesterday's sits on the line
   under it with the direction between them. Two figures side by side needed a
   width a seventh of the screen does not have. */
.kpi-vs .kpi-vs-a.up { color: #166534; }
.kpi-vs .kpi-vs-a.down { color: #be185d; }
/* What the people in a traffic tile spent today. */
.kpi-spent { font-size: .72rem; font-weight: 700; color: var(--brand); margin-top: 3px; }
.kpi { text-align: left; min-width: 0; }
.kpi .v {
  font-size: clamp(1.15rem, 1.1rem + 0.7vw, 1.5rem);
  font-weight: 800; color: var(--brand); letter-spacing: -.01em;
  font-variant-numeric: tabular-nums; line-height: 1.15;
  /* A figure longer than anything anticipated wraps inside the card rather
     than escaping it — never pretty, but never broken either. */
  overflow-wrap: anywhere;
}
.kpi .l { font-size: .76rem; color: var(--muted); font-weight: 600; margin-top: 2px; }
/* Livelier dashboard (Dr Daniel, 2026-09-02): each tile carries its own mark
   and a tone stripe; the charts below are hand-rolled SVG. */
.kpi-fun { display: flex; gap: 10px; align-items: center; border-left: 4px solid var(--kt, var(--brand)); }
.kpi-fun > div { min-width: 0; }
.kpi-fun .kpi-ic {
  font-size: 1.25rem; width: 40px; height: 40px; flex: 0 0 40px; border-radius: 12px;
  display: inline-flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--kt, var(--brand)) 12%, #fff);
}
/* A money figure never wraps mid-number: it shrinks to fit instead. */
.kpi-fun .v {
  color: var(--kt, var(--brand));
  word-break: keep-all; overflow-wrap: normal; white-space: nowrap;
  font-size: clamp(.98rem, .86rem + .6vw, 1.4rem);
}
/* Roomier tracks for the Kumo cards (Dr Daniel, 2026-09-04: "very messy
   and cramp") — wide charts span the full row via .span2. */
.dash-charts { grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); }
.dash-charts .card h2 { margin-top: 0; }
.donut-wrap { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.donut-hole { position: relative; width: 148px; height: 148px; flex: 0 0 148px; }
.donut { width: 100%; height: 100%; transform: rotate(0deg); }
.donut circle { transition: stroke-width .15s; }
.donut circle:hover { stroke-width: 8; }
.donut-centre {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; pointer-events: none;
}
.donut-centre strong { font-size: .95rem; letter-spacing: -.01em; }
.donut-centre span { font-size: .66rem; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }
.donut-legend { flex: 1; min-width: 150px; display: flex; flex-direction: column; gap: 5px; }
.dl-row { display: flex; align-items: center; gap: 7px; font-size: .78rem; }
.dl-dot { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 10px; }
.dl-l { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dl-v { font-weight: 700; font-variant-numeric: tabular-nums; }
.dl-p { color: var(--muted); width: 34px; text-align: right; font-variant-numeric: tabular-nums; }
/* ---- dashboard charts (rebuilt 2026-09-02 to the mark specs) ----------
   Columns: a CSS grid of equal tracks - even spacing by construction.
   Bars <= 24px wide, 4px rounded data-end, square baseline, 2px gaps.
   The line chart's SVG carries shapes only; dots and text are HTML. */
.col-plot {
  display: grid; align-items: end; height: 128px; column-gap: 2px;
  padding-top: 18px; /* room for the two selective value labels */
}
.col-cell {
  position: relative; height: 100%;
  display: flex; align-items: flex-end; justify-content: center;
}
.col-cell:hover { background: color-mix(in srgb, var(--brand) 5%, transparent); border-radius: 6px; }
.col-bars {
  display: flex; align-items: flex-end; justify-content: center; gap: 2px;
  width: 100%; height: 100%;
}
.col-bar {
  width: 100%; max-width: 24px; border-radius: 4px 4px 0 0;
  background: var(--brand);
  transition: filter .15s;
}
.col-bar.ghost { background: #ddd2f2; }
.col-bar.hot { background: #4c1d95; }
.col-cell:hover .col-bar { filter: brightness(1.15); }
.col-val {
  position: absolute; left: 50%; transform: translateX(-50%);
  font-size: .66rem; font-weight: 700; color: var(--text);
  white-space: nowrap; pointer-events: none;
  font-variant-numeric: tabular-nums;
}
.axis-row {
  display: grid; column-gap: 2px;
  font-size: .64rem; color: var(--muted); padding-top: 3px;
}
.axis-row span { text-align: center; overflow: hidden; }
/* the week line */
.line-wrap { position: relative; height: 150px; }
.line-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.line-hit {
  position: absolute; width: 20px; height: 20px; transform: translate(-50%, -50%);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.line-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--brand); border: 2px solid var(--card-bg, #fff);
  box-sizing: border-box; transition: transform .15s;
}
.line-hit:hover .line-dot { transform: scale(1.4); }
.line-val {
  position: absolute; transform: translate(-50%, -170%);
  font-size: .68rem; font-weight: 700; color: var(--text);
  white-space: nowrap; pointer-events: none; font-variant-numeric: tabular-nums;
}
.line-val.end { transform: translate(-80%, -170%); }
/* shared */
.chart-tip {
  position: fixed; z-index: 4000; pointer-events: none;
  background: #241228; color: #fff; font-size: .76rem; font-weight: 700;
  padding: 5px 10px; border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,.25);
  white-space: nowrap; animation: fade .12s ease-out;
}
.cmp-legend {
  display: flex; align-items: center; gap: 6px; justify-content: center;
  font-size: .68rem; color: var(--muted); margin-top: 6px;
}
.cmp-dot { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.cmp-legend .cmp-dot:not(:first-child) { margin-left: 10px; }
.donut circle { cursor: pointer; transition: stroke-width .15s; }
.donut circle:hover { stroke-width: 8; }
.dbar { border-radius: 8px; transition: background .15s; }
.dbar:hover { background: var(--brand-tint, #f5eefa); }
.dbar .dbar-f { transition: width .5s cubic-bezier(.22,.9,.3,1), filter .15s; }
.dbar:hover .dbar-f { filter: brightness(.92) saturate(1.2); }
.dbar-medal { width: 26px; flex: 0 0 26px; text-align: center; }
/* The hero strip: clinician of the day, tracking vs last month, average bill. */
.dash-hero {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px; margin-bottom: 14px; padding: 14px 18px;
}
@media (max-width: 720px) { .dash-hero { grid-template-columns: minmax(0, 1fr); } }
.hero-cell { display: flex; gap: 12px; align-items: center; flex: 1 1 260px; min-width: 240px; }
/* Each half is a half: the clinician starts at the middle of the strip. */
.dash-hero .hero-cell { min-width: 0; }
.hero-body { min-width: 0; }
.hero-body > * + * { margin-top: 1px; }
.hero-t { line-height: 1.25; }
.hero-v { line-height: 1.18; }
/* Today's takings lead the page: the one figure written out in full, in the
   money tone, a size above everything around it. */
.hero-ic-money { background: #d8f2ed; }
.hero-v.hero-money {
  font-size: clamp(1.25rem, 1.1rem + .7vw, 1.7rem); color: #0d9488;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.hero-ic {
  font-size: 1.4rem; width: 46px; height: 46px; flex: 0 0 46px; border-radius: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand-tint, #f3ecf7);
}
.hero-t { font-size: .72rem; color: var(--muted); font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }
.hero-v { font-size: 1.02rem; font-weight: 800; letter-spacing: -.01em; }
.hero-v.up { color: #166534; }
.hero-v.down { color: #be185d; }
.hero-v.muted-v { color: var(--muted); font-weight: 600; }
.yt-wrap { display: flex; gap: 18px; flex-wrap: wrap; }
/* Figures stay INSIDE the card (Dr Daniel, 2026-09-06: "out of box") —
   the pair may shrink, the money never wraps, and the type is a notch
   smaller so RM five-figure days fit beside their arrow. */
.yt-cell { flex: 1; min-width: 0; text-align: center; }
.yt-pair { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 8px; min-width: 0; }
.yt-fig { display: flex; flex-direction: column; min-width: 0; }
.yt-fig strong { font-size: 1.02rem; letter-spacing: -.01em; white-space: nowrap; font-variant-numeric: tabular-nums; }
.yt-sub { font-size: .64rem; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
.yt-arrow { font-size: 1.1rem; }
.yt-arrow.up { color: #166534; }
.yt-arrow.down { color: #be185d; }
/* two charts side by side on one shared scale */
.card.span2 { grid-column: 1 / -1; }
.duo { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
@media (max-width: 760px) { .duo { grid-template-columns: 1fr; } }
.duo-t { font-size: .72rem; font-weight: 800; color: var(--muted); text-align: center; letter-spacing: .06em; margin-bottom: 2px; }
.top-table { width: 100%; font-size: .8rem; border-collapse: collapse; }
.top-table th { text-align: left; font-size: .68rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; padding: 4px 6px; }
.top-table td { padding: 5px 6px; border-top: 1px solid var(--line-soft, #f0eaf4); }
.top-table .num { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; }
.top-table tr:hover td { background: var(--brand-tint, #f5eefa); }

table { border-collapse: collapse; width: 100%; font-size: .88rem; }
th, td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
tr:last-child td { border-bottom: none; }
th { color: var(--muted); font-weight: 700; font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; }
tr.click { transition: background .1s; }
tr.click:hover { background: var(--brand-tint); cursor: pointer; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

button, .btn {
  font: inherit; border: none; background: var(--brand); color: #fff;
  border-radius: 999px; padding: 10px 20px; cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  font-size: .9rem; font-weight: 600; min-height: 42px;
  box-shadow: 0 2px 6px rgba(71, 0, 84, .22);
  transition: background .12s, transform .06s, box-shadow .12s;
}
button:hover, .btn:hover { background: var(--brand-light); box-shadow: var(--shadow-lift); }
button:active, .btn:active { transform: translateY(1px); box-shadow: none; }
button.ghost, .btn.ghost {
  background: var(--brand-tint); color: var(--brand); box-shadow: none;
}
button.ghost:hover, .btn.ghost:hover { background: var(--brand-tint-2); box-shadow: none; }
/* Red is reserved for destroy/cancel/reject/void — nothing else
   (Dr Daniel, 2026-08-23), so a red button always means "this removes something". */
/* A softer red (Dr Daniel, 2026-08-23): destructive actions still read as
   "stop and think" without shouting at staff all day. The darker --bad is
   kept for text and warnings, where contrast matters more. */
/* A destructive button is OUTLINED, not filled (Dr Daniel, 2026-08-29: "dun
   fill up red for the whole button, just make the border red will do"). A
   solid red slab beside three quiet buttons pulled the eye to the one action
   nobody should reach for by accident; the red border still says "careful"
   without shouting it. Filling on hover keeps the commitment obvious at the
   moment of clicking. */
button.danger {
  background: #fff; color: #b3392f; box-shadow: none;
  border: 1.5px solid #d9584f;
}
button.danger:hover { background: #fbe7e5; color: #a8322a; box-shadow: none; }
button.danger:active { background: #f6d5d1; }
/* "ghost danger" is the quiet form — outlined rather than filled, for actions
   that are destructive but routine (delete a draft, turn 2FA off). */
button.ghost.danger, .btn.ghost.danger {
  background: var(--bad-bg); color: #a8322a; box-shadow: none;
}
button.ghost.danger:hover, .btn.ghost.danger:hover { background: #fbd8d4; }
button.small, .btn.small { padding: 6px 14px; font-size: .8rem; min-height: 32px; }

/* The one button that commits money. Deliberately the heaviest control on the
   page so it can never be confused with the light "add a line" actions. */
button.primary-action {
  background: #3d0047; font-size: 1rem; font-weight: 700; padding: 14px 30px;
  min-height: 52px; letter-spacing: .01em;
  box-shadow: 0 4px 14px rgba(61, 0, 71, .38);
}
button.primary-action:hover { background: #520060; box-shadow: 0 6px 20px rgba(61, 0, 71, .45); }

/* Light-weight "build something" actions (product line, mattress, Mochi). */
button.soft, .btn.soft {
  background: var(--brand-tint); color: var(--brand); box-shadow: none;
  border: 1px solid var(--brand-tint-2); font-weight: 600;
}
button.soft:hover, .btn.soft:hover { background: var(--brand-tint-2); box-shadow: none; }

/* Positive completion (collected, taken home) and the money-taking buttons.
   Green border, light green fill (Dr Daniel, 2026-08-29) — the same outlined
   treatment the destructive buttons got, in the opposite colour, so the two
   ends of the scale are built the same way and neither is a solid slab. Green
   already meant "this completes it" on the issue confirmation; it now reads the
   same on the button that opens it. Darkens on hover, so the commitment is
   obvious at the moment of clicking.

   There used to be a second `button.go:hover` further down this block quietly
   overriding the first — one rule now, so the hover cannot drift. */
button.go, .btn.go {
  background: var(--ok-bg); color: #14612f; box-shadow: none;
  border: 1.5px solid var(--ok);
}
button.go:hover, .btn.go:hover { background: #d2ecdb; color: #0f4f26; box-shadow: none; }
button.go:active, .btn.go:active { background: #c3e5cf; }
/* The invoice page's big commit button: same treatment, a heavier border to
   match its weight. */
button.primary-action.go {
  background: var(--ok-bg); color: #14612f; box-shadow: none;
  border: 2px solid var(--ok);
}
button.primary-action.go:hover { background: #d2ecdb; color: #0f4f26; box-shadow: none; }

/* Production stages, coloured so the factory board reads at a glance. */
button.stage-ack { background: var(--brand); }
button.stage-start { background: #b45309; }
button.stage-start:hover { background: #92400e; }
button.stage-ready { background: var(--ok); }
button.stage-ready:hover { background: #14612f; }
button.stage-delivered { background: #2f7fc7; }
/* Factory action row (Dr Daniel, 2026-09-02): even-sized buttons in calm
   tones — the stage action keeps its meaning by hue, not by shouting. */
.fo-actbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.fo-actbar button {
  flex: 0 0 auto; min-height: 42px; font-size: .9rem; white-space: nowrap;
  padding: 9px 16px; border-radius: 12px; box-shadow: none; font-weight: 700;
}
.fo-actbar button.stage-ack { background: #efeaf7; color: #5b4a86; border: 1.5px solid #b9a5d6; }
.fo-actbar button.stage-ack:hover { background: #e4dbf1; }
.fo-actbar button.stage-start { background: #fdf3df; color: #92600a; border: 1.5px solid #d9b26a; }
.fo-actbar button.stage-start:hover { background: #fbe9c6; }
.fo-actbar button.stage-ready { background: #e5f4e9; color: #166534; border: 1.5px solid #7fb591; }
.fo-actbar button.stage-ready:hover { background: #d5ecdc; }
.fo-actbar button.stage-delivered { background: #e6f0f8; color: #2b5d85; border: 1.5px solid #8fb4d2; }
.fo-actbar button.stage-delivered:hover { background: #d6e6f4; }
.fo-actbar button.ghost {
  background: #f3f0f5; color: #55475e; border: 1.5px solid #cfc4d6;
}
.fo-actbar button.ghost:hover { background: #eae4ee; }
button.stage-delivered:hover { background: #256aa8; }
button:disabled { opacity: .45; cursor: not-allowed; box-shadow: none; }

input, select, textarea {
  font: inherit; padding: 10px 14px; border: 1.5px solid var(--line); border-radius: 12px;
  background: #fff; color: var(--text); width: 100%; max-width: 100%; min-height: 42px;
  transition: border-color .12s, box-shadow .12s;
}
textarea { min-height: 60px; }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--brand-light);
  box-shadow: 0 0 0 3px var(--brand-tint-2);
}
button:focus-visible { outline: 2px solid var(--brand-light); outline-offset: 2px; }
input[type="checkbox"] { min-height: 0; width: 20px; height: 20px; accent-color: var(--brand); cursor: pointer; }
label { display: block; font-size: .78rem; color: var(--muted); margin: 9px 0 4px; font-weight: 600; }
fieldset { border: 1.5px solid var(--line); border-radius: 12px; margin: 10px 0; }
legend { font-size: .78rem; color: var(--muted); padding: 0 6px; font-weight: 600; }
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.row > * { flex: 1; min-width: 130px; }
.row > .fix { flex: 0 0 auto; min-width: 0; }
.toolbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; }
.toolbar input, .toolbar select { width: auto; }

/* soft filled status badges */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 11px; border-radius: 999px; font-size: .74rem;
  font-weight: 700; white-space: nowrap; border: none;
  background: var(--neutral-bg); color: var(--muted);
}
.badge::before { content: "●"; font-size: .55rem; }
.b-pending, .b-draft, .b-open { background: var(--info-bg); color: var(--info); }
.b-arrived, .b-in_progress, .b-submitted, .b-in_production { background: var(--warn-bg); color: var(--warn); }
.b-completed, .b-resolved, .b-issued, .b-ready, .b-delivered, .b-acknowledged, .b-signed { background: var(--ok-bg); color: var(--ok); }
.b-cancelled, .b-no_show, .b-replaced, .b-withdrawn, .b-superseded, .b-exception { background: var(--bad-bg); color: var(--bad); }
.b-waiting_customer, .b-waiting_factory, .b-revision_requested { background: var(--neutral-bg); color: var(--muted); }

.muted { color: var(--muted); font-size: .85rem; }
.error { color: var(--bad); font-size: .88rem; margin: 6px 0; white-space: pre-wrap; }
.notice {
  background: var(--brand-tint); border: none; border-radius: 12px;
  padding: 10px 14px; font-size: .85rem; margin: 8px 0;
}
/* A notice that is a warning, not information — used where a sync would do
   something destructive if it were run as configured (2026-08-29). */
.notice.bad { background: var(--bad-bg); color: #8f2a22; }
.bad-text { color: var(--bad); }
/* A compact two-column read-out inside a panel. */
table.mini { width: 100%; border-collapse: collapse; font-size: .84rem; }
table.mini th { text-align: left; font-weight: 700; color: var(--muted); font-size: .76rem;
  text-transform: uppercase; letter-spacing: .03em; padding: 3px 0; }
table.mini td { padding: 3px 0; border-top: 1px solid var(--line); }

/* ---- modal (pop-up) ---- */
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(42, 31, 46, .45);
  backdrop-filter: blur(2px);
  display: flex; align-items: flex-start; justify-content: center; padding: 4vh 12px; z-index: 50;
  overflow-y: auto;
}
.modal {
  background: var(--card); border-radius: 18px; width: 100%; max-width: 640px;
  padding: 0 22px 20px; box-shadow: 0 24px 60px rgba(0, 0, 0, .3); margin-bottom: 4vh;
  animation: modal-in .16s ease-out;
  /* THE MODAL SCROLLS, NOT THE BACKDROP (Dr Daniel, 2026-08-29: "the top bar
     for all pop up pls freeze on top properly. duwan have the gap").
     While the backdrop was the scroller, the sticky header parked about 48px
     down from the top of the screen — the backdrop's own 4vh padding, plus a
     little more — and everything in that band kept scrolling in full view
     ABOVE the header. Measured: at the top 46 pixels the painted element was
     the card's content, not the header.
     With the modal itself scrolling, the header pins to the modal's own top
     edge and there is no band left for anything to show through. */
  max-height: 92vh; overflow-y: auto;
}
@keyframes modal-in { from { opacity: 0; transform: translateY(10px) scale(.98); } }
.modal.wide { max-width: 960px; }
/* The consultation card carries a 300px history rail beside it, so it needs
   more room than an ordinary wide modal (2026-08-28). */
.modal.wide:has(.cn-split) { max-width: 1240px; }
.modal .modal-head {
  display: flex; align-items: center; gap: 10px; padding: 16px 0 10px;
  /* A line you can actually SEE under the title (Dr Daniel, 2026-09-05:
     "make it more obvious"). */
  border-bottom: 2px solid var(--brand-light, #b98cc4); margin-bottom: 12px;
  position: sticky; top: 0; background: var(--card); z-index: 2;
  /* The 12px below the rule is the head's too: with only a margin there, the
     form scrolled THROUGH it and the type buttons showed sliced in half
     (Dr Daniel, 2026-09-14). */
  box-shadow: 0 12px 0 var(--card);
}
.modal .modal-head h2 { margin: 0; color: var(--brand); flex: 1; font-size: 1.1rem; }
.modal .modal-x {
  width: 34px; height: 34px; min-height: 0; padding: 0; border-radius: 50%;
  /* No fill (Dr Daniel, 2026-08-30) - it colours on hover instead. */
  background: transparent; color: var(--brand); box-shadow: none;
  font-size: 1rem; flex: none;
}
.modal .modal-x:hover { background: var(--bad-bg); color: var(--bad); box-shadow: none; }
.modal .actions {
  display: flex; gap: 9px; justify-content: flex-end; margin-top: 18px; flex-wrap: wrap;
  border-top: 1px solid var(--line); padding-top: 14px;
}
/* A footer row stretches its buttons to a common height, which quietly undid
   `small` - the type and padding shrank but the box did not (Dr Daniel,
   2026-08-30). A small action keeps its own height and centres against the
   full-size ones beside it. */
.modal .actions > button.small { align-self: center; min-height: 34px; }
/* FROZEN action bar (Dr Daniel, 2026-09-06: "freeze this tab" at the
   bottom): the PO popup's buttons stay pinned while the order scrolls;
   Edit/Lock order and SUBMIT FINALISED ride its right end. */
/* The WHOLE row is frozen, on its own solid ground (Dr Daniel, 2026-09-13:
   "The bottom tab why transparent? Pls freeze the whole bottom row") — the
   see-through strip of the day before let the mattress preview scroll through
   the buttons, which read as broken rather than as floating. The bar bleeds
   to the edges of whatever holds it, so nothing passes down either side. */
.actions.po-foot {
  position: sticky; bottom: 0; z-index: 3; align-items: center;
  background: var(--card, #fff);
  margin: 12px -22px 0; padding: 10px 22px;
  box-shadow: 0 -10px 12px -12px rgba(71, 0, 84, .3);
}
/* Each container bleeds by its own padding: the page, the popup page, the
   modal — see .content (20px 22px), body.embed .content (8px 14px) and
   .modal (0 22px 20px). */
body.embed .actions.po-foot { margin: 10px -14px 0; padding: 8px 14px; }
.modal .actions.po-foot {
  bottom: -20px; margin: 12px -22px -20px; padding: 10px 22px 16px;
  border-radius: 0 0 18px 18px;
}

/* "Customer Confirmed?" riding the same row as the submit it gates
   (Dr Daniel, 2026-09-12). Two answers, the standing one filled: amber while
   it waits, green once it is confirmed. */
.po-gate { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.po-gate-q { font-size: .8rem; font-weight: 700; color: var(--muted); }
button.po-yn {
  min-height: 28px; padding: 3px 11px; font-size: .78rem; font-weight: 700;
  background: var(--card); color: var(--muted);
  border: 1.5px solid var(--line); box-shadow: none;
}
button.po-yn:hover:not([disabled]) { background: var(--brand-tint); box-shadow: none; }
button.po-yn.on { background: var(--ok-bg); color: var(--ok); border-color: var(--ok); }
button.po-yn.no.on { background: var(--warn-bg); color: var(--warn); border-color: var(--warn); }
button.po-yn[disabled] { opacity: .6; cursor: default; }
/* The footer holds ONE row (Dr Daniel, 2026-09-12) — tightened rather than
   re-worded, so every button keeps the name staff know it by and they still
   fit side by side, Save changes included. */
.actions.po-foot { gap: 6px; }
.actions.po-foot > * { flex: none; }
.modal .actions.po-foot > button.small { min-height: 30px; padding: 5px 11px; }
.actions.po-foot > button.submit-final.head {
  font-size: .82rem; padding: 8px 14px; margin-left: 4px;
}

/* ---- toast ---- */
.toast {
  position: fixed; bottom: 20px; right: 20px; background: var(--brand-dark); color: #fff;
  padding: 12px 18px; border-radius: 14px; z-index: 99; box-shadow: var(--shadow-lift);
  max-width: 400px; font-size: .9rem; display: flex; gap: 9px; align-items: flex-start;
  animation: toast-in .2s ease-out;
}
@keyframes toast-in { from { opacity: 0; transform: translateY(12px); } }
.toast.err { background: var(--bad); }

/* ---- appointments calendar ---- */
.cal { display: grid; grid-template-columns: 52px repeat(7, 1fr); gap: 1px; background: var(--line);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
.cal .hd { background: var(--brand-tint); font-size: .78rem; font-weight: 700; text-align: center; padding: 7px 2px; color: var(--brand); }
/* The header row freezes while the grid scrolls under it (Dr Daniel,
   2026-08-30). It has to be a SIBLING of the body grid, not its first row: a
   sticky grid item is confined to its own grid area - one row tall - so it can
   never travel, and `.cal`'s own `overflow: hidden` would have trapped it in a
   second way. As a block-level child of the scrolling pane it sticks properly.
   z-index clears the now-line (5) so the names are not crossed out in red as
   the clock hand passes under them. */
.cal-hdrow {
  /* Above ANYTHING a card can wear (Dr Daniel, 2026-09-06: cards were
     riding over the frozen branch names — a busy cluster's cascade z
     (2+column) passed the old 6, and a hovered card's 60 always did).
     The hover pop stays higher still at 400. */
  position: sticky; top: 0; z-index: 100;
  border-bottom: none; border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .10);
}
/* The two halves read as one card: the seam between them carries no border and
   no corner, so only the outside of the pair is rounded. */
.cal-hdrow + .cal { border-top: none; border-radius: 0 0 var(--radius) var(--radius); }
.cal .hd.today { background: var(--brand); color: #fff; }
/* Date bar in branch colours (Dr Daniel, 2026-09-03) — keep in lockstep with
   the .cal-branches .hd palette below. */
.cal .hd.today.br-zp { background: #470054; }
.cal .hd.today.br-8tr { background: #0e7490; }
.cal .hd.today.br-ru { background: #a13d63; }
/* The one-day bar names its branch at the left edge; the date stays centred
   (Dr Daniel, 2026-09-03). */
.cal .hd .hd-daybar { position: relative; display: block; }
.cal .hd .hd-brname {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  font-size: .74rem; font-weight: 600; letter-spacing: .03em;
  white-space: nowrap; opacity: .95;
}
@media (max-width: 700px) { .cal .hd .hd-brname { display: none; } }
.cal .timecol { background: #fff; font-size: .68rem; color: var(--muted); text-align: right; padding: 2px 5px; position: relative; }
/* The hour names the row it starts, so it sits at the TOP of that row, right
   under the line (Dr Daniel, 2026-08-30). 4px rather than flush: at 2px the
   type touched the rule and read as sitting ON it, which is what prompted the
   change - this is the same position, just no longer colliding. Absolute so
   the label cannot add to the row's height. */
.cal .timecol .tc-h {
  position: absolute; right: 5px; top: 4px;
  line-height: 1; white-space: nowrap;
}
.cal .slot { background: #fff; min-height: 28px; position: relative; }
/* Today's column keeps only its side rails — the pale-purple WASH is gone
   (Dr Daniel, 2026-09-07: the wash kept reading as the hover band spilling
   past its 15 minutes; the band itself was always one quarter). */
.cal .slot.today-col { background: #fff; box-shadow: inset 1px 0 0 var(--brand-tint-2), inset -1px 0 0 var(--brand-tint-2); }
/* The HOUR boundary is the strong line, right above its hour label; the
   HALF-hour boundary is the whisper (Dr Daniel, 2026-09-07, third
   ruling: "revert back to the original line thickness and paint, then
   swap the thickness of the hour line and half-hour boundary"). BOTH
   are painted by the row itself over the grid's 1px gap — a bare gap
   pixel collapses at fractional browser zoom, which is why on Dr
   Daniel's screen the hour lines kept vanishing while the painted
   half-hour ones survived, reading as swapped weights. The hour line
   carries the ORIGINAL paint (1px var(--line)); nothing is thicker. */
.cal .slot:not(.h30), .cal .timecol:not(.h30) { box-shadow: 0 -1px 0 0 var(--line); }
.cal .slot.today-col:not(.h30) {
  box-shadow: 0 -1px 0 0 var(--line), inset 1px 0 0 var(--brand-tint-2), inset -1px 0 0 var(--brand-tint-2);
}
/* The half-hour line wears the IDENTICAL dot pattern as the 15-minute
   mid-line above — one shared recipe, so the two can never drift apart
   (Dr Daniel, 2026-09-07, after the KumoDoc example). The gap pixel
   above is blanked white and the dots paint on the cell's own top edge. */
.cal .slot.h30 {
  box-shadow: 0 -1px 0 0 #fff;
  background-image: repeating-linear-gradient(90deg, rgba(233, 226, 236, .8) 0 1px, transparent 1px 4px);
  background-repeat: no-repeat; background-size: 100% 1px; background-position: top left;
}
/* The time rail carries HOUR lines only — sub-hour lines never pass
   through the hour-label column (Dr Daniel, 2026-09-07): the rail's
   half-hour gap pixel is blanked and nothing is drawn in its place. */
.cal .timecol.h30 { box-shadow: 0 -1px 0 0 #fff; }
.cal .slot.h30.today-col {
  box-shadow: 0 -1px 0 0 #fff, inset 1px 0 0 var(--brand-tint-2), inset -1px 0 0 var(--brand-tint-2);
  background-image: repeating-linear-gradient(90deg, rgba(233, 226, 236, .8) 0 1px, transparent 1px 4px);
  background-repeat: no-repeat; background-size: 100% 1px; background-position: top left;
}
/* No whole-slot wash on hover — it merged with the quarter-hour band into
   what read as a 30-minute highlight (Dr Daniel, 2026-09-06: "i want it
   highlight in 15 min"). The hov-top/hov-bottom ::after band alone says
   which quarter the click will book. */
.cal .slot:hover { cursor: pointer; }
/* drag-and-drop landing preview: dashed band on the target quarter-hour */
.cal .slot.drop-top::after, .cal .slot.drop-bottom::after {
  content: attr(data-droptime);
  position: absolute; left: 1px; right: 1px; height: calc(50% - 2px);
  border: 2px dashed var(--brand); border-radius: 6px; background: var(--brand-tint-2);
  color: var(--brand); font-size: .68rem; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
  z-index: 4; pointer-events: none;
}
.cal .slot.drop-top::after { top: 1px; }
.cal .slot.drop-bottom::after { bottom: 1px; }
/* Hovering an empty quarter FILLS it light purple with its time printed
   (Dr Daniel, 2026-09-06, after KumoDoc's blue: "please use light purple
   for us") — was a dashed outline before. */
.cal .slot.hov-top::after, .cal .slot.hov-bottom::after {
  content: attr(data-hovlabel);
  /* Fills the whole quarter — flush edges, square corners, soft vertical
     gradient (Dr Daniel, 2026-09-06). HARD PIXELS, not percentages
     (2026-09-07, after the band read as covering a full slot): a slot is
     28px by design, so the band is EXACTLY 14px and each half anchors by
     its own top offset — no container quirk can stretch it. */
  position: absolute; left: 0; right: 0; height: 14px;
  border: none; border-radius: 0;
  background: linear-gradient(180deg, #f9f4fb 0%, #ecdef4 55%, #f4ebf8 100%);
  /* Light ink, left-aligned — KumoDoc's quiet corner time (Dr Daniel,
     2026-09-06: "make it lighter color word, and align to the left"). */
  color: #b3a8bd; font-size: .62rem; font-weight: 600;
  display: flex; align-items: center; justify-content: flex-start;
  padding: 0 8px; z-index: 1; pointer-events: none;
}
.cal .slot.hov-top::after { top: 0; }
/* Top-anchored too — bottom-anchoring put the band in the wrong place
   whenever a slot box came out taller than its designed 28px. */
.cal .slot.hov-bottom::after { top: 14px; bottom: auto; }
/* KumoDoc-style cards (Dr Daniel, 2026-09-06): WHITE card, the left
   stripe wears the CLINICIAN's colour, the status shows as a small dot —
   and a completed booking greys the whole card while the stripe keeps the
   clinician. (Previously: pastel card = clinician, stripe = status.) */
.cal .appt {
  /* A LIVE booking keeps the previous look — clinician pastel fill,
     status on the left stripe (Dr Daniel, 2026-09-06: "the none complete
     one stays the previous color"); only COMPLETED goes KumoDoc-grey with
     the clinician-coloured stripe. */
  /* Less rounded (Dr Daniel, 2026-09-06). */
  position: absolute; left: 2px; right: 2px; z-index: 2; border-radius: 4px; padding: 2px 5px 2px 8px;
  background: var(--pc-bg, var(--brand-tint)); color: var(--text); font-size: .62rem; line-height: 1.3; overflow: hidden;
  cursor: grab; user-select: none; border: 1px solid var(--pc-line, var(--brand-light)); box-shadow: 0 1px 4px rgba(0,0,0,.14);
}
.cal .appt::before {
  /* Flush to the block's left edge (Dr Daniel, 2026-08-27), hugging the
     rounded corners on its side. Thinner (2026-09-06: "colour strip on
     the left, make it thinner"). */
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 3px;
  border-radius: 4px 0 0 4px; background: var(--status-c, var(--muted));
}
/* Status dot, BOTTOM right (Dr Daniel, 2026-09-07: "all icons put it on
   the right bottom corner") — the hover panel says the word. */
.cal .appt::after {
  /* z 2: the workflow pill (z 1) stretches under this corner as an opaque
     backing — without the lift it painted OVER the status figure
     (Dr Daniel, 2026-09-07: "wheres the customer sillouhette?"). */
  content: ""; position: absolute; bottom: 4px; right: 4px; width: 7px; height: 7px;
  border-radius: 50%; background: var(--status-c, #888); z-index: 2;
}
/* The marker stays a DOT while the visit is only planned (pending,
   confirmed), but becomes a little CUSTOMER figure — filled with the same
   status colour — while a person is the story: arrived, cancelled,
   no-show. A COMPLETED visit drops the figure altogether (Dr Daniel,
   2026-09-07 third pass: "the customer sillouhette to disappear after
   the appointment is completed, so only leave the case note, billing and
   courier"). Same ::after, so the colour keeps riding --status-c; the
   silhouette is a mask, not an image, so it tints for free. */
.cal .appt.s-completed::after { display: none; }
.cal .appt.s-completed .wf-strip { padding-right: 4px; }
.cal .appt.s-arrived::after,
.cal .appt.s-cancelled::after, .cal .appt.s-no_show::after {
  /* Bigger figure (Dr Daniel, 2026-09-07: "make it bigger"). */
  bottom: 2px; right: 2px; width: 12px; height: 12px; border-radius: 0;
  -webkit-mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 12c2.7 0 4.8-2.1 4.8-4.8S14.7 2.4 12 2.4 7.2 4.5 7.2 7.2 9.3 12 12 12zm0 2.4c-3.2 0-9.6 1.6-9.6 4.8v2.4h19.2v-2.4c0-3.2-6.4-4.8-9.6-4.8z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 12c2.7 0 4.8-2.1 4.8-4.8S14.7 2.4 12 2.4 7.2 4.5 7.2 7.2 9.3 12 12 12zm0 2.4c-3.2 0-9.6 1.6-9.6 4.8v2.4h19.2v-2.4c0-3.2-6.4-4.8-9.6-4.8z'/%3E%3C/svg%3E") center / contain no-repeat;
}
/* The visit's paperwork on the chip once the customer has ARRIVED (Dr
   Daniel, 2026-09-07): case note / billing $ / factory order — red means
   still to do, green means done, and the factory icon hides until an
   order exists. The strip lines up LEFT of the arrival figure (or the
   completed dot), which stays exactly where it was. */
.cal .appt .wf-strip {
  /* Bottom-right beside the status figure (Dr Daniel, 2026-09-07: "all
     icons put it on the right bottom corner, so that if it overlaps with
     the details, just hide the details and show only icon") — the pill
     inherits the card's own fill, so any text underneath is simply
     covered rather than bleeding through. */
  position: absolute; bottom: 1px; right: 1px; z-index: 1;
  display: inline-flex; gap: 3px; align-items: center;
  /* The pill runs all the way to the card's edge and reserves the status
     figure's zone inside itself (padding-right), so no text sliver can
     peek between the icons or through the silhouette's transparent
     surroundings (Dr Daniel, 2026-09-07: "Still abit overlap"). */
  background-color: inherit; padding: 2px 16px 2px 4px; border-radius: 4px 0 0 0;
}
.cal .appt .wf-ic { width: 10px; height: 10px; display: inline-block; background: #dc2626; }
.cal .appt .wf-ic.ok { background: #059669; }
.cal .appt .wf-note {
  /* A document with a folded corner and two written lines (Dr Daniel,
     2026-09-07: "do you have better icon?"). */
  -webkit-mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill-rule='evenodd' d='M5 2h9l5 5v15H5z M13 3.2V8h4.8z M8 12h8v1.6H8z M8 15.5h8v1.6H8z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill-rule='evenodd' d='M5 2h9l5 5v15H5z M13 3.2V8h4.8z M8 12h8v1.6H8z M8 15.5h8v1.6H8z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.cal .appt .wf-cash {
  background: none !important; width: auto; min-width: 7px;
  color: #dc2626; font: 900 11px/10px ui-sans-serif, system-ui, sans-serif; text-align: center;
}
.cal .appt .wf-cash.ok { color: #059669; }
.cal .appt .wf-fpo {
  /* A little LORRY (Dr Daniel, 2026-09-07: "FPO go for lorry icon") —
     cargo box, cab and two wheels, in place of the hexagon that read as
     a blob at chip size. */
  -webkit-mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M1 5h13v10H1z M14 8h4l3 3v4h-7z'/%3E%3Ccircle cx='6.5' cy='17.5' r='2.6'/%3E%3Ccircle cx='17.5' cy='17.5' r='2.6'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M1 5h13v10H1z M14 8h4l3 3v4h-7z'/%3E%3Ccircle cx='6.5' cy='17.5' r='2.6'/%3E%3Ccircle cx='17.5' cy='17.5' r='2.6'/%3E%3C/svg%3E") center / contain no-repeat;
}
/* A buried card in a cascade lifts to the front under the mouse. */
.cal .appt:hover { z-index: 60 !important; box-shadow: 0 6px 18px rgba(0, 0, 0, .24); }
/* The landing SHADOW (Dr Daniel, 2026-09-07: "use a grey shadow to show
   where exactly the chip will move to. The original chip remain at its
   location until we unclick") — a flat grey ghost at the chip's own
   height. It snaps INSTANTLY between the legal landing quarters: the
   .07s glide it first shipped with read as lag on a fast drag
   (2026-09-07: "not smooth"). The chip itself never moves until the
   drop commits. */
.drag-shadow {
  position: fixed; z-index: 500; pointer-events: none;
  background: rgba(110, 105, 116, .26);
  box-shadow: inset 0 0 0 1.5px rgba(110, 105, 116, .38);
}
/* While a chip is in hand the other cards are hit-transparent — no hover
   lifts or panels flickering under the pointer mid-drag. */
body.chip-dragging { cursor: grabbing; }
body.chip-dragging .cal .appt { pointer-events: none; }
/* Bottom-edge duration handle (Dr Daniel, 2026-09-06: "drag the chips
   longer or shorter") — an invisible 7px grab strip; a small grip bar
   shows on hover so the affordance is discoverable. */
.cal .appt .rz { position: absolute; left: 0; right: 0; bottom: 0; height: 7px; cursor: ns-resize; }
.cal .appt .rz::after {
  content: ""; position: absolute; left: 50%; transform: translateX(-50%); bottom: 2px;
  width: 26px; height: 3px; border-radius: 2px; background: rgba(36, 18, 40, .22); opacity: 0;
}
.cal .appt:hover .rz::after { opacity: 1; }
.cal .appt.s-completed { background: #f0edf1; color: #6d6575; }
.cal .appt.s-completed::before { background: var(--pc-line, var(--brand-light)); }
.cal .appt.s-completed .t { color: #57505e; }
/* Struck through but SOLID (Dr Daniel, 2026-09-06: "Remove the
   transparent effect for cancel and no show"). */
.cal .appt.s-cancelled, .cal .appt.s-no_show { text-decoration: line-through; }
.cal .appt.blocked {
  /* LIGHT PURPLE hatch now (Dr Daniel, 2026-09-06: "colour make it light
     purple see? Remain the 条纹") — the stripes stay, the sand went.
     Full-bleed band, square corners, hatch edge to edge. */
  background: repeating-linear-gradient(45deg, #f5ecf7, #f5ecf7 7px, #e6d7ee 7px, #e6d7ee 14px);
  /* BORDERLESS (Dr Daniel, 2026-09-07: "I DUWAN GAPS") — the border drew a
     pale seam wherever two blocks met; now back-to-back bands read as one
     continuous hatch. */
  color: #5b4a68; border: none; border-radius: 0;
}
/* No side stripe and no status dot on a block (Dr Daniel, 2026-09-06:
   "Block timing has no status, why still got the pending circle") — it is
   held time, not anyone's booking. */
.cal .appt.blocked::before { display: none; }
.cal .appt.blocked::after { display: none; }
.cal .appt.blocked .blk-line { color: #5b4a68; }
.cal .appt .t {
  font-weight: 700; font-size: .66rem; padding-right: 10px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cal .appt .rem {
  font-size: .58rem; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cal .appt .ap-purpose {
  font-size: .58rem; font-style: italic; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cal .appt .ap-meta {
  font-size: .58rem; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* The floating hover panel (Dr Daniel, 2026-09-06, after KumoDoc): status
   banner on top, then the summarised booking. Pointer-events off — it
   informs, it never traps the mouse. */
.appt-pop {
  position: fixed; z-index: 400; width: 250px; background: #fff;
  border-radius: 12px; box-shadow: 0 14px 40px rgba(0, 0, 0, .3);
  overflow: hidden; pointer-events: none;
  /* A soft pop on arrival (Dr Daniel, 2026-09-06: "abit of animation"). */
  animation: appt-pop-in .16s ease-out;
}
@keyframes appt-pop-in {
  from { opacity: 0; transform: translateY(6px) scale(.96); }
}
.appt-pop .ap-banner {
  color: #fff; font-weight: 800; text-align: center; font-size: .72rem;
  letter-spacing: .08em; padding: 5px 8px;
}
.appt-pop .ap-body { padding: 10px 12px; font-size: .78rem; }
.appt-pop .ap-name { font-weight: 800; font-size: .92rem; margin-bottom: 1px; }
.appt-pop .ap-sub { color: var(--muted); font-style: italic; margin-bottom: 6px; }
.appt-pop .ap-line { margin: 2px 0; }
.appt-pop .ap-remarks { margin-top: 6px; background: #f7f4f9; border-radius: 8px; padding: 6px 8px; font-size: .74rem; }
.appt-pop .ap-muted { color: var(--muted); font-size: .7rem; margin-top: 6px; }
/* A block's one line IS its reason (Dr Daniel, 2026-09-01): sized to stay
   readable inside even a 15-minute sliver, so nobody has to hover to learn
   why the time is held. The padding squeezes with it. */
.cal .appt.blocked { padding: 0 4px 0 8px; }
.cal .appt .blk-line {
  font-size: .58rem; font-weight: 700; line-height: 10px; color: #5a5462;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The quarter-hour line (Dr Daniel, 2026-09-01: "change the time interval
   line from 30mins to 15 mins") — a dashed mid-line inside every half-hour
   slot, fainter than the solid line that starts it. Behind the cards, and
   never a click target of its own. */
.cal .slot::before {
  /* The SAME dot pattern as the half-hour line below — one shared recipe
     (Dr Daniel, 2026-09-07: "the half hour line and 15min lines got same
     dashing? U sure?"), fine dots after the KumoDoc example. */
  content: ""; position: absolute; left: 0; right: 0; top: 50%; z-index: 0; height: 1px;
  background-image: repeating-linear-gradient(90deg, rgba(233, 226, 236, .8) 0 1px, transparent 1px 4px);
  pointer-events: none;
}
/* Who is working at each branch, under its name in the ALL view. */
.cal .hd .hd-staff { display: flex; flex-wrap: wrap; gap: 3px; justify-content: center; margin-top: 3px; }
.cal .hd .hd-who {
  font-size: .62rem; font-weight: 700; padding: 1px 7px; border-radius: 999px;
  background: var(--pc-bg); border: 1px solid var(--pc-line);
  /* The branch banner writes in white; these chips are pale, so they must set
     their own dark ink or the names vanish into the pastel (Dr Daniel,
     2026-09-01: "hard to read because of the color"). */
  color: #241b30;
  /* Clickable filters now (Dr Daniel, 2026-09-06) — buttons, so the
     global button chrome is stripped here. */
  min-height: 0; box-shadow: none; cursor: pointer;
}
.cal .hd .hd-who.on { box-shadow: inset 0 0 0 1.5px #241b30; font-weight: 800; }
.cal .hd .hd-clear {
  background: none; border: none; box-shadow: none; min-height: 0;
  padding: 1px 6px; font-size: .6rem; font-weight: 700; cursor: pointer;
  color: inherit; opacity: .85;
}
.cal .hd .hd-clear:hover { opacity: 1; background: none; box-shadow: none; }
/* Tight above and below (Dr Daniel, 2026-09-06). */
.cal-legend { display: flex; gap: 6px; flex-wrap: wrap; margin: 3px 0 0; align-items: center; }
.cal-legend .chip {
  display: inline-flex; align-items: center; gap: 5px; font-size: .72rem; font-weight: 700;
  padding: 2px 10px; border-radius: 999px; background: var(--pc-bg); border: 1px solid var(--pc-line);
}
.cal-legend .dot { width: 10px; height: 10px; border-radius: 3px; background: var(--status-c); }
/* The chips FILTER (Dr Daniel, 2026-09-06): picked chips fill dark; the
   matching bookings lift while every other card fades. */
/* Buttons inherit the global white button ink — these chips must say
   their words in dark text (Dr Daniel, 2026-09-06: "Where are the words"). */
.cal-legend .chip.st-filter { cursor: pointer; min-height: 0; box-shadow: none; color: var(--text, #2a1f2e); }
.cal-legend .chip.st-filter:hover { border-color: var(--brand); }
.cal-legend .chip.st-filter.on { background: #372f3d; border-color: #372f3d; color: #fff; }
/* The Clear word: no fill, just quiet ink (Dr Daniel, 2026-09-06). Unscoped
   from the legend the same day — the Order Form range picker says Clear
   the same way. */
.st-clear {
  background: none; border: none; box-shadow: none; min-height: 0;
  padding: 2px 8px; font-size: .72rem; font-weight: 700; cursor: pointer;
  color: var(--muted, #79707f);
}
.st-clear:hover { color: var(--brand); background: none; box-shadow: none; }
/* Today wears the brand ink, Clear stays quiet — the pair sits at the foot
   of every collapsible calendar (Dr Daniel, 2026-09-06). */
.st-clear.st-today { color: var(--brand, #470054); }

/* Every native date field opens the flat LUNOX picker instead of the
   browser's own (Dr Daniel, 2026-09-06: "why no follow the collapsible
   calendar…") — so the built-in calendar icon goes. */
input[type="date"]::-webkit-calendar-picker-indicator { display: none; }
/* The popup's title is a BUTTON that drills up: days → years → months
   ("Pls let us choose year first, then month, then date"). */
.mc-title-btn {
  background: none; border: 0; box-shadow: none; min-height: 0;
  padding: 2px 8px; cursor: pointer; border-radius: 8px;
}
.mc-title-btn:hover { background: var(--brand-tint); color: var(--brand); box-shadow: none; }
/* Year and month pages: 12 roomy cells, four to a row. */
.date-pop .mc-grid.mc-pick4 { grid-template-columns: repeat(4, 1fr); }
.date-pop .mc-pick4 .mc-day { height: 34px; border-radius: 10px; font-size: .78rem; }
.cal .appt.dim-status { opacity: .22; }
/* A dimmed ghost stacked IN FRONT of a focused card would wash it out —
   its translucent white blends over the card behind (Dr Daniel, 2026-09-06:
   "Why wong choo keong chip semi transparent?"). Ghosts drop to the back
   of the cascade; hover still lifts one for a peek. */
.cal .appt.dim-status:not(:hover) { z-index: 1 !important; }
.cal .appt.focus-status { box-shadow: 0 5px 16px rgba(0, 0, 0, .3); }
.cal-legend .chip.filtered { background: #fff7e6; border-color: #e0b473; color: #8a5a00; font-weight: 700; }

/* Month grid and practitioner filter, always on screen — the date picker used
   to be a dropdown and the practitioner chips were only a legend, so neither
   could be acted on without hunting (Dr Daniel, 2026-08-24). */
/* The calendar's controls live in a rail down the left and stay put while the
   grid scrolls. Across the top they scrolled away exactly when they were needed
   and pushed the schedule off the first screen (Dr Daniel, 2026-08-24). */
.cal-layout { display: flex; gap: 16px; align-items: flex-start; }
/* The rail costs the grid 224px, so the calendar page drops the 1320px reading
   width every other screen uses — a week of columns needs every pixel. */
.content:has(.cal-layout) { max-width: none; }
.cal-rail {
  flex: 0 0 208px; width: 208px;
  /* 59px clears the sticky topbar; the rail scrolls internally if it ever
     outgrows the viewport, so a long practitioner list can never be cut off. */
  position: sticky; top: 59px; max-height: calc(100vh - 75px); overflow-y: auto;
  display: flex; flex-direction: column; gap: 10px; padding-bottom: 4px;
}
.cal-main { flex: 1 1 auto; min-width: 0; }
.rail-primary { width: 100%; }
.rail-views { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
.rail-duo { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
.rail-duo button { width: 100%; padding: 7px 4px; font-size: .8rem; min-height: 0; }
.rail-views button { width: 100%; padding: 7px 4px; font-size: .8rem; min-height: 0; }
.rail-actions { display: flex; flex-direction: column; gap: 5px; }
.rail-actions button { width: 100%; padding: 7px 10px; font-size: .8rem; min-height: 0; text-align: left; }
.cal-rail .cal-legend { margin: 2px 0 0; gap: 4px; }
.cal-rail .cal-legend .chip { font-size: .64rem; padding: 1px 8px; }
.cal-rail .cal-legend .dot { width: 8px; height: 8px; }
.cal-rail .pract-toggles { gap: 4px; }
/* Narrow screens: the rail becomes a normal block above the grid. A sticky
   sidebar on a phone would eat the screen it is meant to save. */
@media (max-width: 900px) {
  .cal-layout { display: block; }
  .cal-rail { position: static; width: auto; max-height: none; flex-direction: row;
    flex-wrap: wrap; align-items: center; margin-bottom: 10px; }
  .rail-primary, .rail-views, .rail-actions { width: auto; }
  .rail-views { display: flex; }
  .rail-actions { flex-direction: row; }
}
.mini-cal {
  background: #fff; border: 1px solid var(--line);
  border-radius: var(--radius); padding: 5px 7px 6px; box-shadow: var(--shadow);
}
.mc-head { display: flex; align-items: center; justify-content: space-between; gap: 4px; margin-bottom: 2px; }
/* The second month carries no arrows — one window, moved by the pair above. */
.mc-head.mc-next { justify-content: center; margin-top: 7px; padding-top: 6px; border-top: 1px solid var(--line); }
/* GNOME-flat mini calendar (Dr Daniel, 2026-09-06: "I want to look exactly
   the same. Except the chosen colour is lunox's purple.") — plain dark
   title, quiet grey arrows, bare numbers, and ONE filled purple circle on
   the chosen day. The global button box-shadow was what made every cell a
   floating white pill, so each piece flattens it explicitly. */
.mc-title { font-size: .72rem; font-weight: 700; color: var(--text); }
/* These are all <button>, so they inherit the 42px touch-target minimum meant
   for real buttons — which is what was padding the whole block out. The
   calendar is a front-desk screen, so a 22px day cell is the right size here. */
.mc-nav {
  background: none; border: 0; box-shadow: none; color: var(--muted);
  font-size: .95rem; line-height: 1;
  padding: 1px 6px; border-radius: 999px; cursor: pointer; min-height: 0;
}
.mc-nav:hover { background: var(--brand-tint); color: var(--brand); box-shadow: none; }
.mc-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
.mc-wd { font-size: .58rem; color: var(--muted); text-align: center; font-weight: 600; }
.mc-day {
  height: 22px; min-height: 0; border: 0; background: none; box-shadow: none;
  border-radius: 999px; cursor: pointer;
  font-size: .68rem; font-weight: 500; color: var(--text); padding: 0;
}
.mc-day:hover { background: var(--brand-tint); box-shadow: none; }
.mc-day.pad { visibility: hidden; }
/* The days currently on the schedule — the one filled circle. */
.mc-day.sel { background: var(--brand); color: #fff; font-weight: 700; }
.mc-day.sel:hover { background: var(--brand); }
/* Today is quiet when not chosen: purple digits, no ring. */
.mc-day.today { color: var(--brand); font-weight: 800; box-shadow: none; }
.mc-day.today.sel { color: #fff; box-shadow: none; }

/* Working list (2026-08-27): a tidy aligned column — one full-width row per
   clinician, ticks in a fixed gutter, the head row carrying the label and the
   Hide/Show-all pill. */
.pract-toggles { display: flex; gap: 3px; flex-direction: column; align-items: stretch; }
.pt-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 2px; }
.pt-label { font-size: .6rem; font-weight: 800; color: var(--muted); letter-spacing: .08em; text-transform: uppercase; }
.pt-chip {
  display: flex; align-items: center; gap: 6px; font-size: .72rem; font-weight: 700;
  padding: 4px 10px; border-radius: 8px; cursor: pointer; line-height: 1.4; min-height: 0;
  width: 100%; justify-content: flex-start; text-align: left;
  background: var(--pc-bg); border: 1px solid var(--pc-line); color: var(--text); box-shadow: none;
}
.pt-chip:hover { box-shadow: none; filter: brightness(.97); }
.pt-chip .pt-tick { font-size: .62rem; width: 1em; display: inline-block; flex: none; }
/* Hidden reads as switched off, not as a different clinician. */
.pt-chip.off { background: #fff; border-color: var(--line); color: var(--muted); text-decoration: line-through; }
.pt-all {
  background: var(--brand-tint); border: 0; color: var(--brand); font-size: .64rem;
  font-weight: 800; border-radius: 999px; cursor: pointer; padding: 2px 10px;
  min-height: 0; box-shadow: none; letter-spacing: .02em;
}
.pt-all:hover { background: var(--brand-tint-2); box-shadow: none; }

/* ---- mattress illustration (side view) ---- */
.stack { max-width: 520px; margin: 10px 0; }
.stack .cap {
  font-size: .74rem; color: var(--muted); display: flex; justify-content: space-between;
  padding: 2px 10px; font-weight: 600; letter-spacing: .03em;
}
.stack .mattress {
  position: relative; border: 3px solid #7a6a80; border-radius: 26px; overflow: hidden;
  background: #fff; box-shadow: 0 14px 24px -12px rgba(71, 0, 84, .35);
}
/* The sewn-on side tag sits on the RIGHT of the base band (Dr Daniel,
   2026-09-06) — a real element now, so a split build carries exactly one. */
.stack .label-tag {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  background: #fff; border: 2px solid #7a6a80;
  border-radius: 6px; font-size: .58rem; font-weight: 700; color: #7a6a80;
  padding: 3px 8px; letter-spacing: .1em; pointer-events: none;
}
.stack .layer {
  display: flex; min-height: 40px; font-size: .8rem; text-align: center;
  align-items: stretch; border-top: 2px solid rgba(122, 106, 128, .55);
}
.stack .layer:first-child { border-top: none; }
.stack .layer.fabric {
  min-height: 44px; font-weight: 600;
  background:
    repeating-linear-gradient(45deg, rgba(122, 106, 128, .16) 0 1.5px, transparent 1.5px 22px),
    repeating-linear-gradient(-45deg, rgba(122, 106, 128, .16) 0 1.5px, transparent 1.5px 22px),
    linear-gradient(#ffffff, var(--fab, #f2ecf5));
  border-bottom: 2px dashed rgba(122, 106, 128, .7);
}
/* Each fabric quilts in its own tint, matching its button icon (Dr Daniel,
   2026-09-02): Nanofrost ❄ ice blue, Tencel 🍃 leaf green, Proneem 🛡 olive;
   the hexa topper skins follow the same families. */
.stack .layer.fabric.fab-nanofrost { --fab: #d9e9f7; }
/* Tencel greyish-white, ProNeem green (Dr Daniel, 2026-09-03). */
.stack .layer.fabric.fab-tencel { --fab: #eceeeb; }
.stack .layer.fabric.fab-proneem { --fab: #cfe6cc; }
.stack .layer.fabric.fab-nanohexa { --fab: #d9e9f7; }
.stack .layer.fabric.fab-halfnanohexa { --fab: #e5eff8; }
.stack .layer.fabric.fab-tencelhexa { --fab: #eceeeb; }
/* Topper cover: a thin skin, not a layer (2026-09-02). */
.stack .layer.fabric.thin { min-height: 20px; font-size: .68rem; }
/* A topper is a thin slab, not a capsule (Dr Daniel, 2026-09-02: the wide
   pill looked "out of proportion"): narrower drawing, square-ish corners,
   lighter shell, and a slimmer band per layer. */
.stack.topper { max-width: 380px; }
.stack.topper .layer { min-height: 34px; }
.stack.topper .mattress {
  border-radius: 10px; border-width: 2px;
  box-shadow: 0 8px 14px -10px rgba(71, 0, 84, .3);
}
.stack.topper .label-tag { padding: 2px 6px; font-size: .54rem; right: 8px; top: 42%; transform: none; }
.stack .layer.base {
  min-height: 74px; font-weight: 600; border-top: 2px solid #7a6a80;
}
/* The base as it is actually upholstered: LUNOX purple, logo on the right,
   the customer's embroidery on the left (Dr Daniel, 2026-08-23). */
.stack .layer.base.brandbase {
  position: relative; border-top: 2px solid #2e0036; color: #fff;
}
/* Keep the centre label clear of the embroidery (left) and logo (right). */
.stack .layer.base.brandbase .side { color: #fff; box-shadow: none; padding: 6px 76px; }
.stack .layer.base.brandbase .side small { color: rgba(255, 255, 255, .72); }
.stack .layer.base.brandbase .baselogo,
.stack .layer.base.brandbase .baseemb {
  position: absolute; top: 50%; transform: translateY(-50%);
  font-size: .62rem; font-weight: 800; letter-spacing: .18em; color: #fff;
  text-transform: uppercase; opacity: .92; pointer-events: none;
}
.stack .layer.base.brandbase .baselogo { right: 12px; }
/* Embroidery is stitched, so it reads as thread rather than print. */
.stack .layer.base.brandbase .baseemb {
  left: 12px; letter-spacing: .1em; font-style: italic; opacity: 1;
  text-shadow: 0 1px 0 rgba(0, 0, 0, .45);
}
.stack .layer.base.brandbase.spring {
  background:
    repeating-linear-gradient(90deg, transparent 0 5px, rgba(255, 255, 255, .16) 5px 7px, transparent 7px 22px),
    repeating-linear-gradient(180deg, transparent 0 12px, rgba(255, 255, 255, .08) 12px 14px),
    linear-gradient(#5b1668, var(--brand));
}
.stack .layer.base.brandbase.foam {
  background:
    radial-gradient(rgba(255, 255, 255, .13) 1.5px, transparent 1.5px) 0 0 / 14px 14px,
    linear-gradient(#5b1668, var(--brand));
}
/* Thickness gauge beside the stack: a tick per layer plus the total. */
.stack-row { display: flex; align-items: stretch; gap: 8px; }
.stack-row .mattress { flex: 1; }
.gauge {
  position: relative; width: 34px; display: flex; flex-direction: column;
  justify-content: space-around; padding: 6px 0;
  border-left: 2px solid var(--line);
}
.gauge .tick { display: block; height: 1px; background: var(--line); margin-left: -2px; width: 9px; }
.gauge .gauge-total {
  position: absolute; right: -2px; top: 50%;
  transform: translateY(-50%) rotate(90deg); transform-origin: center;
  font-size: .62rem; font-weight: 700; color: var(--muted); white-space: nowrap;
}
.stack .side {
  flex: 1; display: flex; align-items: center; justify-content: center; padding: 6px 8px;
  box-shadow: inset 0 -8px 10px -8px rgba(0, 0, 0, .18), inset 0 6px 8px -6px rgba(255, 255, 255, .8);
}
.stack .side .matname { font-weight: 600; }
.stack .side small { display: block; font-weight: 400; color: rgba(34, 24, 38, .6); font-size: .68rem; }
.stack .layer .side + .side { border-left: 3px dashed rgba(122, 106, 128, .8); }
.stack .m-latex_topper, .stack .m-latex { background-color: var(--mat-latex); }
.stack .m-memory_foam { background-color: var(--mat-memory); }
.stack .m-soft_foam { background-color: var(--mat-soft); }
.stack .m-medium_foam { background-color: var(--mat-medium); }
.stack .m-hard_foam { background-color: var(--mat-hard); }
.stack .dims { text-align: center; font-size: .76rem; color: var(--muted); margin-top: 6px; }
.stack .dims .rule {
  height: 8px; border-left: 2px solid var(--muted); border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted); margin: 2px 24px 3px; border-radius: 0 0 4px 4px;
}
.superseded-banner {
  background: var(--bad); color: #fff; font-weight: 700; text-align: center;
  padding: 7px; border-radius: 10px; letter-spacing: .08em; margin: 8px 0;
}

/* ---- production status stepper ---- */
/* One row, always (Dr Daniel, 2026-08-29). Six stages wrapped "Delivered" onto
   a line of its own, which reads as two separate journeys rather than one.
   The connectors give up their width first, then the labels ellipsis (their
   full name is on hover), and the strip scrolls only as a last resort. */
.steps {
  display: flex; flex-wrap: nowrap; gap: 0; margin: 10px 0 14px;
  overflow-x: auto; scrollbar-width: thin;
}
.step { display: flex; align-items: center; gap: 7px; padding: 4px 0; min-width: 0; flex: 0 1 auto; }
.step .dot {
  width: 30px; height: 30px; border-radius: 50%; display: inline-flex; align-items: center;
  justify-content: center; font-size: .78rem; font-weight: 700; flex: none;
  background: #fff; border: 2px solid var(--line); color: var(--muted);
}
/* Nothing that carries meaning shrinks: the dot holds the number and the label
   holds the stage name. Clipping "Draft" to "Dra…" to save four pixels looks
   broken, so ALL the slack is taken out of the connectors instead, and when
   even that is not enough the strip scrolls rather than truncating. */
.step .lbl { font-size: .78rem; color: var(--muted); white-space: nowrap; font-weight: 600; flex: 0 0 auto; }
.step .dot { flex: none; }
.step::after {
  content: ""; height: 2px; background: var(--line);
  margin: 0 7px; flex: 0 1 26px; min-width: 6px;
}
.step:last-child::after { display: none; }
.step.done .dot { background: var(--brand); border-color: var(--brand); color: #fff; }
.step.done .lbl { color: var(--text); }
.step.now .dot { border-color: var(--brand); color: var(--brand); box-shadow: 0 0 0 4px var(--brand-tint-2); }
.step.now .lbl { color: var(--brand); }

/* ---- definition rows ---- */
.kv { display: grid; grid-template-columns: 130px 1fr; gap: 5px 12px; font-size: .9rem; margin: 8px 0; }
.kv dt { color: var(--muted); font-size: .78rem; padding-top: 2px; font-weight: 600; }
.kv dd { margin: 0; }

/* ---- Custom Mochi assessment ---- */
.mochi-wrap { position: relative; }
.mochi-bg { position: absolute; inset: 0; color: var(--brand-tint); pointer-events: none; }
.mochi-bg svg { width: 100%; height: 100%; display: block; }
.mochi-pa {
  position: relative;
  display: grid; grid-template-columns: minmax(120px, 1fr) minmax(180px, 2fr) minmax(120px, 1fr);
  grid-template-rows: auto auto auto; gap: 8px;
  padding: 26px 34px 40px;
}
.pocket {
  background: rgba(255, 255, 255, .7); border: 2px dashed var(--brand-tint-2);
  border-radius: 16px; padding: 10px 8px; text-align: center;
}
.pocket.d { grid-row: span 3; display: flex; flex-direction: column; justify-content: center; }
/* outer pockets follow the pillow contour */
.pocket.p-d1 { border-radius: 60px 14px 14px 60px; padding-left: 14px; }
.pocket.p-d2 { border-radius: 14px 60px 60px 14px; padding-right: 14px; }
.pocket.p-b { border-radius: 30px 30px 12px 12px; }
.pocket.p-c { border-radius: 12px 12px 38px 38px; }
.pocket h4 { margin: 0 0 2px; font-size: .74rem; letter-spacing: .07em; text-transform: uppercase; color: var(--brand); }
.pocket .sub { font-size: .68rem; color: var(--muted); margin-bottom: 6px; }
.pocket .gram { width: 84px; text-align: center; margin: 0 auto; }
.pocket.hot { border-color: var(--brand); border-style: solid; }
.layerstack { display: flex; flex-direction: column; gap: 3px; margin: 4px 0; }
.layerstack .lay {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  background: #fff; border: 1.5px solid var(--brand-tint-2); border-radius: 9px;
  padding: 4px 7px 4px 11px; font-size: .76rem; font-weight: 700; color: var(--brand);
  min-height: 32px; text-align: left;
}
.layerstack .lay.thin { min-height: 21px; padding-top: 1px; padding-bottom: 1px; }
.layerstack .lay.mf { background: var(--brand-tint); }
.layerstack .lay button {
  min-height: 0; padding: 0; width: 20px; height: 20px; border-radius: 50%; flex: none;
  background: transparent; color: var(--bad); box-shadow: none; font-size: .68rem;
}
.stacksum { font-size: .72rem; color: var(--muted); font-weight: 700; margin: 3px 0 5px; }
.lay[draggable="true"] { cursor: grab; }
.lay .grip { color: var(--brand-tint-2); font-size: .8rem; flex: none; letter-spacing: -2px; }
.lay.dragging { opacity: .45; }
.lay.dragtarget { outline: 2px dashed var(--brand); outline-offset: 2px; }
select.addpad {
  min-height: 32px; padding: 4px 28px 4px 12px; font-size: .78rem; width: auto;
  border-radius: 10px; background: var(--card); border: 1.5px solid var(--line);
  color: var(--text); font-weight: 600; cursor: pointer; margin-top: 2px;
}
select.addpad:hover { background: var(--brand-tint); }
/* Aroma chips: each scent its own calm tone; picked = its tone, ink and a
   touch of weight (2026-09-02). */
/* Same state language as the layering rows above (2026-09-02): resting =
   neutral grey, chosen = a DEEP fill of the scent's own colour. */
button.aroma-chip {
  background: var(--neutral-bg); color: var(--muted);
  border: none; border-radius: 10px;
  padding: 6px 10px; font-size: .8rem; font-weight: 600; box-shadow: none;
  min-height: 32px; flex: 1; min-width: 110px;
}
button.aroma-chip:hover { background: var(--tone-bg, #eceef2); color: var(--tone-fg, #55606f); }
button.aroma-chip.picked {
  background: var(--tone-deep, #d9dde3); color: var(--tone-fg, #55606f); font-weight: 800;
}
button.aroma-chip:disabled { opacity: .45; cursor: not-allowed; }
button.aroma-chip.aroma-none {
  display: block; width: 100%; margin-top: 6px;
  min-height: 24px; padding: 3px 10px; font-size: .74rem; flex: none;
}
.optrow { display: flex; gap: 8px; flex-wrap: wrap; }
.optrow button { flex: 1; min-width: 110px; }
/* Chosen = calm lavender with brand ink, not the solid block (2026-09-02). */
/* :not(.aroma-chip): the aroma chips share the row container but carry
   their own per-scent colours — this rule was painting them all lavender
   and eating their picked state (found 2026-09-02). */
.optrow button:not(.off):not(.aroma-chip) {
  background: #e9d5f0; color: var(--brand);
  box-shadow: none; font-weight: 700;
}
.optrow button:not(.off):not(.aroma-chip):hover { background: #e0c6e9; }
.optrow button.off { background: var(--neutral-bg); color: var(--muted); box-shadow: none; }
/* Which kind of consultation this is, on the case note card: four pills that
   used to stretch the full width because .optrow buttons are flex:1, which
   made a setting look like the point of the page (Dr Daniel, 2026-09-14:
   "it's messy"). They now take the room their own words need. */
.cn-typebar { gap: 6px; margin: 0 0 14px; }
.cn-typebar button { flex: 0 0 auto; min-width: 0; min-height: 30px; padding: 4px 15px; font-size: .82rem; }
/* Beside the customer's name there is less room than there was across the
   body, so the pills tighten rather than wrap the title row onto two lines. */
.cn-headtools .cn-typebar button { padding: 4px 11px; font-size: .78rem; white-space: nowrap; }
.optrow button.off:hover { background: var(--brand-tint); color: var(--brand); }
.hline { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin: 7px 0; }
.hline .v { font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ok); }
.hline .v.over { color: var(--bad); }
.guide-panel { background: var(--brand-tint); border-radius: 14px; padding: 12px 14px; font-size: .86rem; }
.guide-panel dt { color: var(--brand); font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; font-weight: 700; margin-top: 8px; }
.guide-panel dd { margin: 1px 0 0; font-weight: 600; }

/* ---- phone view (mobile.html) ---- */
.mob { max-width: 680px; margin: 0 auto; padding: 0 12px 30px; }
.mob-head {
  position: sticky; top: 0; z-index: 20; background: var(--bg);
  padding: 8px 0 6px; border-bottom: 1px solid var(--line); margin-bottom: 8px;
}
/* Every millimetre the header keeps is a half-hour the grid loses, so the
   brand line is as small as it can be and still be itself (2026-09-14). */
.mob-head .brand { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.mob-head .brand img { height: 22px; width: auto; }
.mob-head .brand button { min-height: 32px; padding: 4px 12px; }
.mob-head .brand .who { margin-left: auto; font-size: .78rem; color: var(--muted); font-weight: 700; }
/* The phone's branch toggle (Dr Daniel, 2026-09-01) — same chips as the
   desktop sidebar, a thumb-height row of their own under the logo. */
.mob-branch { display: flex; gap: 6px; margin: 2px 0 6px; }
.mob-branch .br-chip { min-height: 32px; padding: 3px 12px; font-size: .82rem; }
.mob-daynav { display: flex; gap: 6px; align-items: center; }
.mob-daynav button { min-height: 36px; padding: 5px 12px; }
.mob-daynav input[type="date"] { flex: 1; min-height: 40px; }
.mob-appt {
  display: block; width: 100%; text-align: left; background: var(--pc-bg, var(--card));
  color: var(--text); border: 1px solid var(--pc-line, var(--line)); border-radius: 14px;
  padding: 10px 12px 10px 14px; margin-bottom: 8px; position: relative; overflow: hidden;
  box-shadow: var(--shadow); min-height: 0; font-weight: 500; font-size: .9rem;
}
.mob-appt::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 5px;
  background: var(--status-c, var(--muted));
}
.mob-appt .l1 { display: flex; gap: 8px; align-items: baseline; font-weight: 800; }
.mob-appt .l1 .time { font-variant-numeric: tabular-nums; }
.mob-appt .l2 { font-size: .78rem; color: var(--muted); margin-top: 1px; }
.mob-appt.blocked { background: repeating-linear-gradient(45deg, #eceaee, #eceaee 7px, #e0dde4 7px, #e0dde4 14px); }
.mob-fabbar {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 25;
  display: flex; gap: 8px; padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  background: var(--bg); border-top: 1px solid var(--line);
}
.mob-fabbar button { flex: 1; }
.mob .card { padding: 12px 14px; }
.mob .statusrow { display: flex; gap: 6px; flex-wrap: wrap; }
.mob .statusrow button { flex: 1 1 30%; min-height: 44px; box-shadow: none; }
.mob-photos img { max-height: 120px; max-width: 46vw; border-radius: 10px; display: block; }
.mob-week { display: flex; gap: 2px; }
.mob-week .tcol { position: relative; width: 20px; flex: none; }
.mob-week .tcol span {
  position: absolute; right: 2px; font-size: .58rem; color: var(--muted); font-weight: 700;
}
.mob-wcol { flex: 1; min-width: 0; }
.mob-whd {
  display: block; width: 100%; min-height: 0; padding: 3px 0 4px; border-radius: 8px;
  background: var(--brand-tint); color: var(--brand); font-size: .64rem; font-weight: 800;
  box-shadow: none; line-height: 1.1; text-align: center;
}
.mob-whd span { display: block; font-size: .72rem; }
.mob-whd.today { background: var(--brand); color: #fff; }
.mob-wbody {
  position: relative; margin-top: 2px; background: #fff; border-radius: 8px; cursor: pointer;
  background-image: repeating-linear-gradient(to bottom, transparent 0 47px, var(--line) 47px 48px);
}
.mob-wapp {
  position: absolute; left: 1px; right: 1px; min-height: 0; padding: 0 2px; overflow: hidden;
  border-radius: 4px; background: var(--pc-bg); color: var(--text); font-size: .56rem;
  font-weight: 800; text-align: left; box-shadow: none;
  border: 1px solid var(--pc-line); border-left: 3px solid var(--status-c);
}
.mob-wapp.s-cancelled, .mob-wapp.s-no_show { opacity: .5; text-decoration: line-through; }
.mob-wapp.blocked { background: repeating-linear-gradient(45deg, #eceaee, #eceaee 5px, #e0dde4 5px, #e0dde4 10px); color: var(--muted); }

/* ---- phone: five destinations on a thumb bar (Dr Daniel, 2026-09-14) ----
   Dashboard, Calendar, Notes, Mochi, Mattress. The bar is the last thing on
   the screen, so everything above it keeps clear of both it and the action
   row that floats over it. */
.mob-tabs {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 30;
  display: flex; background: var(--card); border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom);
  box-shadow: 0 -8px 20px -14px rgba(71, 0, 84, .4);
}
.mob-tab {
  flex: 1; min-height: 54px; border-radius: 0; background: none; color: var(--muted);
  box-shadow: none; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 1px; padding: 6px 2px; font-weight: 700;
}
.mob-tab:hover { background: var(--brand-tint); box-shadow: none; }
.mob-tab .ic { font-size: 1.15rem; line-height: 1; }
.mob-tab .lb { font-size: .62rem; letter-spacing: .01em; }
.mob-tab.on { color: var(--brand); background: var(--brand-tint); }
/* The action row sits ON TOP of the tab bar, not over the page's last line. */
.mob-fabbar { bottom: calc(54px + env(safe-area-inset-bottom)); }
.mob-fabbar:empty { display: none; }
.mob-body { padding-bottom: 124px; }
.mob-h { margin: 2px 0 8px; font-size: 1.15rem; }
.mob-h3 { margin: 0 0 6px; font-size: .95rem; }
.mob-line { display: flex; justify-content: space-between; gap: 10px; padding: 4px 0; font-size: .9rem; }
.mob-line + .mob-line { border-top: 1px solid var(--line); }
.mob-kpis { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px; }
.mob-kpi { padding: 10px 12px; }
.mob-seg { display: flex; gap: 6px; }
.mob-seg button { flex: 1; min-height: 38px; font-size: .8rem; }
/* A row in a phone list: a line, a quieter line under it, the whole thing a
   target big enough for a thumb. */
.mob-row {
  display: block; width: 100%; text-align: left; background: var(--card); color: var(--text);
  border: 1px solid var(--line); border-radius: 12px; padding: 10px 12px; margin-bottom: 7px;
  box-shadow: none; min-height: 0; font-weight: 500;
}
.mob-row .l1 { display: flex; align-items: center; gap: 8px; font-weight: 800; font-size: .92rem; }
.mob-row .l2 { font-size: .76rem; color: var(--muted); margin-top: 2px; }
/* Seven days as bars — figures above, day letters below, today picked out. */
.mob-bars { display: flex; align-items: flex-end; gap: 3px; height: 110px; }
/* min-width: 0 or a long figure widens its own column and pushes the week
   out of the card (Dr Daniel, 2026-09-14). */
.mob-bar { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 2px; }
.mob-bar .bv {
  font-size: .55rem; color: var(--muted); font-weight: 700;
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mob-bar .bfill { width: 100%; border-radius: 5px 5px 0 0; background: var(--brand-tint-2); }
.mob-bar .bfill.now { background: var(--brand); }
.mob-bar .bl { font-size: .62rem; color: var(--muted); font-weight: 700; }

/* ---- phone calendar: the desktop grid, narrowed ------------------------
   One column per clinician on duty, time down the side, a booking drawn
   where it actually sits. Whitespace in a column IS availability — the flat
   list this replaced could not show that at all. */
.mcal-title { display: flex; align-items: baseline; gap: 8px; margin: 2px 0 6px; flex-wrap: wrap; }
.mcal-title h2 { margin: 0; font-size: 1.1rem; }
.mcal-title .muted { font-size: .78rem; }
.mcal-scroll {
  overflow: auto; max-height: calc(100vh - 250px); border: 1px solid var(--line);
  border-radius: 12px; background: var(--card); -webkit-overflow-scrolling: touch;
}
.mcal { display: flex; align-items: flex-start; min-width: min-content; }
.mcal-gutwrap { position: sticky; left: 0; z-index: 3; background: var(--card); flex: none; }
.mcal-colwrap { flex: 1 1 0; min-width: 104px; }
.mcal-name {
  position: sticky; top: 0; z-index: 4; min-height: 30px; display: flex; align-items: center;
  justify-content: center; font-size: .72rem; font-weight: 800; color: var(--text);
  background: var(--pc-bg, var(--card)); border-bottom: 1px solid var(--line);
  border-left: 1px solid var(--line); padding: 4px 6px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.mcal-name.gut { background: var(--card); border-left: none; }
.mcal-gutter { position: relative; width: 46px; }
.mcal-gutter span {
  position: absolute; left: 0; right: 4px; text-align: right; transform: translateY(-6px);
  font-size: .62rem; font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums;
}
.mcal-col { position: relative; border-left: 1px solid var(--line); }
.mcal-slot { position: absolute; left: 0; right: 0; height: 30px; border-top: 1px solid var(--line); }
.mcal-slot.half { border-top-style: dotted; opacity: .6; }
.mcal-appt {
  position: absolute; left: 2px; right: 2px; z-index: 2; display: block; overflow: hidden;
  text-align: left; padding: 2px 4px 2px 6px; border-radius: 7px; min-height: 0;
  background: var(--pc-bg, var(--brand-tint)); color: var(--text); box-shadow: none;
  border: 1px solid var(--pc-line, var(--line)); border-left: 4px solid var(--status-c, var(--muted));
  font-weight: 600; line-height: 1.15;
}
.mcal-appt .tm { display: block; font-size: .6rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.mcal-appt .nm { display: block; font-size: .68rem; font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mcal-appt .pu { display: block; font-size: .58rem; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mcal-appt.s-cancelled, .mcal-appt.s-no_show { opacity: .55; text-decoration: line-through; }
.mcal-appt.blocked {
  background: repeating-linear-gradient(45deg, #eceaee, #eceaee 6px, #e0dde4 6px, #e0dde4 12px);
  color: var(--muted); border-left-color: var(--muted);
}
.mcal-hint { font-size: .74rem; margin: 6px 0 0; }

/* ---- login ---- */
/* The rows open (Dr Daniel, 2026-09-15: "make able to click in and check which
   appt"), so they say so on hover. */
tr.ml-row { cursor: pointer; }
tr.ml-row:hover > td { background: var(--brand-tint); }
/* In the drill-down: counted toward nothing, but shown, because a low number
   is exactly when someone wants to see what fell out of it. */
tr.ml-dropped > td { color: var(--muted); text-decoration: line-through; }

/* Whose turn is the next mattress consultation (Dr Daniel, 2026-09-15). The
   row with the fewest is marked, because finding the smallest number in a
   column is work the page can do for you. */
.ml-table { width: 100%; }
.ml-table td.num, .ml-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* An edge, not a fill: five clinicians can sit level on zero, and five filled
   rows is a shout where a marked edge will do. */
tr.ml-next > td:first-child { box-shadow: inset 3px 0 0 var(--brand); }
.ml-flag {
  margin-left: 8px; font-size: .68rem; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; color: var(--brand);
}

/* The eye on a password box (Dr Daniel, 2026-09-15). The button sits INSIDE
   the field, so the field makes room for it rather than the button covering
   what you typed. Quiet until you reach for it. */
.pw-wrap { position: relative; display: block; }
.pw-wrap input { padding-right: 46px; width: 100%; }
button.pw-eye {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px; min-height: 0; padding: 0; border: 0;
  background: transparent; box-shadow: none; color: var(--muted);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
button.pw-eye svg { width: 20px; height: 20px; display: block; }
button.pw-eye:hover { background: transparent; color: var(--brand); }
button.pw-eye:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 8px; }

.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background: linear-gradient(160deg, var(--brand) 0%, var(--brand-dark) 100%); padding: 16px; }
.login-card { background: #fff; border-radius: 20px; padding: 30px 32px; width: 100%; max-width: 400px;
  box-shadow: 0 30px 70px rgba(0, 0, 0, .35); }
.login-card .login-logo { display: block; width: 230px; max-width: 80%; height: auto; margin: 0 auto 6px; }

/* ---- print (FPO, customer invoice copy) ---- */
@media print {
  .sidebar, .topbar, .no-print, .toast { display: none !important; }
  .content { padding: 0; max-width: none; }
  body { background: #fff; }
  .card { box-shadow: none; }
  body:has(.modal-backdrop) .shell { display: none !important; }
  .modal-backdrop { position: static; padding: 0; background: #fff; overflow: visible; display: block; backdrop-filter: none; }
  .modal { box-shadow: none; max-width: 100%; margin: 0; padding: 0; animation: none; }
  .modal .actions, .modal .modal-x { display: none !important; }
  /* On paper the calendar has no fold to scroll: unbound the pane, or only the
     hours that happened to be on screen would print (Dr Daniel, 2026-08-30 -
     the pane took its own scrollbar so the header could freeze). The header
     goes back into the flow for the same reason: a sticky row has nothing to
     stick to once the page is not scrolling. */
  .cal-scroll { max-height: none !important; overflow: visible !important; }
  .cal-hdrow { position: static; box-shadow: none; }
  /* Customer invoice copy: one tidy A4 page (Dr Daniel, 2026-09-02). The
     on-screen modal chrome (title bar) duplicates the letterhead, so it goes;
     everything else tightens so a long bill still lands on a single sheet. */
  @page { margin: 12mm; }
  body:has(.inv-print) .modal-head { display: none !important; }
  .modal-backdrop.print-only { visibility: visible; }
  .inv-print { font-size: 11.5px; line-height: 1.35; }
  .inv-print h3 { font-size: 12px; margin: 7px 0 2px; }
  .inv-print p { margin: 3px 0; }
  .inv-print table { margin-top: 4px !important; }
  .inv-print table th, .inv-print table td { padding: 3px 6px; font-size: 11.5px; }
  .inv-print img { width: 120px !important; }
  .inv-print .inv-print-head { margin-bottom: 6px; padding-bottom: 6px; }
  /* Production Detail Confirmation (Dr Daniel, 2026-09-06): the popup body
     scrolls on screen, and printing an element mid-scroll CLIPPED whatever
     stood above the fold ("the details are eaten up on to top") — on paper
     the popup unbinds completely and the document always starts from its
     own letterhead. The popup's title bar would duplicate that letterhead,
     so it goes; brand marks are forced to print even with background
     graphics off. One tidy A4. */
  .modal { max-height: none !important; overflow: visible !important; height: auto !important; }
  body:has(.po-doc) .modal-head { display: none !important; }
  /* The po-doc print compressions live in their own @media print block at
     the very END of the sheet — they must come AFTER the screen .po-doc
     rules, or same-specificity screen rules written later in the file win
     and the print sizes never apply (the 2026-09-06 two-page bug). */
}

/* The print copy never shows on screen — it goes straight to the print
   dialog (Dr Daniel, 2026-09-03). visibility (not display) keeps it in the
   render tree so the printer still sees it. */
.modal-backdrop.print-only { visibility: hidden; }

/* ---- customer invoice copy letterhead (screen + paper) ---- */
.inv-print-head {
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: 16px; border-bottom: 3px solid var(--brand);
  padding-bottom: 10px; margin-bottom: 12px;
}
.inv-print-co { text-align: right; font-size: .78rem; color: var(--muted); line-height: 1.5; }
.inv-print-co strong { display: block; color: var(--text); font-size: .84rem; }

/* ---- responsive ---- */
/* The menu button belongs to the phone only; on a desk the menu is the
   sidebar and needs no opening (Dr Daniel, 2026-09-14). */
.nav-burger { display: none; }
@media (max-width: 900px) {
  .shell { flex-direction: column; }
  .sidebar { width: 100%; flex: none; height: auto; position: sticky; top: 0; z-index: 30; }
  /* Folded away by default: a page opened on a phone starts with the page,
     not with a screen and a half of navigation. */
  .sidebar nav, .sidebar .foot { display: none; }
  .shell.navopen .sidebar nav { display: flex; flex-wrap: wrap; }
  .shell.navopen .sidebar .foot { display: block; }
  .nav-burger {
    display: block; width: calc(100% - 24px); margin: 2px 12px 8px;
    min-height: 40px; background: var(--brand-tint); color: var(--brand);
    box-shadow: none; font-weight: 800;
  }
  .sidebar nav a { padding: 7px 10px; }
  .cal { grid-template-columns: 40px repeat(7, minmax(80px, 1fr)); }
  .cal-scroll { overflow-x: auto; }
  .content { padding: 14px; }
}

/* ---- motion -------------------------------------------------------------
   Movement here is functional: screens settle in, controls answer the moment
   they are pressed, and anything that changed draws the eye for a moment.
   Nothing loops, nothing delays the work, and the whole layer switches off
   for anyone whose device asks for reduced motion (guard at the end).      */

/* A page settles in section by section. */
.content > * { animation: rise .3s cubic-bezier(.22, .9, .3, 1) backwards; }
.content > *:nth-child(1) { animation-delay: .02s; }
.content > *:nth-child(2) { animation-delay: .06s; }
.content > *:nth-child(3) { animation-delay: .1s; }
.content > *:nth-child(n+4) { animation-delay: .14s; }
@keyframes rise { from { opacity: 0; transform: translateY(8px); } }

/* Controls answer the press. */
button:active:not(:disabled), .btn:active:not(:disabled) { transform: scale(.976); }
.sidebar nav a { transition: background .12s, color .12s, padding-left .14s ease; }
.sidebar nav a:hover { padding-left: 16px; }
.topbar .iconbtn { transition: background .12s, transform .12s; }
/* The topbar's controls grow a little under the cursor (Dr Daniel,
   2026-08-30). They are small targets in a quiet strip, so a size change reads
   faster than a colour change alone. The jump shortcuts are <a>, not <button>,
   so the global button hover never reached them - they need their own rule.
   The reduced-motion block further up flattens the transition for anyone who
   asks for that. */
.topbar .iconbtn:hover { transform: scale(1.1); }
.topbar .iconbtn:active { transform: scale(.94); }
.topbar-jump .jump { transition: background .12s, color .12s, transform .12s; }
.topbar-jump .jump:hover { transform: scale(1.12); }
.topbar-jump .jump:active { transform: scale(.96); }
/* The blanket reduced-motion rule below only flattens the TRANSITION, which
   would leave the size still jumping - and a size change under the cursor is
   exactly the movement that rule exists to spare people. So drop the scale
   itself for them; the colour change alone still answers the hover. */
@media (prefers-reduced-motion: reduce) {
  .topbar .iconbtn:hover, .topbar .iconbtn:active,
  .topbar-jump .jump:hover, .topbar-jump .jump:active { transform: none; }
}
.card { transition: box-shadow .18s ease, border-color .18s ease; }
tr.click:hover { transition: background .12s; }

/* Numbers that just refreshed deserve a beat of attention. */
.kpi .v { animation: rise .42s .06s cubic-bezier(.22, .9, .3, 1) backwards; }

/* The backdrop fades so the modal does not arrive on a hard black flash. */
.modal-backdrop { animation: fade .14s ease-out; }
@keyframes fade { from { opacity: 0; } }

/* Calendar: blocks fade in when the day (re)loads — the confirmation that a
   drag, a status change or a new booking actually landed. Deliberately no
   movement, so nothing fights the drag-and-drop preview band. */
.cal .appt { animation: fade .16s ease-out; }
.cal .slot { transition: background .1s; }
.cal .slot.drop-top::after, .cal .slot.drop-bottom::after { animation: fade .1s ease-out; }

/* Builder stacks (Mochi pads, mattress layers) settle as they are edited. */
.stack .layer, .stack .side { animation: fade .14s ease-out; }

/* Status pills ease between colours instead of snapping. */
.badge { transition: background .18s ease, color .18s ease; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
}

/* ---- Mattress station and configurator ------------------------------------
   Chips instead of dropdowns, and the price panel sticks.

   These were built at iPad-touch size — 50px chips, 1rem type, 48px inputs —
   which made the configurator far taller than anything else in the system
   (Dr Daniel, 2026-08-29: "the components and buttons dont need to be so big.
   Make it compact and dun use up so many spaces"). They now sit just under the
   42px of an ordinary button: still a comfortable target, a third less height.
   Each state re-asserts BOTH background and colour (cascade lesson learnt). */
.st-grid { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(300px, .9fr); gap: 12px; align-items: start; }
@media (max-width: 920px) { .st-grid { grid-template-columns: 1fr; } }
/* Compact enough that a form of six chip groups still reads at a glance
   (Dr Daniel, 2026-08-29: "make these buttons smaller and more compact, use
   lesser space"). Still a comfortable touch target on the shop-floor station,
   which shares this class. */
.st-chips {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
  gap: 6px; margin: 3px 0 2px;
}
.st-chip {
  background: var(--card); color: var(--text); border: 1.5px solid var(--line);
  border-radius: 10px; padding: 6px 10px; font-size: .8rem; font-weight: 600;
  min-height: 32px; line-height: 1.25; cursor: pointer;
  min-width: 0; overflow-wrap: break-word;
}
.st-chip:hover { background: var(--brand-tint); color: var(--text); }
/* Chosen = calm lavender tint with brand ink, outlined in its own colour
   (Dr Daniel, 2026-09-02: "when choose the option, give it a calmer colour")
   — the solid purple block was shouting. */
.st-chip.on {
  background: #e9d5f0; border-color: transparent;
  color: var(--brand); font-weight: 700;
}
.st-chip.on:hover { background: #eee0f2; color: var(--brand); }
.st-chip:active { transform: scale(.97); }
/* ---- KumoDoc-style chart cards (2026-09-04) ---- */
.kcard { display: flex; flex-direction: column; padding-bottom: 14px; }
.kcard .kbody { margin-top: 6px; }
.ktitle { text-align: center; font-weight: 800; font-size: 1.02rem; color: #374151; margin: 2px 0; }
.ksub { text-align: center; color: #9ca3af; font-size: .8rem; margin-bottom: 8px; }
.kbody { display: flex; gap: 2px; }
.kytitle {
  writing-mode: vertical-rl; transform: rotate(180deg);
  color: #9ca3af; font-size: .7rem; text-align: center; flex: none;
}
.kmain { flex: 1; min-width: 0; }
.kplotrow { display: flex; }
.kyaxis { position: relative; width: 36px; height: 170px; flex: none; }
.kyaxis span {
  position: absolute; right: 8px; transform: translateY(50%);
  font-size: .66rem; color: #6b7280;
}
.kplot { position: relative; flex: 1; height: 170px; min-width: 0; }
.kgrid, .kline { position: absolute; inset: 0; width: 100%; height: 100%; }
.kcols { position: absolute; inset: 0; display: grid; }
.kcell { position: relative; }
.kbar {
  position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
  border-radius: 3px 3px 0 0; max-width: 44px;
}
.kval {
  position: absolute; left: 50%; transform: translateX(-50%);
  font-size: .64rem; font-weight: 700; color: #374151; white-space: nowrap;
}
/* A month of days gives each bar about 30px: the figure goes short and small
   rather than overlapping its neighbours. */
.kval.tight { font-size: .55rem; letter-spacing: -.02em; }
.khit { position: absolute; width: 18px; height: 18px; transform: translate(-50%, -50%); z-index: 2; }
.kdot { position: absolute; inset: 3px; border-radius: 50%; background: #fff; border: 2px solid; }
.kxaxis { display: grid; margin-left: 36px; margin-top: 4px; }
.kxaxis span { text-align: center; font-size: .7rem; color: #374151; font-weight: 600; overflow: hidden; }
.klegend {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  margin-top: 10px; font-size: .76rem; color: #4b5563;
}
.kleg { display: inline-flex; align-items: center; gap: 6px; }
.kleg-ring { width: 11px; height: 11px; border-radius: 50%; border: 2px solid; background: #fff; flex: none; }
.kleg-sq { width: 11px; height: 11px; border-radius: 3px; flex: none; }

/* KPI comparison line — the Kumo habit (2026-09-04). */
.kpi-cmp { font-size: .68rem; font-weight: 800; margin-top: 2px; letter-spacing: .01em; }
/* Payment-method chips: each method its own calm tone (2026-09-03) —
   coloured at rest too (Dr Daniel: "when havednt choose, i want it to have
   colours"); picking deepens the fill and bolds the ink. */
/* The CHOSEN method must be unmistakable and the rest greyish (Dr Daniel,
   2026-09-05: "make the chosen button more obvious, other not chosen have
   to be greyish less obvious") — unchosen chips drop their tone for a
   quiet grey; hover previews the tone; chosen wears the deep tone, bold,
   with a ring in its own ink. */
button.pm-chip {
  background: #f2f1f4; color: #9a93a1;
  border-color: transparent;
}
button.pm-chip:hover { background: var(--tone-bg, #eceef2); color: var(--tone-fg, #55606f); }
button.pm-chip.on {
  background: var(--tone-deep, #d4d9e0); color: var(--tone-fg, #55606f);
  border-color: transparent; font-weight: 800;
  box-shadow: inset 0 0 0 2px var(--tone-fg, #55606f);
}
button.pm-chip.on:hover { background: var(--tone-deep, #d4d9e0); color: var(--tone-fg, #55606f); }

/* ---- Tap-to-answer rows on the consultation card --------------------------
   (Dr Daniel, 2026-08-29: "pls show the common options in buttons so they can
   click rightaway".) Smaller again than the configurator chips: a card carries
   twelve of these groups.

   Each group is a form control in its own right, so it gets the same border as
   the inputs beside it — without that boundary the eye could not tell where one
   question's answers ended and the next began, which was most of what "messy"
   meant. A grid rather than flex: sized by content, a group asks for the width
   of its answers UNWRAPPED (around 860px for the pillow questions) and each one
   took a whole line to itself. Equal columns that wrap their own chips are
   tidier and predictable, and auto-fit collapses the spare track so a
   two-question row still fills the width. */
.row.row-opts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px 16px; align-items: start;
}
.row.row-opts > * { min-width: 0; }
/* Each group wears its COMPONENT's tone (Dr Daniel, 2026-08-29). Shape,
   material and duration are three questions about one pillow, so they share a
   tone; the mattress questions share another. Colouring them separately said
   they were unrelated, which is the opposite of true.
   The tone arrives as --tone-bg / --tone-fg on the group, so a field added
   later needs no new CSS; a group with no tone falls back to a plain card. */
/* The tone marks the group without filling it (Dr Daniel, 2026-09-14: five
   solid colours down one form read as a quilt). The colour stays where it
   carries meaning — the hairline and the chosen chip. */
.cn-opts {
  border: 1px solid var(--tone-fg, var(--line)); border-radius: 12px;
  padding: 7px 9px 8px; background: transparent;
}
/* A group's own border is quiet until you are working in it. */
.cn-opts { border-color: color-mix(in srgb, var(--tone-fg, var(--line)) 28%, #fff); }
.cn-opts:focus-within { border-color: var(--tone-fg, var(--brand)); }
.cn-chips { display: flex; flex-wrap: wrap; gap: 6px; }
/* Sleeping habit as percentages (Dr Daniel, 2026-09-01):
   Supine _%  Side _%  Back _% on one line, in the habit tone. */
.pct-trio .cn-chips { align-items: center; gap: 4px; }
.pct-trio .pct-name { font-size: .78rem; font-weight: 700; color: var(--tone-fg, var(--text)); margin-left: 6px; }
.pct-trio .pct-name:first-child { margin-left: 0; }
.pct-trio .pct-box { width: 64px; min-height: 34px; text-align: right; }
.pct-trio .pct-legacy { font-size: .72rem; margin-left: 8px; }
.cn-chip {
  background: var(--card); color: var(--text); border: 1.5px solid var(--line);
  border-radius: 14px; padding: 6px 11px; font-size: .8rem; font-weight: 600;
  min-height: 0; line-height: 1.25; box-shadow: none;
}
.cn-chip:hover:not(:disabled) { background: #fff; border-color: var(--tone-fg, var(--brand)); }
.cn-chip.on {
  background: var(--tone-fg, var(--brand)); border-color: var(--tone-fg, var(--brand)); color: #fff;
}
.cn-chip.on:hover { background: var(--tone-fg, var(--brand-dark)); color: #fff; }
/* The add-on cover chip: borderless wash, a step below the chosen item
   (Dr Daniel, 2026-09-05: "remove the outer line. leave a gap"). */
.cn-chip.cover-addon { border: none; background: var(--neutral-bg, #f1eef3); padding: 7px 12px; }
/* Pending marker (Dr Daniel, 2026-09-05, third pass: "revert the button
   design to just bold words like before, but now only write pending, and
   put it on bright red"). */
.pending-badge {
  color: #e5231b; font-weight: 800; letter-spacing: .03em;
  white-space: nowrap; vertical-align: middle;
}

/* Greyish-white action buttons (Dr Daniel, 2026-09-05: the factory
   card's Edit / Edit-details buttons) — quiet until hovered. */
.soft-btn { background: #f4f3f5; color: #57505e; border: 1px solid #e6e3e9; box-shadow: none; font-weight: 600; }
.soft-btn:hover:not(:disabled) { background: #eae7ee; color: #3f3946; }
/* ✏️ Edit order pops a touch more than the soft greys around it (Dr
   Daniel, 2026-09-06: "Edit button colour not obvious enough") — a light
   brand wash with purple ink, still calm. */
.edit-btn {
  background: var(--brand-tint, #f5ecf7); color: var(--brand, #470054);
  border: 1px solid #d9c3e0; box-shadow: none; font-weight: 700;
}
.edit-btn:hover:not(:disabled) { background: var(--brand-tint-2, #ecdcf1); box-shadow: none; }

/* Factory accept/decline on clinic changes (Dr Daniel, 2026-09-05):
   calm washes, riding the same row as the request. Scoped under the
   banner because .amended-banner button paints everything amber. */
.amend-accept, .amended-banner button.amend-accept {
  background: #e3f0e6; color: #1d5a33; border: none; box-shadow: none;
  font-weight: 700; padding: 6px 12px;
}
.amend-accept:hover:not(:disabled), .amended-banner button.amend-accept:hover { background: #d3e6d8; }
.amend-decline, .amended-banner button.amend-decline {
  background: #fbe4e2; color: #99291f; border: none; box-shadow: none;
  font-weight: 700; padding: 6px 12px;
}
.amend-decline:hover:not(:disabled), .amended-banner button.amend-decline:hover { background: #f5d4d1; }

/* The construction must-choose block glows softly until answered
   (Dr Daniel, 2026-09-05: "more prominent so that it is a must choose"). */
.notice.altera-ask { background: #fdf6e7; border: 1px solid #e5c878; }

/* ---- Production Detail Confirmation (approved design 2026-09-06;
   reply-to-confirm revision 2026-09-07) ---------------------------------
   Letterhead with the caps title block on the right, purple hero ("Made
   for you. Checked by you."), numbered sections, rounded soft panels,
   the mattress facts beside the illustration with colour-dot side
   labels, the name-and-sleeping-side review panel (neutral grey-white
   since the revision), then the lavender "Ready to confirm?" panel and
   the prose terms line. Customers confirm by REPLYING to the
   accompanying message — the document carries no checkboxes and no
   signature rules, and the closing panel is an instruction, not a
   button. Colours are lifted from the approved PDF itself. */
.po-doc {
  --pod-purple: #511a65; --pod-ink: #2e2733; --pod-grey: #756d7b;
  --pod-panel: #f7f4f8; --pod-lav: #f2eaf4; --pod-line: #9a81a5;
  color: var(--pod-ink); font-size: .88rem; line-height: 1.45;
}
.po-doc .po-hd { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; margin-bottom: 12px; }
.po-doc .po-logo { width: 146px; height: auto; }
.po-doc .po-hd-meta { text-align: right; display: grid; gap: 3px; font-size: .78rem; color: var(--pod-grey); }
.po-doc .po-hd-title {
  font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  color: var(--pod-purple); font-size: .74rem;
}
.po-doc .po-hero {
  background: var(--pod-purple); border-radius: 14px;
  padding: 16px 20px 14px; margin: 0 0 14px; color: #fff;
}
.po-doc .po-hero h1 { margin: 0 0 6px; font-size: 1.72rem; letter-spacing: -.01em; color: #fff; }
.po-doc .po-hero-hi { margin: 0 0 8px; font-size: 1rem; }
.po-doc .po-hero-sub { margin: 0; font-size: .8rem; color: #eee4f1; }
.po-doc .po-sec-head { display: flex; align-items: center; gap: 11px; margin: 18px 0 8px; }
.po-doc .po-num {
  width: 27px; height: 27px; flex: none; border-radius: 9px;
  background: var(--pod-purple); color: #fff; font-weight: 800; font-size: .74rem;
  display: inline-flex; align-items: center; justify-content: center;
}
.po-doc .po-sec-head h2 { margin: 0; font-size: 1.08rem; color: var(--pod-purple); flex: 1; }
/* Panels sit flat on the page (Dr Daniel, 2026-09-07: "Remove the grey
   background") — only the review callout and the confirm panel keep a
   fill of their own. */
.po-doc .po-panel { border-radius: 10px; padding: 12px 15px; }
.po-doc .po-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 20px; }
.po-doc .po-cell { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.po-doc .po-lab {
  font-size: .62rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .08em; color: var(--pod-grey);
}
.po-doc .po-val { font-weight: 600; overflow-wrap: anywhere; }
.po-doc .po-matt { display: grid; grid-template-columns: 1.05fr .95fr; gap: 16px; align-items: start; }
.po-doc .po-matt-left { display: flex; flex-direction: column; gap: 12px; }
.po-doc .po-size { display: flex; flex-direction: column; gap: 3px; }
.po-doc .po-size-val { font-size: 1.2rem; font-weight: 800; color: var(--pod-purple); }
.po-doc .po-loose { padding: 0 3px; }
/* The review panel holds plain ROWS (2026-09-07 revision): neutral
   grey-white ground, purple row headings, a subtle grey divider — no
   checkboxes, customers confirm by replying to the message. */
.po-doc .po-callout { background: #f5f5f4; padding: 2px 0; }
.po-doc .po-crow { padding: 10px 15px; margin: 0; }
.po-doc .po-crow + .po-crow { border-top: 1px solid #e4e4e1; }
.po-doc .po-crow .po-lab { color: var(--pod-purple); }
.po-doc .po-crow-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.po-doc .po-callout-name { font-size: 1.16rem; font-weight: 800; color: var(--pod-purple); }
.po-doc .po-callout-sub { font-size: .78rem; }
.po-doc .po-matt-right { text-align: center; }
.po-doc .po-matt-right img { width: 100%; max-width: 300px; height: auto; background: none; }
.po-doc .po-fig-labs { display: flex; justify-content: space-around; margin-top: 2px; gap: 12px; }
.po-doc .po-fig-labs > div { display: flex; flex-direction: column; align-items: center; gap: 1px; font-size: .8rem; font-weight: 700; }
.po-doc .po-fig-labs strong {
  font-size: .6rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .07em; color: var(--pod-grey);
}
.po-doc .fl-dot { width: 8px; height: 8px; border-radius: 50%; margin-bottom: 2px; }
.po-doc .fl-dot.blue { background: #3454c8; }
.po-doc .fl-dot.red { background: #de2828; }
/* One row, however many panels the order carries (Dr Daniel, 2026-09-07:
   "make these 3 component in one row") — auto-flow columns split the
   width equally between one, two or three panels. */
.po-doc .po-items-row { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 12px; margin-bottom: 10px; }
.po-doc .po-items { border-radius: 10px; padding: 11px 14px; }
.po-doc .po-items h4 {
  margin: 0 0 7px; font-size: .64rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .08em; color: var(--pod-purple);
}
/* Qty hugs the item name (Dr Daniel, 2026-09-07: "The qty column make it
   nearer to the item description") instead of flushing to the far edge. */
.po-doc .po-item { display: flex; gap: 10px; padding: 2.5px 0; font-size: .8rem; }
.po-doc .po-item strong { flex: none; }
.po-doc .po-none { margin: 0; font-size: .78rem; color: var(--pod-grey); }
.po-doc .po-cta { background: var(--pod-lav); border-radius: 10px; padding: 12px 15px; margin-top: 16px; }
.po-doc .po-cta h3 { margin: 0 0 4px; font-size: .92rem; color: var(--pod-purple); }
.po-doc .po-cta p { margin: 0; font-size: .78rem; }
.po-doc .po-cta p + p { margin-top: 3px; }
.po-doc .po-cta strong { color: var(--pod-purple); }
.po-doc .po-before { margin-top: 12px; }
.po-doc .po-terms-p { margin: 0; font-size: .76rem; }

/* Separate construction (Dr Daniel, 2026-09-06, "split the base vs
   topper/altera into 2 components"): the comfort piece and the base piece
   are TWO boxes stacked with a small labelled gap — not one box with a
   dotted band inside. */
.stack-row .stack-col { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.stack-col .mattress { flex: none; border-radius: 16px; }
/* The two split kinds wear different shells (Dr Daniel, 2026-09-06): a
   Topper piece outlines in WHITE, an Altera piece in LUNOX purple — and
   the Altera's line is DOUBLE (a second ring just outside the border). */
.stack-col .mattress.split-topper { border-color: #fff; }
/* The Altera piece reverted to the ORIGINAL grey shell (Dr Daniel,
   2026-09-06: "revert back to your ori design. this one nicer") — no rule
   needed; it falls back to the standard .mattress border. */
/* Construction chips carry tiny drawings in the preview's own language
   (Dr Daniel, 2026-09-06): one piece / white-outline piece over the base /
   double-outline piece over the base. A tinted plate keeps the white
   outline readable on the white button. */
.cons-ico {
  display: inline-flex; flex-direction: column; justify-content: center; gap: 3px;
  width: 40px; padding: 5px; border-radius: 8px; background: #e9def0; flex: none;
  vertical-align: middle;
}
.cons-ico .ci-one {
  height: 19px; border-radius: 5px; border: 2px solid #7a6a80;
  background: linear-gradient(#fff 0 55%, var(--brand, #470054) 55%);
}
.cons-ico .ci-piece { height: 9px; border-radius: 4px; background: #f7f2fa; border: 2px solid #fff; }
/* The altera piece wears the standard grey shell, matching the reverted
   preview (Dr Daniel, 2026-09-06: "remove the double outline"). */
.cons-ico.altera .ci-piece { border-color: #7a6a80; }
.cons-ico .ci-base {
  height: 10px; border-radius: 4px; border: 2px solid #2e0036;
  background: var(--brand, #470054);
}
.pickbtn .cons-ico { margin-right: 4px; }
/* Flip sides wears the same greyish white as the soft buttons (Dr Daniel,
   2026-09-06, superseding the sky tone from earlier the same day) — one
   family with Edit order / Edit Configuration. */
.flip-btn {
  background: #f4f3f5; color: #57505e; border: 1px solid #e6e3e9;
  box-shadow: none; font-weight: 600;
}
.flip-btn:hover { background: #eae7ee; color: #3f3946; box-shadow: none; }

/* Compact construction chips (Dr Daniel, 2026-09-06: "Construction button
   no need so big") — icon and label side by side on one tight row. */
.cons-grid .pickbtn {
  display: flex; align-items: center; justify-content: flex-start; gap: 8px;
  padding: 7px 10px; min-height: 0; font-size: .8rem; line-height: 1.2;
  text-align: left; border-radius: 10px;
  /* A grid column will not shrink past its content unless it is told it may;
     without this the three construction chips pushed themselves off the side
     of a phone (Dr Daniel, 2026-09-14). */
  min-width: 0;
}
.cons-grid .pickbtn span { min-width: 0; overflow-wrap: anywhere; }
/* Three across is a desk's luxury; a phone takes them in one column, at full
   width, where the label can be read. */
@media (max-width: 620px) {
  .pick-grid.cons-grid { grid-template-columns: minmax(0, 1fr) !important; }
}
.cons-grid .cons-ico { width: 30px; padding: 3px 4px; gap: 2px; margin: 0; }
.cons-grid .cons-ico .ci-one { height: 13px; border-width: 1.5px; }
.cons-grid .cons-ico .ci-piece { height: 6px; border-width: 1.5px; }
.cons-grid .cons-ico .ci-base { height: 7px; border-width: 1.5px; }

/* The standalone Topper / Altera Topper products wear the same shells
   (Dr Daniel, 2026-09-06: "make topper and altera configuration same
   design"). */
.stack.topper .mattress.kind-topper { border-color: #fff; }
/* kind-altera keeps the ORIGINAL grey shell (reverted 2026-09-06). */
.stack-col .split-note {
  padding: 6px 10px; text-align: center;
  font-size: .66rem; font-weight: 800; letter-spacing: .06em; color: var(--brand);
}
.stack-col .split-base .layer.base { border-top: none; }
.cn-chip.cover-addon:hover:not(:disabled) { border: none; background: var(--brand-tint); }
.cn-chip.cover-addon.on { background: var(--brand); color: #fff; }
/* "Other…" reads as the escape hatch it is, not as one more answer. */
.cn-chip.other { color: var(--muted); font-style: italic; padding: 6px 9px; font-weight: 500; }
.cn-chip.other.on { color: #fff; font-style: normal; }
.cn-chip.clear { padding: 6px 9px; color: var(--muted); }
.cn-chip:disabled { opacity: .55; cursor: default; box-shadow: none; }
.cn-chip:disabled:hover { background: var(--card); border-color: var(--line); }
.cn-chip.on:disabled:hover { background: var(--brand); color: #fff; }
.cn-opts input { margin-top: 7px; max-width: 340px; }

/* An icon-only action: square, so it reads as a control rather than as a
   button whose label failed to load. Its name is on hover and on aria-label.
   The icon is drawn larger than it would be beside text — a 16px mark in a 42px
   button is doing all the work and has room to spare (2026-08-29). */
.modal-actions .iconlabel, .iconlabel { padding: 10px; width: 42px; flex: none; }
.iconlabel .ic, .iconlabel .ic svg { width: 20px; height: 20px; }

/* The two catalogue recommendations: a dashed field reads as "this opens a
   chooser", and fills in solid once something is chosen. */
.cn-pick {
  flex: 1; min-width: 0; justify-content: flex-start; text-align: left;
  background: var(--card); color: var(--muted); border: 1.5px dashed var(--line);
  border-radius: 12px; padding: 9px 12px; font-size: .82rem; font-weight: 600;
  min-height: 0; line-height: 1.25; box-shadow: none;
}
.cn-pick:hover:not(:disabled) {
  background: #fff; border-color: var(--tone-fg, var(--brand));
  color: var(--tone-fg, var(--brand)); box-shadow: none;
}
.cn-pick.on { border-style: solid; border-color: var(--tone-fg, var(--brand)); color: var(--text); }
.cn-pick:disabled { opacity: .55; cursor: default; }
.cn-pick:disabled:hover { background: var(--card); border-color: var(--line); color: var(--muted); }

.st-step { display: inline-flex; align-items: center; gap: 7px; }
.st-step button {
  width: 40px; height: 40px; min-height: 0; border-radius: 11px; font-size: 1.2rem; padding: 0;
  background: var(--brand-tint-2); color: var(--brand); font-weight: 800;
}
.st-step button:hover { background: var(--brand-tint); color: var(--brand); }
.st-step .v { min-width: 66px; text-align: center; font-size: 1rem; font-weight: 800; }
.st-price { position: sticky; top: 12px; }
/* The running total is the one thing here that stays big — it is what the
   customer is looking at. */
.st-total { font-size: 1.8rem; font-weight: 800; color: var(--brand); letter-spacing: -.02em; }
.st-select { font-size: .92rem; min-height: 40px; width: 100%; }
.station-page h2 { font-size: 1rem; margin-top: .85rem; }
.station-page input[type="text"], .station-page input[type="number"] { font-size: .95rem; min-height: 40px; }
.st-level { border: 1.5px solid var(--line); border-radius: 12px; padding: 9px 11px; margin: 6px 0; background: var(--card); }
.st-level .lvhead { display: flex; align-items: center; gap: 8px; margin-bottom: 3px; }
.st-level .lvhead h3 { margin: 0; flex: 1; }
.st-split { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; }
.st-split > div + div { border-left: 1.5px solid var(--line); padding-left: 11px; }

/* ---- calendar "now" line: red, moving with the clock ---- */
.cal .now-line {
  position: absolute; height: 0; border-top: 2px solid #dc2626;
  z-index: 5; pointer-events: none;
}
.cal .now-line::before {
  content: ""; position: absolute; left: -5px; top: -5px;
  width: 8px; height: 8px; border-radius: 50%; background: #dc2626;
}
.cal .now-line .now-time {
  /* In the slot BELOW the red rule, not straddling it (Dr Daniel,
     2026-09-07: "the display time … shall be in the slot below the
     thicker horizontal line"). */
  position: absolute; left: -50px; top: 3px; font-size: .68rem;
  font-weight: 800; color: #fff; background: #dc2626;
  padding: 1px 5px; border-radius: 7px;
}

/* Invoice actions: the "add something" row sits above the row that decides the
   bill's fate, so a mis-tap on ➕ can never hit Cancel or Issue
   (Dr Daniel, 2026-08-23). */
.build-row { margin-bottom: 6px; }
/* The two "add something" buttons sit above the cart and are reached often but
   are not the point of the page, so they are deliberately smaller than an
   ordinary button (Dr Daniel, 2026-08-29). */
.build-row button {
  min-height: 34px; padding: 5px 13px; font-size: .8rem; line-height: 1.25;
}

/* The two ends of a draft — cancel it, or issue it — sit side by side, so they
   are the same height (Dr Daniel, 2026-08-29). They cannot be left to work it
   out from a shared min-height: Issue carries 1rem text and Cancel only an
   icon, so the same rule rendered them 4px apart. Both are pinned to one value
   here, and the border widths match, or they would still differ by a pixel. */
/* Outline, not fill (Dr Daniel, 2026-08-30): the colour is in the border and
   the mark, the middle stays empty. For actions that are available without
   being what you came to do. */
button.outline-act {
  background: transparent; border: 1.5px solid var(--brand); color: var(--brand);
  box-shadow: none;
}
button.outline-act:hover { background: var(--brand-tint); box-shadow: none; }

/* The opposite: keep the fill, drop the border (Dr Daniel, 2026-08-30). Some
   buttons carry a border from their colour class - .go and .danger both do -
   and in a row where only ONE thing is outlined, the rest should not be. */
button.no-outline { border: none; }

/* No fill at all: the label and the mark carry the colour (Dr Daniel,
   2026-08-30). For the quiet actions beside a decisive one. */
button.no-fill { background: transparent; box-shadow: none; }
button.no-fill:hover { background: var(--brand-tint); box-shadow: none; }
/* `.ghost.danger` carries its own fill at a higher specificity than `.no-fill`,
   so the two destructive marks - remove this line, delete this draft - need the
   combination spelled out or they keep their pink. The red stays in the mark
   itself, and the hover answers in red rather than borrowing the purple tint. */
button.ghost.danger.no-fill, button.danger.no-fill {
  background: transparent; box-shadow: none;
}
button.ghost.danger.no-fill:hover, button.danger.no-fill:hover {
  background: #fbe7e5; box-shadow: none;
}

/* Sleepy Points spend in the gold of their own mark (Dr Daniel, 2026-08-30):
   redeeming points is not the same act as taking money, so the button does not
   borrow the green that issues the bill. Taken from the moon in the icon
   (#f2cd72 on #9a7118) rather than picked to sit near it. */
button.tone-sleepy {
  background: #fbf1d5; color: #7f5d10; border: none; box-shadow: none;
}
button.tone-sleepy:hover { background: #f6e7bd; color: #6b4e0b; box-shadow: none; }
button.tone-sleepy:active { background: #efdca6; }

/* Fill AND outline: the light purple stays, the border marks it out
   (Dr Daniel, 2026-08-30). */
button.tint-outline {
  background: var(--brand-tint); color: var(--brand);
  border: 1.5px solid var(--brand); box-shadow: none;
}
button.tint-outline:hover { background: var(--brand-tint-2); box-shadow: none; }

/* Calm fills that are nobody else's colour on these screens: sky for the
   factory shortcut, teal for rescheduling. Neither collides with the five
   solid status chips above them, nor with the green that takes money. */
button.tone-lav { background: #efeaf7; color: #5b4a86; border: none; box-shadow: none; }
button.tone-lav:hover { background: #e3daf2; color: #4b3c72; box-shadow: none; }
button.tone-sky { background: #e6f0f8; color: #3f6b8e; border: none; box-shadow: none; }
button.tone-sky:hover { background: #d7e6f3; color: #35597a; box-shadow: none; }

/* Signing is irreversible, so it neither matches the ordinary purple actions
   nor shouts like the green one: a calm yellow with its own outline. */
button.sign-act {
  background: #fdf4e3; color: #7a5410; border: 1.5px solid #e0a33a; box-shadow: none;
}
button.sign-act:hover { background: #f9e9cd; color: #6a4809; box-shadow: none; }
button.sign-act:active { background: #f4dfb8; }

/* Reschedule sits with the status chips but is not one of them: outlined, and
   pushed away from the five so the eye reads it as a different kind of answer
   (Dr Daniel, 2026-08-30). */
.resched-btn {
  margin-left: 10px; border: none; background: #e4f1f0; color: #3d726e;
  box-shadow: none; min-height: 38px; padding: 6px 14px; font-size: .84rem;
}
.resched-btn:hover { background: #d3e8e6; color: #2f5d5a; box-shadow: none; }

/* An amended invoice keeps its original number, so the tag is what says which
   copy you are holding (Dr Daniel, 2026-08-30). */
.amend-tag {
  display: inline-flex; align-items: center; align-self: center;
  background: #fdf0d8; color: #8a5a00; border: 1px solid #e0a33a;
  border-radius: 999px; padding: 2px 10px;
  font-size: .68rem; font-weight: 800; letter-spacing: .05em; white-space: nowrap;
}
.amend-tag.withdrawn { background: #fdeaea; color: #97321f; border-color: #d98274; }
.inv-no .amend-tag { font-size: .6rem; padding: 1px 7px; margin-right: 5px; }

/* Room above the pair that ends the draft (Dr Daniel, 2026-08-30) - the last
   cart line should not sit right on top of Delete / Issue. */
.cart-actions { margin-top: 18px; }

/* 56px -> 46px (Dr Daniel, 2026-08-30): still the largest pair on the page,
   without owning a whole band of it. */
button.primary-action.go, button.cancel-draft { min-height: 46px; }
button.primary-action.go { padding: 9px 20px; font-size: .92rem; }
/* The bin alone now, so it is padded to a square rather than left the width
   of the word it no longer carries (Dr Daniel, 2026-08-30). */
button.cancel-draft { padding: 9px 14px; border-width: 2px; font-size: .92rem; }

/* ---- lorry board ---- */
.day-card .run + .run { margin-top: 14px; padding-top: 10px; border-top: 1px dashed var(--line); }
.day-card .run h3 { color: var(--brand); margin: 0 0 6px; }
/* Disposal is easy to miss and expensive to miss: the lorry turns up without
   room for the old mattress. It is called out wherever the order appears. */
.warn-flag { display: inline-block; font-size: .72rem; font-weight: 800; color: #8a5a00;
  background: #fff7e6; border: 1px solid #e0b473; border-radius: 999px; padding: 0 7px; margin-top: 3px; }
.ok-flag { font-size: .74rem; font-weight: 700; color: var(--good, #059669); }
/* The delivery board's address column: readable, but not allowed to push
   the editable columns off the edge (Dr Daniel, 2026-09-12). */
.rfd-addr { font-size: .8rem; color: var(--muted); max-width: 230px; }
/* Wide enough that the promised date and time always share one line. */
.rfd-slot { min-width: 216px; }
/* CS remarks read as a line to write on, not another box in a row full of
   them (Dr Daniel, 2026-09-12). */
input.rfd-remark {
  border: none; border-bottom: 1px solid var(--line); border-radius: 0;
  background: transparent; padding-left: 2px; padding-right: 2px;
}
input.rfd-remark:focus {
  outline: none; border-bottom-color: var(--brand); background: transparent;
}
/* Saved, the remark is just words — the underline belongs to the box you are
   typing in, not to the note you already wrote (Dr Daniel, 2026-09-12: "click
   tick, then it'll show it there without the underline"). A pencil opens it
   again; the tick that saves it is green, the ✕ beside it changes nothing. */
.rfd-remarkrow { display: flex; align-items: center; gap: 2px; }
.rfd-remarktext { font-size: .8rem; white-space: pre-wrap; word-break: break-word; }
.rfd-remarkrow > button.rfd-tick { color: var(--ok); }
/* The board scrolls INSIDE a box no taller than the screen, so the
   left-right bar is always within reach instead of hiding at the foot of a
   long list (Dr Daniel, 2026-09-12: "Freeze the drag left and right bar"),
   and the headings stay put above the rows. */
.rfd-scroll { overflow: auto; max-height: calc(100vh - 250px); min-height: 180px; }
.rfd-scroll table { margin: 0; }
.rfd-scroll th {
  position: sticky; top: 0; z-index: 2;
  background: var(--card); box-shadow: 0 1px 0 var(--line);
}
/* The status IS the control (Dr Daniel, 2026-09-12: "Ready for Delivery
   status label make it a dropdown box instead. To change the status to
   Delivered"). It keeps the badge's shape and the badge's colour — which
   comes from the same .b-<status> classes the pill used — so the board still
   reads as a column of status pills, each one openable. */
select.rfd-statussel {
  width: auto; min-height: 0; padding: 3px 22px 3px 11px;
  border: none; border-radius: 999px;
  font-size: .74rem; font-weight: 700; white-space: nowrap;
  cursor: pointer; appearance: none;
  /* The caret is drawn in, since removing the native appearance takes the
     browser's own away with it. */
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 10px center, right 6px center;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}
select.rfd-statussel:focus { box-shadow: 0 0 0 3px var(--brand-tint-2); }
select.rfd-statussel:hover { filter: brightness(.96); }
select.rfd-statussel[disabled] { opacity: .6; cursor: progress; }
/* The options themselves render in the system menu, which does not inherit
   the pill's colours — keep them plainly readable. */
select.rfd-statussel option { background: var(--card); color: var(--text); font-weight: 600; }
/* The disposal flag rides beside the product rather than wrapping under it. */
.rfd-disposal { font-size: .66rem; padding: 0 6px; white-space: nowrap; margin-top: 2px; }

/* Money still owed on an order that is about to leave the building
   (Dr Daniel, 2026-09-12) — red, under the status, impossible to skim past. */
.due-flag {
  font-size: .76rem; font-weight: 800; color: var(--bad);
  margin-top: 3px; white-space: nowrap;
}
tr.row-ready td { background: #f2fbf6; }
/* Day close: a variance should be impossible to skim past. */
tr.row-variance td { background: #fff7e6; }
tr.total-row td { border-top: 2px solid var(--line); }

/* ---- Director Dashboard ----
   One page across four systems. Two ideas carry the layout: a source strip
   that says which systems are actually answering, and an attention list that
   sits ABOVE every number — the director should meet his to-do list before he
   meets his revenue. Brand primaries only; there is no traffic-light palette
   here beyond the semantic tokens the rest of the app already uses. */
.panel-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: .5rem;
}
.src-strip { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 14px; }
.src {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px; border-radius: 999px; font-size: .78rem; font-weight: 600;
  background: var(--card); box-shadow: var(--shadow);
}
.src-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.src.on .src-dot { background: var(--ok); }
.src.off .src-dot { background: var(--bad); }
.src.off { color: var(--muted); }
.src-age { color: var(--muted); font-weight: 500; }

/* An item waiting on the director. The severity rail is the only colour. */
.att {
  display: grid; grid-template-columns: 84px 1fr auto; gap: 12px;
  align-items: start; padding: 11px 0; border-bottom: 1px solid var(--line);
}
.att:last-child { border-bottom: none; }
.att-sev {
  font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .06em;
  padding: 3px 8px; border-radius: 6px; text-align: center;
}
.att-bad .att-sev { background: var(--bad-bg); color: var(--bad); }
.att-warn .att-sev { background: var(--warn-bg); color: var(--warn); }
.att-info .att-sev { background: var(--info-bg); color: var(--info); }
.att-where { color: var(--muted); font-size: .78rem; white-space: nowrap; padding-top: 3px; }

/* A system that is dark says why, and what would light it. */
.dark-panel { background: var(--brand-tint); border-radius: 12px; padding: 14px 16px; }
.dark-title { font-weight: 800; color: var(--brand); font-size: .82rem; text-transform: uppercase; letter-spacing: .05em; }

/* One bar row, used by BOTH the dashboard and the Director page. The track
   is the only flexible part, so a long figure shrinks the bar instead of
   pushing itself out of the card — which is what the dashboard's own
   hand-rolled version did once the values ran to six figures. */
.dbar { display: flex; align-items: center; gap: 8px; margin: 4px 0; }
.dbar-l {
  flex: none; color: var(--muted); font-size: .8rem; font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dbar-t { flex: 1 1 auto; min-width: 0; height: 14px; background: var(--brand-tint); border-radius: 4px; overflow: hidden; }
.dbar-f { height: 100%; background: var(--brand); border-radius: 4px; min-width: 2px; }
.dbar-v {
  flex: none; color: var(--muted); font-size: .8rem; font-variant-numeric: tabular-nums;
  min-width: 74px; text-align: right; white-space: nowrap;
}

.two-col { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 18px; }
@media (max-width: 720px) {
  .att { grid-template-columns: 1fr; gap: 4px; }
  .att-sev { justify-self: start; }
  .att-where { white-space: normal; }
}

/* The branch switcher looks like the control it is (2026-08-26). Every login
   can now switch, so the switcher has to be findable by someone who has never
   been told it exists. */
.sidebar .logo select.branch-switch {
  display: block; width: 100%; margin-top: 8px; padding: 7px 30px 7px 10px;
  font: 600 .72rem/1.2 inherit; letter-spacing: .08em; color: var(--ink, #2b0f33);
  background: var(--panel, #fff); border: 1px solid var(--line, #ebe0e8);
  border-radius: 9px; cursor: pointer; appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236d5974' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center;
  min-height: 0;
}
.sidebar .logo select.branch-switch:hover { border-color: var(--brand, #470054); }

/* FPO priority + post-edit flag (2026-08-26). ASAP is loud, later is calm,
   and NEWLY EDITED is impossible to scroll past on the factory floor. */
.prio-asap {
  display: inline-block; background: #fde8e6; color: #a3352c; font-weight: 700;
  padding: 3px 10px; border-radius: 999px; font-size: .74rem; letter-spacing: .04em;
  border: 1px solid rgba(163, 53, 44, .35); white-space: nowrap;
}
.prio-later {
  display: inline-block; background: #e8eef8; color: #23508f; font-weight: 700;
  padding: 3px 10px; border-radius: 999px; font-size: .74rem; letter-spacing: .04em;
  border: 1px solid rgba(35, 80, 143, .3); white-space: nowrap;
}
.amended-banner {
  background: #fdf3df; border: none; border-radius: 12px;
  padding: 12px 14px; margin: 8px 0;
}
/* The Noted button: calm amber tint but unmissable on the pale banner
   (Dr Daniel, 2026-09-02) — its ink matches the banner's warning hue. */
.amended-banner button {
  background: #f4d78a; color: #6b4708; border: none; box-shadow: none;
  font-weight: 800; padding: 9px 16px;
}
.amended-banner button:hover { background: #eec96b; }
.amended-banner s { text-decoration-color: rgba(163, 53, 44, .6); }

/* Factory portal accordion (2026-08-26): a clean list of order + customer,
   tap to open. The flags that must never hide ride on the closed row. */
.fo-card { padding: 0; overflow: hidden; }
.fo-card.open { padding-bottom: 14px; }
.fo-card.open .fo-head { border-bottom: 1px solid var(--line, #ebe0e8); }
.fo-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 13px 16px; cursor: pointer; user-select: none; min-height: 52px;
}
.fo-head:hover { background: var(--wash, rgba(71,0,84,.05)); }
.fo-chev { color: var(--muted, #6d5974); transition: transform .14s ease; flex: 0 0 auto; }
.fo-chev.open { transform: rotate(90deg); }
.fo-id { display: flex; flex-direction: column; min-width: 150px; }
/* The quiet type word beside the FPO number (Dr Daniel, 2026-09-06): the
   left-border colour alone let a courier card pass for a mattress PO. */
.fo-type {
  font-size: .58rem; font-weight: 800; letter-spacing: .08em;
  color: var(--muted); text-transform: uppercase; white-space: nowrap;
}
.fo-id .muted { font-size: .74rem; }
.fo-cust { flex: 1 1 auto; font-weight: 600; min-width: 120px; }
.fo-flags { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.fo-card.open > :not(.fo-head) { margin-left: 16px; margin-right: 16px; }
.edited-chip {
  display: inline-block; background: #fff7e0; color: #7a5a00; font-weight: 700;
  border: 1px solid #d9a92b; padding: 3px 9px; border-radius: 999px;
  font-size: .72rem; letter-spacing: .03em; white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) { .fo-chev { transition: none; } }

/* rail-hold (2026-08-26): set the moment « is clicked, cleared when the
   pointer leaves the sidebar once. Without it the pointer that clicked the
   toggle is still hovering the sidebar, the flyout re-opens instantly, and
   the collapse appears to do nothing. Overrides every hover-flyout rule. */
.shell.rail.rail-hold .sidebar:hover { width: var(--rail-w, 64px); box-shadow: none; }
.shell.rail.rail-hold .sidebar:hover .logo { padding: 14px 0 10px; text-align: center; }
.shell.rail.rail-hold .sidebar:hover .logo img { display: none; }
.shell.rail.rail-hold .sidebar:hover .logo .logo-mark { display: inline-block; }
.shell.rail.rail-hold .sidebar:hover .logo .sub,
.shell.rail.rail-hold .sidebar:hover .foot { opacity: 0; pointer-events: none; }
.shell.rail.rail-hold .sidebar:hover nav { padding: 6px 8px; }
.shell.rail.rail-hold .sidebar:hover nav a { justify-content: center; padding: 10px 0; }
.shell.rail.rail-hold .sidebar:hover nav a .lbl { opacity: 0; width: 0; }
.shell.rail.rail-hold .sidebar:hover .rail-btn { align-self: center; margin: 0 0 12px; }

/* By-staff calendar view (2026-08-26): the header chip carries the
   practitioner's colour so the column is identifiable at a glance. */
.cal-staff .staff-hd {
  background: var(--pc-bg, #fff); border-bottom: 2px solid var(--pc-line, var(--line));
  display: flex; align-items: center; justify-content: center; gap: 7px;
  font-weight: 700;
}
.cal-staff .staff-hd .staff-dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--pc-line, #888);
  flex: 0 0 auto;
}
/* Empty slots invite the click — a soft hint on hover says "book here". */
/* No whole-slot wash here either (Dr Daniel, 2026-09-07: the by-staff view
   still showed a second pale band under the quarter highlight — THIS rule
   was the culprit, missed when the generic slot:hover wash was removed). */
.cal-staff .slot:hover { cursor: pointer; }

/* The by-staff grid gets one column per rostered practitioner — twelve of
   them cannot fit the viewport, so the CALENDAR PANE scrolls sideways while
   the page itself never does (2026-08-26). The week/day views still fit and
   are unaffected. */
/* The PANE scrolls, not the page (Dr Daniel, 2026-08-30). Both axes belong on
   .cal-scroll: it is the box the sticky header measures itself against, and
   two nested scroll containers would leave the header stuck to a box that
   never moves. The max-height here is only a fallback - sizeCal() measures the
   real one - but it has to exist, or the pane is unbounded until JS runs and
   the header cannot stick at all. */
.cal-main { overflow: visible; }
.cal-scroll { overflow: auto; max-height: calc(100vh - 220px); overscroll-behavior: contain; }
/* The by-staff view fits the pane, however many people are on it (2026-08-29).
   It used to be `min-width: max-content` with a 130px floor per column, so a
   full roster ran off the side and had to be dragged — the one view whose
   whole purpose is seeing everybody at once. */
.cal-staff { min-width: 0; }
.cal-staff .slot, .cal-staff .hd { min-width: 0; }
/* Narrow columns need smaller type, not clipped names. */
.cal-staff.dense .staff-hd { font-size: .74rem; padding: 5px 2px; }
.cal-staff.dense .appt .t { font-size: .68rem; }
.cal-staff.dense .appt .rem { display: none; }
.cal-staff.denser .staff-hd { font-size: .66rem; padding: 4px 1px; letter-spacing: -.01em; }
.cal-staff.denser .staff-hd .staff-dot { display: none; }
.cal-staff.denser .staff-offroster { display: none; }
.cal-staff.denser .appt { padding: 2px 3px; }
.cal-staff.denser .appt .t { font-size: .62rem; line-height: 1.15; }
.cal-staff.denser .appt .rem { display: none; }
/* A name that still cannot fit is cut with an ellipsis rather than pushing
   the column wider and bringing the scrollbar back. */
.cal-staff .staff-hd { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Branch-roster grid (2026-08-26): each cell is a single branch choice, so
   one person cannot hold two branches on one day by construction. */
.roster-grid select.roster-cell {
  min-height: 0; padding: 5px 8px; font-size: .8rem; border-radius: 8px;
  border: 1px solid var(--line, #ebe0e8); background: #fff; cursor: pointer;
}
.roster-grid select.rb-ZP  { background: #ecfdf1; border-color: #5f7a44; }
.roster-grid select.rb-8TR { background: #f3ecfd; border-color: #6d47a3; }
.roster-grid select.rb-RU  { background: #ecf4fd; border-color: #23508f; }

/* ---- roster: one month calendar per branch (Dr Daniel, 2026-08-30) --------
   Shaped after the team's own Google Sheet work schedule - a month grid per
   outlet with the names written into the days - because that is the layout
   everyone here already reads a rota in. */
.rc-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.rc-month { font-size: 1rem; min-width: 9.5em; text-align: center; }
/* Side by side while there is room; stacked once there is not. Three months
   abreast is the whole point - it is how you see that Thursday is thin at one
   branch and crowded at another. */
.rc-wrap {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
/* One branch, the whole width. A day square then has room for four or five
   names instead of clipping at two (Dr Daniel, 2026-08-30). */
.rc-wrap.one { grid-template-columns: 1fr; }
.rc-wrap.one .rc-day { min-height: 86px; padding: 5px 5px 6px; }
.rc-wrap.one .rc-n { font-size: .72rem; }
.rc-wrap.one .rc-who { font-size: .72rem; padding: 2px 6px; }
.rc-wrap.one .rc-head strong { font-size: 1rem; }
.rc-wrap.one .rc-wd { font-size: .68rem; padding-bottom: 4px; }
/* The branch strip, in the toolbar beside the month. */
.rc-picks { display: flex; align-items: center; gap: 5px; margin-left: 10px; }
.rc-cal {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); padding: 9px 10px 11px; min-width: 0;
}
.rc-head { display: flex; align-items: center; gap: 7px; margin-bottom: 7px; }
.rc-head strong { font-size: .86rem; font-weight: 600; }
.rc-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.rc-wd {
  font-size: .58rem; color: var(--muted); text-align: center;
  font-weight: 700; padding-bottom: 2px;
}
/* A day is a small block, not a single line: the names live inside it, which
   is what makes the branch's week legible at a glance. */
.rc-day {
  display: flex; flex-direction: column; align-items: stretch; gap: 1px;
  min-height: 52px; min-width: 0; padding: 3px 3px 4px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--card); text-align: left; font: inherit; color: inherit;
  box-shadow: none; cursor: default;
}
button.rc-day { cursor: pointer; }
button.rc-day:hover { border-color: var(--brand); background: var(--brand-tint); box-shadow: none; }
.rc-day.pad { visibility: hidden; border: none; min-height: 0; }
/* An empty day is left plainly empty rather than marked - a rota is mostly
   empty squares and a symbol in every one of them is just noise. */
.rc-day.empty { background: var(--paper, #fbf9fc); }
.rc-day.today { box-shadow: inset 0 0 0 2px var(--brand); }
.rc-n { font-size: .6rem; color: var(--muted); font-weight: 700; line-height: 1; }
/* Each name is a little card in that practitioner's OWN colour - the same
   stable hue their appointments carry on the calendar, so a person reads as
   the same colour wherever they appear (Dr Daniel, 2026-08-30). The branch no
   longer tints the names: with one branch on screen at a time the outlet is
   already named above, and colouring by branch would have wasted the one
   signal that tells two clinicians apart at a glance. */
.rc-who {
  font-size: .62rem; line-height: 1.3; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  background: var(--pc-bg, var(--brand-tint));
  color: var(--text);
  border-left: 3px solid var(--pc-line, var(--brand-light));
  border-radius: 4px; padding: 1px 4px;
}

/* The day picker: who works this day at this branch. */
.rd-list { display: flex; flex-direction: column; gap: 1px; max-height: 46vh; overflow: auto; }
.rd-row {
  display: flex; align-items: center; gap: 9px; padding: 5px 7px;
  border-radius: 8px; cursor: pointer; margin: 0;
}
.rd-row:hover { background: var(--brand-tint); }
.rd-row .inv-tick { margin: 0; }
/* Placed at another branch that day. Shown rather than hidden: the reason a
   name cannot be ticked belongs on screen, not in an error afterwards. */
.rd-row.busy { opacity: .55; cursor: not-allowed; }
.rd-row.busy:hover { background: none; }
.rd-at { margin-left: auto; font-size: .68rem; color: var(--muted); font-weight: 700; }
.staff-offroster {
  font-size: .64rem; font-weight: 700; color: #a3352c; background: #fde8e6;
  border-radius: 999px; padding: 2px 7px; margin-left: 6px; letter-spacing: .03em;
}

/* KumoDoc case notes mirror (2026-08-26): read-only, collapsed by default. */
.kumo-note { border: 1px solid var(--line, #ebe0e8); border-radius: 10px; padding: 8px 12px; margin: 6px 0; }
.kumo-note summary { cursor: pointer; font-weight: 600; font-size: .88rem; }
.kumo-note-body { white-space: pre-wrap; font-size: .85rem; color: var(--muted, #6d5974); padding: 8px 2px 2px; }

/* Configurator pickers (2026-08-26): the complimentary set and item add-ons
   start as two buttons; everything else lives in popups. */
.comp-choose { display: flex; gap: 8px; align-items: center; }
/* Was 64px wide and 1.2rem tall for what is one glyph (Dr Daniel, 2026-08-29). */
.comp-choose > button { padding: 6px 12px; min-height: 32px; font-size: .82rem; }
.comp-plus { font-size: 1rem; min-width: 42px; }
.pick-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 9px; }
.pickbtn {
  /* The global button rule paints text white for purple fills; on this white
     card that made the label invisible (only the emoji showed). */
  padding: 16px 10px; font-size: .95rem; font-weight: 600; border-radius: 12px;
  border: 1px solid var(--line, #ebe0e8); background: #fff; cursor: pointer;
  min-height: 56px; color: var(--text, #2a1f2e); box-shadow: none;
}
.pickbtn:hover { border-color: var(--brand, #470054); background: var(--wash, rgba(71,0,84,.05)); }

/* Purpose chips on the booking form (Dr Daniel, 2026-08-29). Each purpose
   carries its own tone, supplied per chip as --tone-bg / --tone-fg, so adding a
   purpose needs no new CSS. Chosen fills with the tone's own dark shade, which
   is unmistakable without being loud. */
.purp-chips { display: flex; flex-wrap: wrap; gap: 7px; margin: 2px 0 8px; }
.purp-chip {
  background: var(--tone-bg); color: var(--tone-fg);
  border: 1.5px solid transparent; border-radius: 13px;
  padding: 7px 12px; font-size: .82rem; font-weight: 600;
  min-height: 0; line-height: 1.25; box-shadow: none;
}
.purp-chip:hover { background: var(--tone-bg); border-color: var(--tone-fg); box-shadow: none; }
.purp-chip.on { background: var(--tone-fg); color: #fff; border-color: var(--tone-fg); }
.purp-chip.on:hover { background: var(--tone-fg); color: #fff; }

/* Choosing a customer (2026-08-29): smaller type than an ordinary pick button,
   because a name and a phone number is more text than "Pillows", and a round
   initial to give the eye something to land on while scanning a long list. */
.pickbtn.cust-pick {
  justify-content: flex-start; text-align: left; gap: 9px;
  padding: 9px 11px; min-height: 0; font-size: .82rem; font-weight: 500;
}
.cust-face {
  width: 28px; height: 28px; border-radius: 50%; flex: none;
  background: var(--brand); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: .8rem;
}
/* min-width:0 is what lets a long name ellipsis instead of stretching the
   button past its grid cell. */
.cust-who { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.cust-nm { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cust-ph { font-size: .72rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Searching for a customer on the booking form itself (Dr Daniel, 2026-08-29).
   The box takes the width; New sits beside it and never wraps under. Results
   are capped in height so a broad search cannot push the rest of the form off
   the screen. */
.cust-find-row { display: flex; gap: 8px; align-items: center; }
.cust-find-row input { flex: 1; min-width: 0; }
.cust-find-row button { flex: none; }
.cust-find { display: flex; flex-direction: column; gap: 5px; margin-top: 6px; max-height: 268px; overflow-y: auto; }
.cust-find:empty { margin-top: 0; }

/* Add-to-Cart item search (Dr Daniel, 2026-09-05: "this is so messy"): the
   category chips step aside while results show, and each hit is ONE compact
   line — icon, name, SKU, price at the right edge. */
.item-find { gap: 4px; margin-top: 8px; max-height: 320px; }
.item-find .item-hit { display: flex; align-items: center; gap: 10px; padding: 7px 12px; border-radius: 10px; }
.item-find .item-hit .ic { flex: none; }
.item-find .find-nm { font-weight: 600; font-size: .84rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.item-find .find-sku { flex: none; font-size: .72rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.item-find .find-price { flex: none; margin-left: auto; font-weight: 700; font-size: .84rem; white-space: nowrap; font-variant-numeric: tabular-nums; }

/* One bar that takes a scan OR a typed name (Dr Daniel, 2026-08-30). The
   magnifier sits inside it in plain grey - a coloured mark on a field you type
   into competes with the text. */
.scanfind { position: relative; display: flex; align-items: center; }
.scanfind > .ic {
  position: absolute; left: 11px; color: var(--muted);
  pointer-events: none; display: flex;
}
.scanfind > .ic svg { width: 16px; height: 16px; }
.scanfind > input { width: 100%; padding-left: 34px; padding-right: 36px; }
/* Bare ✕ inside the bar (Dr Daniel, 2026-09-05): no outline, no fill —
   only shown while there is something to clear. */
.scanfind > .scan-clear {
  position: absolute; right: 4px; top: 0; bottom: 0;
  display: flex; align-items: center;
  background: none !important; border: none; box-shadow: none !important;
  color: var(--muted); font-size: .95rem; padding: 0 10px; min-height: 0; cursor: pointer;
}
.scanfind > .scan-clear:hover { color: var(--text); }

/* The practitioner row: name, percent, bin and the add button all on one line
   (Dr Daniel, 2026-08-30). */
.alloc-row { align-items: center; gap: 8px; margin-bottom: 8px; }
.alloc-row > .fix > button { min-height: 38px; }
.alloc-add { font-size: .9rem; }

/* Buttons inside a table cell: inline, never flex - a flex <td> is no longer a
   table cell and its column stops lining up (Dr Daniel, 2026-08-30). */
.cell-acts { white-space: nowrap; }
.cell-acts > button { display: inline-flex; vertical-align: middle; margin-right: 4px; }

/* Allocation: the name and its pencil share a line, the name shrinking rather
   than wrapping (Dr Daniel, 2026-08-30). */
/* NOT display:flex - these are <td>s, and a flex td stops being a table cell,
   which drops it out of the column model and lets the columns wander row to
   row (Dr Daniel, 2026-08-30: "why out of alignment"). Inline-block children
   in a nowrap cell give the same one-line layout and keep the table a table. */
.alloc-cell { white-space: nowrap; }
.alloc-name {
  display: inline-block; vertical-align: middle;
  font-size: .78rem; line-height: 1.25; max-width: 150px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Smaller than a normal small button: it sits inside a table cell beside a
   name, so it should read as a mark on that name rather than a control of its
   own (Dr Daniel, 2026-08-30). */
/* button.alloc-pen, not .alloc-pen: `button.small` sets min-height 32px at the
   same specificity, so the bare class lost. */
button.alloc-pen {
  min-height: 0; height: 24px; width: 24px; padding: 0; margin-left: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  vertical-align: middle;
}
button.alloc-pen .ic svg { width: 13px; height: 13px; }

/* The line's own number (Dr Daniel, 2026-08-30): a handle for talking about
   the cart out loud, not information about the sale, so it stays quiet.
   Tabular figures keep the column straight once the cart passes nine lines. */
.line-no {
  width: 34px; color: var(--muted); font-variant-numeric: tabular-nums;
  text-align: right; padding-right: 10px;
}
/* Qty sits beside the price it multiplies, and is typed straight into on a
   draft. The box is narrower than its cell so the header sits over its left
   edge rather than floating away from it. */
.qty-cell { width: 76px; }
.qty-cell .qty-in {
  width: 58px; min-height: 30px; padding: 3px 6px; text-align: center;
  font-variant-numeric: tabular-nums; margin: 0;
}
/* A locked qty is a fact, not a control: no box, no affordance to press. */
.qty-cell > .muted { display: inline-block; padding-left: 6px; }

/* Discount / configure / remove: one row, always. */
/* Same reason as .alloc-cell: a table cell, not a flex box. */
.line-acts { white-space: nowrap; text-align: right; }
/* Closer together (Dr Daniel, 2026-08-30). With the fills gone the buttons
   have no visible edge, so their padding read as empty space and the three
   marks looked like three unrelated controls rather than one line's actions:
   23px apart (9 + 5 + 9) became 14px (6 + 2 + 6). The 28px min-height keeps
   the tap target honest even though the box no longer shows. */
.line-acts > button, .line-acts > .mochi-slot > button {
  display: inline-flex; vertical-align: middle; margin-left: 2px;
  padding: 3px 6px; min-height: 28px; font-size: .8rem;
}
.line-acts > .mochi-slot { display: inline; }

/* The heading, with the add controls tucked in at its right once the cart has
   something in it. */
.cart-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.cart-head > h2 { margin: 0 0 6px; }
.cart-head > .build-row { margin: 0 0 6px auto; }
/* Tucked into the heading they are a group, not a spread row - the spacer that
   pushes them apart in the empty state has no job up here. */
.cart-head > .build-row .build-gap { display: none; }
.cart-head > .build-row input { max-width: 190px; }

/* The empty cart: the add controls sit where the first row would be, and a
   clear gap separates them from Cancel / Issue below. */
.cart-empty-slot:not(:empty) { margin: 4px 0 22px; }
.cart-empty-slot > .build-row { margin-bottom: 0; }

/* The draft's build row: configurators left, the plus and the scanner right.
   The Mochi button is appended after its own API call and would otherwise land
   last, so both configurators are ordered ahead of the spacer explicitly. */
/* Mattress / Topper sits with the plus and the scanner, not away from them
   (Dr Daniel, 2026-08-30) - all three are ways of putting something in the
   cart. Only the Mochi configurator stays out on the left. The spacer is
   ordered between them, so the group lands hard right. */
.build-row { align-items: center; }
/* Mochi sits WITH the mattress configurator, not away from it (Dr Daniel,
   2026-08-30). Explicit order because the Mochi button is appended after its
   own API call and would otherwise land last: spacer, Mochi, Mattress/Topper,
   plus, scanner. */
.build-row .build-gap { order: -4; flex: 1 1 auto; }
.build-row .mochi-act { order: -3; flex: 0 0 auto; }
.build-row .cfg-act { order: -2; flex: 0 0 auto; }
.build-row .add-act { order: -1; flex: 0 0 auto; }
.build-row input { max-width: 230px; }

/* The cart's footer: Mochi at the left end, everything else pushed right.
   `order` and `margin-right:auto` do it without caring what order the
   buttons were appended in - the Mochi button arrives after its own API
   call, so it would otherwise always land last. */
.cart-actions .mochi-act { order: -1; margin-right: auto; }

/* The three document actions on an issued bill, held at the right end of the
   row (Dr Daniel, 2026-08-30). `flex: 1` rather than `margin-left: auto`: the
   Mochi button already claims a `margin-right: auto`, and two auto margins
   split the free space between them, which would leave this group stranded
   mid-row. Flex-grow is resolved before auto margins, so growing this box
   takes the space outright and its children sit at its right edge whatever
   else is on the row. `order` keeps it last however late a sibling arrives. */
.cart-actions .doc-actions {
  display: flex; align-items: center; gap: 6px;
  order: 2; flex: 1; justify-content: flex-end;
}
/* On a draft nothing goes in it, and an empty flex child would still eat the
   row's free space. */
.cart-actions .doc-actions:empty { display: none; }

/* The item and its three controls share a line. While the category chips are
   showing they take the whole row and the controls drop beneath; once an item
   is chosen the name and the controls sit side by side. */
.item-line { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.item-line > .cn-chips { flex: 1 1 100%; }
.item-line > .comp-summary { flex: 1 1 auto; min-width: 0; margin: 0; }
/* The chosen item, its price, quantity, pencil and FOC on ONE row, with the
   tint on the ITEM alone (Dr Daniel, 2026-08-30) - the fill marks what was
   picked, not the controls that act on it. */
.item-line > .comp-summary {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.item-chip {
  background: var(--brand-tint); border: 1px solid var(--brand-tint-2, #e0d3e6);
  border-radius: 999px; padding: 5px 13px;
  font-weight: 700; color: var(--brand); font-size: .9rem;
}
/* It is also the way back to the picker, so it behaves like a button without
   looking like another control on a row that already has two. */
button.item-chip {
  min-height: 0; box-shadow: none; cursor: pointer; font-family: inherit;
}
button.item-chip:hover { background: var(--brand); color: #fff; box-shadow: none; }
.item-price { font-weight: 800; font-size: .95rem; white-space: nowrap; }
.item-src { font-size: .78rem; color: var(--muted); white-space: nowrap; }
.item-ctl { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.iq-qty { display: flex; align-items: center; gap: 6px; margin: 0; font-weight: 600; }
.iq-qty > span { font-size: .78rem; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
.iq-qty input { width: 62px; min-height: 34px; padding: 4px 8px; text-align: center; }

/* A remark is an aside, so it gets a rule to write on rather than a slab. */
.remark-line { margin: 8px 0 2px; }
.remark-line > label { display: block; margin-bottom: 0; }
.remark-line > input {
  border: none; border-bottom: 1.5px solid var(--line); border-radius: 0;
  background: transparent; padding: 3px 2px; min-height: 30px; box-shadow: none;
}
.remark-line > input:focus {
  border-bottom-color: var(--brand); outline: none; box-shadow: none;
}

/* FOC belongs beside the quantity: how many, and whether they are paying. */
.foc-tick {
  display: flex; align-items: center; gap: 7px; margin: 0 0 9px;
  font-weight: 600; white-space: nowrap; flex: 0 0 auto;
}
.foc-tick input[type="checkbox"] { width: 18px; height: 18px; flex: none; }

/* Ship from factory, on one line: the tick, the reason, the note. */
/* No fill (Dr Daniel, 2026-08-30): it is one line among several, and a tinted
   slab gave it more weight than the item it belongs to. */
.fj-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 6px 0 0; margin: 6px 0 0; background: none; border: none;
  border-top: 1px solid var(--line); border-radius: 0;
}
.fj-row > label {
  display: flex; align-items: center; gap: 7px; margin: 0;
  font-weight: 600; white-space: nowrap; flex: 0 0 auto;
}
.fj-row > label input[type="checkbox"] { width: 18px; height: 18px; flex: none; }
.fj-row > .fj-reasons { display: flex; gap: 6px; flex: 0 0 auto; }
.fj-row > .fj-reasons .cn-chip { padding: 4px 11px; font-size: .78rem; min-height: 30px; }
.fj-row > select { flex: 1 1 190px; min-width: 150px; min-height: 34px; padding: 5px 10px; font-size: .84rem; }
/* Narrow enough that label + both reason chips + the note still share one
   line once the tick is on (measured: 144 + 237 + gaps leaves ~183px). */
.fj-row > input { flex: 1 1 130px; min-width: 110px; min-height: 34px; padding: 5px 10px; font-size: .84rem; }

/* The Item label and its search box share a line: browse by chip, or type. */
.item-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 2px; }
.item-head label { margin: 0; }
/* The search sits BESIDE the label, so it takes only the room it needs rather
   than stretching across the popup (Dr Daniel, 2026-08-29). Smaller type and
   padding than a full form field: it is a shortcut, not the main input. */
.item-head input {
  flex: 0 1 190px; min-width: 130px;
  font-size: .8rem; padding: 5px 10px; min-height: 0;
}

/* Clinician chips (Dr Daniel, 2026-08-29). Same shape as the purpose chips but
   in the brand colour, because a clinician is a person rather than a category
   and giving thirteen of them thirteen colours would be noise. Anyone off the
   roster stays on screen, dimmed and dashed, so it is clear they exist and why
   they cannot be picked. */
.clin-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 8px; }
.clin-chip {
  background: var(--card); color: var(--text); border: 1.5px solid var(--line);
  border-radius: 13px; padding: 7px 12px; font-size: .82rem; font-weight: 600;
  min-height: 0; line-height: 1.25; box-shadow: none;
}
.clin-chip:hover:not(:disabled) { background: var(--brand-tint); border-color: var(--brand); }
.clin-chip.on { background: var(--brand); border-color: var(--brand); color: #fff; }
.clin-chip.on:hover { background: var(--brand-dark); color: #fff; }
.clin-chip:disabled { opacity: .45; border-style: dashed; cursor: default; }
.clin-chip:disabled:hover { background: var(--card); border-color: var(--line); }
/* A pickbtn can also act as a persistent choice (e.g. cover yes/no): .on is
   the selected one. */
.pickbtn.on { border-color: var(--brand, #470054); background: var(--brand-tint, #f5ecf7); box-shadow: inset 0 0 0 1px var(--brand, #470054); }
/* Date presets: calm per-window tones, the chosen one outlined in its own ink. */
.pickbtn.preset-tone { background: var(--tone-bg, #eceef2); color: var(--tone-fg, #55606f); border-color: transparent; font-weight: 600; }
.pickbtn.preset-tone:hover { border-color: var(--tone-fg, #55606f); background: var(--tone-bg, #eceef2); }
.pickbtn.preset-tone.on { border-color: var(--tone-fg, #55606f); box-shadow: inset 0 0 0 1px var(--tone-fg, #55606f); background: var(--tone-bg, #eceef2); }
.comp-summary { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 6px; }
.comp-pick-row { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--line, #ebe0e8); }
.comp-pick-label { font-weight: 700; min-width: 64px; }
.comp-pick-val { flex: 1 1 auto; }
.addon-row { border-left: 3px solid var(--brand-tint-2, #d9c2e0); padding-left: 10px; margin-bottom: 10px; }
.st-price { font-size: 2.2rem; font-weight: 800; color: var(--brand, #470054); margin: 6px 0 2px; }

/* ---- grouped nav (2026-08-26) ---- */
.nav-group { margin-bottom: 4px; }
.nav-group + .nav-group { border-top: 1px solid var(--line); margin-top: 4px; }
.nav-group-h {
  display: flex; align-items: center; width: 100%; gap: 8px;
  background: none; border: none; box-shadow: none; cursor: pointer;
  color: var(--brand); font-weight: 800; font-size: .8rem; line-height: 1;
  letter-spacing: .07em;
  text-transform: uppercase; padding: 12px 12px 6px; min-height: 0; border-radius: 0;
}
.nav-group-h .ic { flex: none; display: inline-flex; }
.nav-group-h .ic span { display: inline-flex; }
.nav-group-h svg { width: 16px; height: 16px; }
.nav-group-h:hover { background: none; color: var(--brand-light); box-shadow: none; transform: none; }
.nav-group-h:active { transform: none; box-shadow: none; }
.nav-group-h .lbl { flex: 1; text-align: left; }
.nav-chev { transition: transform .15s; font-size: .7rem; }
.nav-group.folded .nav-chev { transform: rotate(-90deg); }
.nav-group.folded > a { display: none; }
/* The icon rail has no room for headers: they disappear and every icon stays
   reachable regardless of fold — a fold must never hide pages from the rail.
   rail-hold keeps the rail closed under the cursor, so it needs the same. */
.shell.rail .sidebar:not(:hover):not(:focus-within) nav .nav-group-h,
.shell.rail.rail-hold .sidebar:hover nav .nav-group-h { display: none; }
.shell.rail .sidebar:not(:hover):not(:focus-within) nav .nav-group.folded > a,
.shell.rail.rail-hold .sidebar:hover nav .nav-group.folded > a { display: flex; }

/* Back / Today / forward read as ONE control, not three loose buttons: the
   arrows are square, Today keeps its words, and they share a single outline. */
.cal-step { display: inline-flex; align-items: center; }
/* The viewed day, spelt out between the arrows (2026-09-04). No fill on
   the controls, and arrows big enough to hit (Dr Daniel, same day). */
.cal-step-date { font-weight: 700; padding: 0 10px; white-space: nowrap; font-size: .92rem; }
.cal-step > button { background: none !important; border: none; box-shadow: none; }
.cal-step > button:hover { background: var(--brand-tint) !important; }
.cal-step > button:first-child, .cal-step > button:last-child {
  font-size: 1.45rem; line-height: 1; padding: 2px 12px;
}
.cal-step > button { border-radius: 0; margin: 0; box-shadow: none; }
.cal-step > button:first-child { border-radius: 10px 0 0 10px; }
.cal-step > button:last-child { border-radius: 0 10px 10px 0; }
.cal-step > button:not(:first-child) { margin-left: -1px; }
.cal-step > button:first-child, .cal-step > button:last-child {
  padding: 0 12px; font-size: 1.1rem; line-height: 1;
}
.cal-step > button:hover { position: relative; z-index: 1; }

/* The disposal question, in the one colour the rest of the form never uses.
   :has() lets the block answer for itself: amber while undecided, green the
   moment the box is ticked — so "did we ask?" is answered from across the
   room without reading a word (Dr Daniel, 2026-08-30). */
.disposal-ask {
  display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
  border: none; background: none;
  border-radius: 12px; padding: 9px 0; margin: 10px 0;
}
.disposal-ask > label {
  display: flex; gap: 10px; align-items: center; margin: 0;
  font-size: 1rem; font-weight: 700; color: #7a5410;
}
.disposal-ask input[type="checkbox"] { width: 20px; height: 20px; accent-color: #b57614; }
.disposal-ask:has(input[type="checkbox"]:checked) > label { color: #1f5c41; }
.disposal-ask:has(input[type="checkbox"]:checked) input[type="checkbox"] { accent-color: #2f7d5b; }

/* The target date is the one field on the fulfilment block that only WE can
   answer, and the factory schedules against it (Dr Daniel, 2026-08-29). It is
   marked out rather than sitting as one of four equal boxes. */
.target-block {
  border: none; border-radius: 12px;
  background: var(--brand-tint); padding: 9px 12px 10px; margin: 8px 0 10px;
}
.target-label { font-weight: 700; color: var(--brand); margin-bottom: 4px; display: block; }
.target-input {
  background: none; font-size: .92rem;
  border: none; border-bottom: 1.5px solid var(--line, #cfc4d6);
  border-radius: 0; box-shadow: none; padding-left: 2px;
}
.target-input:focus { outline: none; border-bottom-color: var(--brand); }
/* Labels that run from "ASAP" to "Within 2 weeks" (or from "SE" to "Deluxe"):
   equal columns would size every one of them to the longest, so these take the
   room the words need and wrap. */
.chips-tight .st-chips,
.target-block .st-chips { display: flex; flex-wrap: wrap; margin-top: 7px; }
/* :not(.on) matters. Without it this rule wins over `.st-chip.on` on source
   order alone — same specificity — and repaints the SELECTED chip white while
   `.on` keeps the white text, so the label disappears (Dr Daniel, 2026-08-29:
   "why after click, the word in the button missing?"). */
.target-block .st-chip:not(.on) { background: var(--card); }

/* ---- invoice header tiles (2026-08-27) ---- */
/* Four facts about the bill, in the least room that still reads (Dr Daniel,
   2026-08-29). The strip is a header, not a section: its card loses most of
   its padding so the tiles are what takes the space, not the frame. */
.hdr-card { padding: 9px 10px; margin-bottom: 10px; }
.hdr-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(186px, 1fr)); gap: 8px; }
.hdr-tile {
  display: flex; flex-direction: column; gap: 1px; align-items: flex-start;
  text-align: left; padding: 6px 10px; min-height: 46px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: var(--text); box-shadow: none;
}
/* A tile with its own small action in the corner. The tile still fills the
   grid cell and still opens its own popup; the action sits over it and stops
   the click from reaching it. */
.hdr-tile-wrap { position: relative; display: flex; }
.hdr-tile-wrap > .hdr-tile { flex: 1; min-width: 0; padding-right: 36px; }
.hdr-edit {
  position: absolute; top: 6px; right: 6px;
  width: 24px; height: 24px; min-height: 0; padding: 0; border-radius: 7px;
  background: var(--card); border: 1px solid var(--line); color: var(--muted);
  box-shadow: none;
}
.hdr-edit:hover { background: var(--brand-tint); border-color: var(--brand); color: var(--brand); box-shadow: none; }
button.hdr-tile { cursor: pointer; }
button.hdr-tile:hover { border-color: var(--brand); background: var(--brand-tint); box-shadow: none; }
.hdr-tile .t-label {
  font-size: .6rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); line-height: 1.25;
}
.hdr-tile .t-value { font-weight: 700; font-size: .85rem; line-height: 1.3; }
.hdr-tile.empty .t-value { color: var(--brand); }

/* ---- invoice register rows (2026-08-27) ---- */
/* A grid with one shared column template, so every row's columns line up
   like a table (Dr Daniel, 2026-08-27): date | number | mode | paid |
   customer | (draft actions). */
/* A 34px tick column leads every row (2026-08-28), so a batch can be printed
   or cleared in one go; the header's tick takes whatever the filters left. */
.inv-row {
  display: grid;
  grid-template-columns: 30px 112px 138px minmax(118px, .5fr) 132px minmax(180px, 1.6fr) 132px;
}
/* Finalised rows have no action cell — same template minus that column, so
   the frozen header and every row align to the pixel in both views. */
.inv-row.no-acts {
  grid-template-columns: 30px 112px 138px minmax(118px, .5fr) 132px minmax(180px, 1.6fr);
  align-items: stretch; gap: 0;
  padding: 0; margin-bottom: 4px; cursor: pointer; overflow: hidden;
  border: 1px solid var(--line); border-radius: 12px; background: var(--card);
}
/* Every cell centres its own content and carries a full-height divider on its
   left (Dr Daniel, 2026-08-27) — the columns read like a proper table. */
.inv-row > * {
  padding: 5px 10px; display: flex; flex-direction: column; justify-content: center;
  line-height: 1.25;
}
.inv-row > * + * { border-left: 1px solid var(--line); }
.inv-row:hover { border-color: var(--brand); background: var(--brand-tint); }
/* The frozen header: same template, stuck beneath the glassy topbar. */
.inv-head-row {
  position: sticky; top: 58px; z-index: 8; cursor: default;
  font-size: .68rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em; color: var(--brand-light);
  background: rgba(255, 255, 255, .92);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  margin-bottom: 6px;
}
.inv-head-row > * { padding: 5px 10px; }
.inv-head-row:hover { border-color: var(--line); background: rgba(255, 255, 255, .92); }
.inv-tick {
  width: 16px; height: 16px; min-height: 0; margin: 0 auto; padding: 0;
  accent-color: var(--brand); cursor: pointer; align-self: center;
}
/* The tick cell is the one cell with no left divider and no padding. */
.inv-row > .inv-tick { padding: 0; border-left: none; }
.inv-row.picked { border-color: var(--brand); background: var(--brand-tint); }

/* ---- draft bubbles (Dr Daniel, 2026-08-30) --------------------------------
   The Drafts tab shows tiles, not rows. A draft is a job in progress: you want
   who it is for and what it has come to, not a grid read across seven columns.
   auto-fill rather than auto-fit, so two open drafts stay two card-width tiles
   instead of stretching to half the screen each. */
.inv-bubbles {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
}
.inv-bub {
  border: 1px solid var(--line); border-radius: 18px; background: var(--card);
  padding: 11px 14px 9px; cursor: pointer; display: flex; flex-direction: column;
  gap: 3px; min-width: 0; transition: border-color .12s, background .12s, box-shadow .12s;
}
.inv-bub:hover {
  border-color: var(--brand); background: var(--brand-tint);
  box-shadow: 0 2px 10px rgba(71, 0, 84, .10);
}
.inv-bub.picked { border-color: var(--brand); background: var(--brand-tint); }
.bub-top { display: flex; align-items: center; gap: 7px; min-width: 0; }
.bub-top .inv-tick { margin: 0; flex: 0 0 auto; }
/* The draft's own number is the quiet part - it is a handle, not the headline. */
.bub-no {
  font-size: .68rem; font-weight: 800; letter-spacing: .05em;
  text-transform: uppercase; color: var(--brand-light);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bub-top .br-tag { margin-left: auto; flex: 0 0 auto; }
/* The customer leads, and a long name wraps rather than pushing the tile wide. */
.bub-name {
  font-size: .95rem; font-weight: 600; line-height: 1.3;
  overflow-wrap: anywhere; margin-top: 1px;
}
.bub-total { font-size: 1.24rem; font-weight: 800; letter-spacing: -.01em; }
.bub-sub {
  font-size: .72rem; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The date and the two actions share the last line: the actions sit at the
   right edge where the thumb expects them, and the tile keeps its height
   whether or not there is a consultant to name. */
.bub-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-top: 5px; min-height: 30px;
}
.bub-when { font-size: .7rem; color: var(--muted); white-space: nowrap; }
.bub-acts { display: flex; gap: 4px; flex: 0 0 auto; }
.bub-acts button { min-height: 28px; padding: 3px 9px; }
/* Select-everything, standing in for the header row the bubbles do not have. */
.bub-all {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  font-size: .72rem; font-weight: 700; color: var(--brand-light);
  text-transform: uppercase; letter-spacing: .05em; margin: 0 2px 10px;
}
.bub-all .inv-tick { margin: 0; }

/* The bar only exists while something is selected, so it never takes space
   from the list it acts on. */
.bulk-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 12px; margin-bottom: 10px;
  border: 1px solid var(--brand); border-radius: 12px; background: var(--brand-tint);
}
.bulk-count { font-weight: 800; color: var(--brand); font-size: .86rem; }

.inv-who { min-width: 0; }
.inv-dt { font-variant-numeric: tabular-nums; font-weight: 600; font-size: .82rem; }
.inv-dt .muted { font-weight: 400; font-size: .72rem; }
/* "Bank Transfer" was wrapping to two lines and setting the height of every
   row in the register (Dr Daniel, 2026-08-30). It gets the width it needs and
   smaller type, and the whole list halves. */
.inv-mode { font-size: .8rem; font-weight: 700; white-space: nowrap; }
.inv-row .inv-no { font-size: .84rem; }
.inv-row .badge { font-size: .62rem; padding: 1px 7px; }
.inv-row .inv-who strong, .inv-row .inv-who b { font-size: .86rem; }
.inv-row .muted { font-size: .72rem; }
.inv-row .num, .inv-row .inv-amt { font-size: .86rem; }
/* Branch chip beside the number, not under it. */
.inv-row .inv-no { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
@media (max-width: 860px) {
  .inv-row, .inv-row.no-acts { grid-template-columns: 34px 92px 1fr auto; }
  .inv-mode, .inv-no { display: none; }
}
.flt-chip { width: auto; min-height: 38px; padding: 6px 12px; border-radius: 999px; font-size: .84rem; }
.br-tag {
  display: inline-block; font-size: .62rem; font-weight: 800; letter-spacing: .04em;
  border-radius: 6px; padding: 1px 7px; color: #fff; margin-top: 2px;
}
.br-tag.br-zp { background: #470054; } .br-tag.br-8tr { background: #0e7490; }
.br-tag.br-ru { background: #a13d63; } .br-tag.br-cs { background: #0b5fa5; }
.br-tag.br-pt { background: #166534; }
/* The customer name carries the row on SIZE, not weight (Dr Daniel,
   2026-08-28: "too many bolds in the list, too heavy") — with the invoice
   number and the total already bold, a third bold column made every row shout. */
.inv-name { font-weight: 500; font-size: 1.02rem; }

/* The customer's name is a control - it opens their record - but it has to
   read as the name it is, not as a button parked in the middle of a row
   (Dr Daniel, 2026-08-30). Everything the base button gives a button is taken
   back off: the pill, the fill, the shadow, the 42px floor. What is left is the
   underline on hover, which is how a name that can be opened has always looked. */
button.name-link {
  background: none; border: none; box-shadow: none; padding: 0; margin: 0;
  font: inherit; color: inherit; min-height: 0; border-radius: 4px;
  display: inline-flex; align-items: center; justify-content: flex-start;
  gap: 5px; text-align: left; cursor: pointer;
}
button.name-link:hover {
  background: none; box-shadow: none;
  color: var(--brand); text-decoration: underline;
}
/* The base rules press every button down on click; a line of text that sinks
   under the cursor just looks like a rendering fault. */
button.name-link:active { transform: none; box-shadow: none; }
button.name-link:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* The profile's name line, with its pencil alongside (Dr Daniel, 2026-08-30).
   The name keeps its own weight; the pencil is a mark beside it, not a control
   competing with it. */
.prof-name { display: flex; align-items: center; gap: 8px; }
button.prof-edit { min-height: 26px; padding: 2px 6px; }
button.prof-edit .ic svg { width: 15px; height: 15px; }
.inv-amts { text-align: right; align-items: flex-end; }
.inv-total { font-weight: 800; }
.inv-due { color: var(--bad); font-weight: 700; font-size: .82rem; }
.inv-settled { color: var(--ok); font-size: .82rem; }
.inv-acts { flex-direction: row; align-items: center; gap: 6px; }
.inv-pop-who { margin-bottom: 10px; }

/* Appointments / Invoices / Production Orders, one tap from any screen. They
   sit just left of the profile, and the one you are on is filled in. */
.topbar-jump { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.topbar-jump .jump {
  width: 34px; height: 34px; min-height: 0; padding: 0; border-radius: 10px;
  background: transparent; color: var(--muted); box-shadow: none;
  display: inline-flex; align-items: center; justify-content: center;
}
.topbar-jump .jump:hover { background: var(--brand-tint); color: var(--brand); box-shadow: none; }
.topbar-jump .jump.on { background: var(--brand-tint); color: var(--brand); }
/* Both the .ic box AND the svg: .ic is 1.15em with flex:none, so sizing only
   the svg leaves it squeezed back to the container's width. */
.topbar-jump .jump .ic, .topbar-jump .jump .ic svg { width: 20px; height: 20px; }
/* The profile block already had margin-left:auto to push itself right; with the
   jumps taking that job, it just follows them. */
.topbar .topbar-jump ~ .who { margin-left: 10px; }
@media (max-width: 620px) { .topbar-jump { display: none; } }

/* The register's filter row, on one line (Dr Daniel, 2026-08-30). One height
   for everything in it, so the row reads as a single control strip; the search
   takes the leftover width instead of a fixed slab. */
.reg-toolbar { gap: 6px; align-items: center; }
.reg-toolbar > * { flex: 0 0 auto; }
.reg-toolbar .seg-row { border-radius: 999px; }
.reg-toolbar .pickbtn.seg-toggle {
  min-height: 34px; padding: 5px 11px; font-size: .8rem;
}
.reg-toolbar .flt-chip { min-height: 34px; padding: 5px 10px; font-size: .8rem; }
.reg-toolbar .branch-seg { align-self: center; }
.reg-toolbar .br-chip { min-height: 26px; padding: 3px 8px; font-size: .72rem; }
.reg-find { flex: 1 1 110px; min-width: 90px; }
.reg-find > input { min-height: 34px; padding: 5px 10px 5px 32px; font-size: .82rem; }
.reg-find > .ic { left: 9px; }
.reg-new {
  min-height: 34px; padding: 5px 14px; font-size: .82rem; white-space: nowrap;
}
/* Drafts / Finalised toggle on the register (2026-08-27): the active side is
   a solid brand fill so the two states cannot be confused at a glance. */
.seg-row { display: flex; gap: 0; border: 1px solid var(--brand); border-radius: 999px; overflow: hidden; }
.pickbtn.seg-toggle {
  min-height: 46px; padding: 8px 22px; font-size: .92rem; font-weight: 700;
  border: none; border-radius: 0; background: var(--card); color: var(--brand);
}
.pickbtn.seg-toggle:hover { background: var(--brand-tint); box-shadow: none; }
.pickbtn.seg-toggle.on {
  background: var(--brand); color: #fff; box-shadow: none;
}
.pickbtn.seg-toggle.on:hover { background: var(--brand); }

/* ---- branch toggle, one colour per branch (2026-08-27) ---- */
.branch-seg { display: flex; gap: 4px; flex-wrap: wrap; }
.br-chip {
  /* Also used as a plain tag on a <span> (a note's branch, a booking being
     read from another branch), so it must not rely on being a button. */
  display: inline-block;
  min-height: 0; padding: 4px 10px; font-size: .72rem; font-weight: 800;
  border-radius: 8px; background: transparent; box-shadow: none;
  border: 1px solid var(--line); color: var(--muted); letter-spacing: .03em;
}
.br-chip:hover { box-shadow: none; background: var(--neutral-bg); color: var(--text); }
/* branch colours: ZP brand purple, 8TR teal, RU amber, CS blue */
.br-chip.on { color: #fff; }
.br-chip.on.br-zp { background: #470054; border-color: #470054; }
.br-chip.on.br-8tr { background: #0e7490; border-color: #0e7490; }
.br-chip.on.br-ru { background: #a13d63; border-color: #a13d63; }
.br-chip.on.br-cs { background: #0b5fa5; border-color: #0b5fa5; }
.br-chip.on.br-pt { background: #166534; border-color: #166534; }
.br-chip.on:hover { opacity: .92; }
/* ALL is not a branch, so it takes the neutral ink rather than a branch hue */
.br-chip.on.br-all { background: var(--text); border-color: var(--text); }
/* Order Form list (Dr Daniel, 2026-09-06: "remove the label's filling
   colour"): branch and status read as plain coloured TEXT — the branch
   keeps its hue as ink, the status badge keeps its ink and dot only. */
.br-plain { font-weight: 800; letter-spacing: .03em; font-size: .78rem; }
.br-plain.br-zp { color: #470054; }
.br-plain.br-8tr { color: #0e7490; }
.br-plain.br-ru { color: #a13d63; }
.br-plain.br-cs { color: #0b5fa5; }
.br-plain.br-pt { color: #166534; }
.badge.badge-plain { background: transparent !important; border: none !important; padding: 0; }
/* Draft orders wear a light-YELLOW wash across the row, and the word
   Draft itself goes orange (Dr Daniel, 2026-09-06: "Draft column pls
   make it yellow. The colour of the word 'Draft' orange colour." —
   superseding the light-blue wash from earlier the same day). */
tr.row-draft td { background: #fdf6df; }
tr.row-draft.click:hover td { background: #f9edc7; }
tr.row-draft .badge.badge-plain { color: #d97706; }
/* A DELIVERED order recedes — the whole row slightly transparent but
   still clickable; pointing at it brings it back for reading
   (Dr Daniel, 2026-09-06). */
/* One step fainter than the first cut (Dr Daniel, 2026-09-06: "slightly
   more transparent. Probably about semi transparent"). */
tr.row-done td { opacity: .45; }
tr.row-done.click:hover td { opacity: .95; }

/* ---- configurator: start over (2026-08-28) ---------------------------- */
.cfg-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.cfg-head h1 { margin: 0; flex: 1; }
.cfg-reset { white-space: nowrap; }
/* The configurator: build form beside the preview on a desk, stacked on a
   phone (Dr Daniel, 2026-09-14). The preview only follows the scroll where
   there is a second column for it to follow in — pinned on a phone it would
   sit on top of the very form it is previewing. */
.cfg-grid { grid-template-columns: minmax(0, 1fr); align-items: start; }
@media (min-width: 900px) {
  .cfg-grid { grid-template-columns: minmax(320px, 1fr) minmax(320px, 1fr); }
  .cfg-preview { position: sticky; top: 12px; max-height: calc(100vh - 24px); overflow-y: auto; }
}

/* ---- the case-note editor: card right, history left (2026-08-28) ------- */
/* The clinic reads a consultation with the previous ones beside it, so the
   editor puts them there rather than behind a tab. Below 1000px the grid
   collapses and the rail falls under the card, which is the stacked order the
   phone had before. */
.cn-split { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 16px; align-items: start; }
/* The rail stays put while the card scrolls (Dr Daniel, 2026-08-29: "the side
   tab pls freeze it"). It was already sticky, but at top:0 — which is where the
   modal's own sticky header sits, so the rail parked BEHIND it and its first
   lines were hidden the moment anything scrolled. It now stops just below the
   header, and is tall enough to fill the rest of the screen. */
.cn-history {
  position: sticky; top: 68px; max-height: calc(92vh - 96px); overflow-y: auto;
  padding-right: 10px; border-right: 1px solid var(--line);
}
.cn-editor { min-width: 0; }
.cn-h {
  margin: 4px 0 6px; font-size: .72rem; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; color: var(--brand);
}
.cn-h + .cn-h, .cn-list + .cn-h, table + .cn-h, p + .cn-h { margin-top: 16px; }
.cn-list { display: flex; flex-direction: column; gap: 6px; }
/* Each past note is a button: it opens read-only, where it can be amended or
   withdrawn. Nothing in this rail is edited in place. */
.cn-item {
  width: 100%; text-align: left; display: block; min-height: 0;
  padding: 8px 10px; border-radius: 10px; box-shadow: none;
  background: var(--card); border: 1px solid var(--line); color: var(--text);
  font-weight: 400; font-size: .84rem;
}
.cn-item:hover { background: var(--brand-tint); border-color: var(--brand); }
.cn-item.on { border-color: var(--brand); background: var(--brand-tint); }
.cn-item-head { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.cn-item-sub {
  margin-top: 2px; font-size: .78rem;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* A rail row that carries its own action: the row still takes the width and
   opens the record, the action sits beside it and never inside it (a button
   cannot contain a button). */
/* The row IS the card now, so the delete sits inside the same frame rather
   than floating beside it (Dr Daniel, 2026-08-30). */
.cn-item-row {
  display: flex; align-items: center; gap: 4px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); padding-right: 6px; margin-bottom: 6px;
}
.cn-item-row:hover { border-color: var(--brand); background: var(--brand-tint); }
.cn-item-row .cn-item {
  flex: 1; min-width: 0; border: none; background: transparent; border-radius: 9px;
}
.cn-item-row .cn-item:hover { background: transparent; border-color: transparent; }
.cn-item-row .cn-item-act { flex: 0 0 auto; }
/* Bare inside the card (Dr Daniel, 2026-08-30): no fill and no border - the
   row already draws the box, so a second frame inside it was a box in a box.
   Colour arrives on hover, which is when it matters. */
.cn-item-act {
  flex: none; width: 34px; min-height: 0; padding: 0; border-radius: 9px;
  background: transparent; border: none; color: var(--muted); box-shadow: none;
}
.cn-item-act:hover:not(:disabled) { background: var(--brand-tint); color: var(--brand); box-shadow: none; }
.cn-item-act.danger { color: #b3392f; border-color: transparent; background: transparent; }
.cn-item-act.danger:hover { background: #fbe7e5; border-color: #d9584f; color: #a8322a; }
.cn-item-act:disabled { opacity: .35; cursor: not-allowed; }
.cn-item-act:disabled:hover { background: transparent; border-color: transparent; }
.cn-buys { width: 100%; font-size: .8rem; }
/* Four things the rail can show, one at a time (2026-08-28): notes,
   purchases, the mattress that was built, the Mochi that was built. Icon and
   count only — the heading underneath says which one is open, so the strip
   stays narrow enough for a 300px rail. */
/* The strip stays put while the rail's own list scrolls beneath it (Dr Daniel,
   2026-08-29). .cn-history is itself a scroll box, so without this the four
   buttons scrolled out of their own panel and you had to scroll back up just to
   change what the rail was showing. Opaque, or the list shows through it. */
.cn-seg {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;
  position: sticky; top: 0; z-index: 1;
  background: var(--card); padding-bottom: 8px; margin-bottom: 2px;
}
.cn-tab {
  min-height: 0; padding: 6px 2px; border-radius: 9px; box-shadow: none;
  background: var(--neutral-bg); color: var(--muted);
  border: 1px solid transparent; display: flex; flex-direction: column;
  align-items: center; gap: 1px; line-height: 1;
}
.cn-tab:hover { background: var(--brand-tint); color: var(--brand); }
.cn-tab.on { background: var(--brand); color: #fff; border-color: var(--brand); }
.cn-tab-ic { font-size: 1rem; }
.cn-tab-n { font-size: .66rem; font-weight: 800; }
/* A purchase row puts its amount at the far end so the column of figures
   lines up down the rail. */
.cn-amt { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; }
/* The Mochi build in a popup uses the portal's own markup and classes, so it
   needs no overrides here — squashing the pockets is exactly what stopped it
   looking like the portal. */
.cn-buys td { padding: 3px 4px; border-bottom: 1px solid var(--line); }
/* On a phone the consultation card IS the screen — it replaced a full-screen
   sheet, so it should not sit in a floating box with the day behind it. */
@media (max-width: 700px) {
  .modal-backdrop:has(.cn-split) { padding: 0; }
  .modal:has(.cn-split) {
    border-radius: 0; margin-bottom: 0; min-height: 100vh; max-height: 100vh;
    padding: 0 14px 20px;
  }
}
@media (max-width: 1000px) {
  .cn-split { grid-template-columns: 1fr; }
  /* Card first on a phone, history under it — the order it has always been. */
  .cn-history {
    position: static; top: auto; max-height: none; order: 2;
    border-right: none; border-top: 1px solid var(--line);
    padding-right: 0; padding-top: 12px; margin-top: 12px;
  }
  .cn-editor { order: 1; }
}

/* ---- clinical notes on a customer profile (2026-08-28) ---------------- */
/* Closed, a note is one scannable header line; opened, it is the consultation
   card in the card's own order. */
.note-card {
  border: 1px solid var(--line); border-radius: 12px;
  padding: 9px 12px; margin-bottom: 8px; background: var(--card);
  /* The label/value grid below collapses on the CARD's width, not the
     viewport's: this sits inside a modal, so the two are not the same. */
  container-type: inline-size;
}
.note-card[open] { box-shadow: var(--shadow-sm, 0 1px 3px rgba(0,0,0,.06)); }
.note-head { cursor: pointer; list-style: none; display: flex; align-items: center; flex-wrap: wrap; gap: 2px; }
.note-head::-webkit-details-marker { display: none; }
.note-head::before {
  content: '\25b8'; color: var(--muted); margin-right: 6px;
  transition: transform .15s ease; display: inline-block;
}
.note-card[open] > .note-head::before { transform: rotate(90deg); }
/* Which branch recorded it: a customer's history follows the person, so a
   note from another branch is normal and must be labelled, not hidden. */
.note-br { padding: 1px 7px; font-size: .64rem; margin: 0 2px; }
.note-sec {
  display: flex; align-items: center; gap: 6px;
  margin: 16px 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--line);
  font-size: .74rem; font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase; color: var(--brand);
}
.note-sec:first-child { margin-top: 0; }
/* A category stands above the section headings: same family, one step louder,
   and a rule over it rather than under (Dr Daniel, 2026-09-14). */
.note-cat {
  margin: 22px 0 2px; padding-top: 10px; border-top: 2px solid var(--brand-light, #b98cc4);
  font-size: .82rem; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text);
}
.note-cat + .note-sec { margin-top: 8px; }
.cn-cat { margin-top: 26px; }
.cn-sec { margin: 18px 0 8px; }
/* Current bedding: the pillow on the left, the mattress on the right, each
   question under the last (Dr Daniel, 2026-09-14). One column on a narrow
   screen, where side by side would squeeze six chips into nothing. */
/* Not an even split (Dr Daniel, 2026-09-17): "current pillow answer line
   no need so long, can cut short by 20%, then give the space to current
   mattress" — the mattress side holds the sinking pairs and their remarks,
   the pillow side three short words. */
.cn-bedding { display: grid; grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); gap: 12px 18px; }
@media (max-width: 760px) { .cn-bedding { grid-template-columns: minmax(0, 1fr); } }
.cn-bedcol > * + * { margin-top: 8px; }
/* The bedding columns take the SAME 105px track as every other panel
   (2026-09-17: "Label track pls uniform throughout the case notes"). */
/* Patient history as one sheet (Dr Daniel, 2026-09-14: "make it in one typing
   box … leave the titles there and let us to type the answers"). One border
   round all three sections and a rule under each answer instead of a box round
   it — the page reads as something written on, not something filled in. */
.cn-sheet {
  border: 1px solid var(--line); border-radius: 12px;
  padding: 2px 16px 16px; background: var(--card);
}
.cn-sheet > .cn-sec:first-child { margin-top: 14px; }
/* Four panels on one card: the gap between them does the separating, so the
   heads inside them can be quieter and closer than they were when each one
   had to hold its own section apart. */
.cn-sheet + .cn-cat, .cn-sheet + .cn-sheet { margin-top: 14px; }
.cn-sheet .cn-sec { margin: 16px 0 7px; }
.cn-sheetgrid {
  /* ONE label track for the whole card (Dr Daniel, 2026-09-17: "Label
     track pls uniform throughout the case notes. I saw some gap wider and
     some narrower"). 105px is the longest label that should not wrap —
     "Is the bed sinking?" — measured at the .8rem the labels wear, plus
     rounding headroom; only "Material / Firmness / Duration" wraps, as it
     always has. */
  display: grid; grid-template-columns: 108px minmax(0, 1fr);
  gap: 0 14px; align-items: baseline;
}
.cn-sheetlab { font-size: .8rem; color: var(--muted); }
/* Labelled lines that follow a prose box need the air a heading would have
   given them. */
.cn-sheetgap { margin-top: 16px; }
/* Age, gender and the latex question across, then the three measurements
   under them (Dr Daniel, 2026-09-14). Six label-and-line pairs, three to a
   row, so the answers line up in columns down the panel. */
/* The three-across patient grid keeps its own UNIFORM pair track — 80px
   fits its longest label (Shoulder (cm), 79px) without eating the lines. */
.cn-sheet3 { grid-template-columns: repeat(3, 80px minmax(0, 1fr)); }
@media (max-width: 900px) { .cn-sheet3 { grid-template-columns: repeat(2, 80px minmax(0, 1fr)); } }
@media (max-width: 620px) { .cn-sheet3 { grid-template-columns: minmax(0, 1fr); } }
/* Positions: Supine __ %  Side __ %  Prone __ %, printed once and typed in
   three (Dr Daniel, 2026-09-14). The names are quiet because they are not
   answers — the numbers are. Spinners off: this is a percentage, not a
   quantity anybody wants to click up and down five at a time. */
/* Centred, not baseline-aligned: a right-aligned number box sits its
   baseline lower than a plain word, and the row grew 12px taller than every
   other answer on the panel to hold both. */
.cn-sheet .pct-trio .cn-chips { gap: 2px 20px; align-items: center; }
.cn-sheet .pct-trio .pct-name {
  font-size: .8rem; font-weight: 400; color: var(--muted); margin: 0 3px 0 0;
}
.cn-sheet .pct-trio .pct-box {
  /* .cn-opts input adds a 7px top margin for the free-text box that used
     to sit under a chip group; there is no chip group here. */
  width: 54px; min-height: 30px; text-align: right; padding: 5px 3px; margin: 0;
  -moz-appearance: textfield; appearance: textfield;
}
.cn-sheet .pct-trio .pct-box::-webkit-outer-spin-button,
.cn-sheet .pct-trio .pct-box::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.cn-sheet .pct-trio .pct-pc { font-size: .8rem; color: var(--muted); margin-left: 2px; }
.cn-sheet .pct-trio .pct-legacy { flex-basis: 100%; margin: 2px 0 0; }
/* An empty note still claimed a flex row, which made the Positions line half
   as tall again as every other answer on the panel. */
.cn-sheet .pct-trio .pct-legacy:empty { display: none; }

/* A catalogue picker on a panel line: it keeps its button, drops the frame
   the button used to sit in. */
.cn-sheet .cn-opts { border: 0; padding: 0; }
.cn-sheet .cn-opts .cn-chips { padding: 3px 0; }
.cn-linewrap { display: block; }
/* The phrases this clinic writes, offered under the box while it has focus
   (Dr Daniel, 2026-09-14). Quiet by default — they are a shortcut, not an
   instruction — and element-qualified so the house button styling does not
   paint them like something to be pressed. */
.cn-suggest { display: flex; flex-wrap: wrap; gap: 5px; margin: 6px 0 2px; }
button.cn-sugg {
  background: transparent; border: 1px solid var(--line); border-radius: 999px;
  color: var(--muted); font-size: .74rem; font-weight: 600;
  padding: 2px 10px; min-height: 0; box-shadow: none; flex: 0 0 auto;
}
button.cn-sugg:hover {
  border-color: var(--brand); color: var(--brand); background: var(--brand-tint);
}
/* A line, not a box: the frame drops away and only the rule under the answer
   is left. Element-qualified base rules (input, select, textarea) lose to a
   class, so this needs no !important — but it DOES need the radius and the
   shadow cleared, or the old box shows through at the corners. */
.cn-line {
  border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
  background: transparent; box-shadow: none;
  width: 100%; padding: 6px 2px; min-height: 34px;
}
.cn-sheet textarea.cn-line { min-height: 62px; resize: vertical; }
.cn-line:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 1px 0 0 var(--brand);
}
/* A signed note is read, not typed: the rules fade but the words do not. */
.cn-line:disabled { color: var(--text); opacity: 1; border-bottom-style: dotted; }
@media (max-width: 620px) { .cn-sheetgrid { grid-template-columns: minmax(0, 1fr); } }

/* Who served and when ride the camera's row, each label in front of its box so
   the row stays one line tall (Dr Daniel, 2026-09-14). */
/* Age is two digits; the two questions beside it are chips. A narrow track
   for the box and an equal share for each chip group (Dr Daniel, 2026-09-14). */
.cn-patient-a {
  display: grid; grid-template-columns: 150px minmax(0, 1fr) minmax(0, 1fr);
  gap: 14px 16px; align-items: start;
}
@media (max-width: 860px) { .cn-patient-a { grid-template-columns: minmax(0, 1fr); } }
/* The camera, the gallery, who served and when ride the note's title row
   (Dr Daniel, 2026-09-14): the holder takes the room left after the title and
   keeps its own row one line tall. */
/* The title row carries what the note IS; the body opens with what you do to
   it (Dr Daniel, 2026-09-14, swapping the two). */
.cn-headtools { flex: none; margin-left: auto; }
.cn-headtools .cn-typebar { margin: 0; flex-wrap: nowrap; }
.cn-toptools { margin-bottom: 14px; }
/* The item leads every order card (Dr Daniel, 2026-09-14): what it IS, above
   where it goes. It carries the count, so it reads a little larger than the
   rows under it. */
.fo-itemline { font-size: 1.02rem; color: var(--text); }
.fo-itemhead {
  display: flex; align-items: baseline; gap: 10px; margin: 0 0 10px;
  padding-bottom: 8px; border-bottom: 1px solid var(--line);
}
.fo-itemhead .muted { font-size: .78rem; }

/* One drawing per unit, SIDE BY SIDE (Dr Daniel, 2026-09-14). Never stacked:
   the diagram builds bottom-up, so two 2.5cm toppers in a column look like one
   5cm topper, which is the whole thing being guarded against. Each drawing
   wears its own frame and says which unit it is. */
/* A grid, not a flex row: squeezed into a narrow column the drawing ran its
   "Embroidery Side" and "Label Side" captions together, and an unreadable
   drawing is a worse fault than a wrapped one. Each unit gets the width the
   diagram needs; they sit side by side where there is room and stack where
   there is not. Stacking is safe because the FRAME and the "Unit 1 of 2"
   caption are what stop two units reading as one taller build — the
   side-by-side arrangement was only ever the belt to that pair of braces. */
.fo-units {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 14px; align-items: start;
}
.fo-unit {
  margin: 0; border: 1px solid var(--line); border-radius: 12px;
  padding: 8px 10px 6px; background: var(--card); break-inside: avoid;
}
/* Top LEFT of the frame and in the brand ink, not grey underneath it (Dr
   Daniel, 2026-09-14: "make it at the top left of the preview component. And
   the font colour has to be obvious"). Which unit you are looking at is the
   first thing to know about a drawing, not a footnote to it. */
.fo-unit figcaption {
  margin: 0 0 8px; text-align: left; font-size: .82rem; font-weight: 800;
  letter-spacing: .05em; text-transform: uppercase; color: var(--brand);
}
@media print { .fo-unit figcaption { color: #000; } }
.fo-unit-one { display: inline-block; }
/* What was bought, under the drawing. It carries its own count, so it reads
   "2.5cm Lunox Latex Topper (Queen) x6" (Dr Daniel, 2026-09-14). */
.fo-unitlabel {
  margin: 8px 0 0; font-weight: 700; font-size: .92rem; color: var(--text);
}
@media print {
  .fo-units { gap: 10px; }
  .fo-unit { border: 1px solid #000; }
  .fo-unit figcaption { color: #000; }
}

/* How many to MAKE. The spec beside it describes one unit, so this has to
   carry more weight than a number in a list (Dr Daniel, 2026-09-14: two 2.5cm
   toppers must never be built as one 5cm one). Printed as well as on screen —
   the bench works off the paper. */
.fpo-units {
  color: var(--bad); font-weight: 800; letter-spacing: .04em;
}
/* Black on paper, not red: a mono printer renders red as a mid grey, which
   would make the most important line on the work order the FAINTEST thing on
   it. The weight carries it there instead. */
@media print { .fpo-units { color: #000; } }

/* A button on its own, no longer inside a panel (Dr Daniel, 2026-09-14). */
.cn-pressbtn { margin: 16px 0 4px; }
.cn-toptools .cn-toprow { margin: 0; }
/* On a phone the head WRAPS (Dr Daniel, 2026-09-14: "case note mobile version
   not user friendly"). Four pills beside a customer's name need 516px; a phone
   head has 327px, which squeezed the name to 42px and pushed the ✕ clean off
   the screen — the note could not be closed at all. The name and the ✕ keep
   the first line; the pills take their own underneath, two and two. They are
   still at the top of the box, which is where they were asked for. */
@media (max-width: 700px) {
  .modal .modal-head { flex-wrap: wrap; row-gap: 8px; }
  .modal .modal-head h2 { flex: 1 1 auto; min-width: 0; font-size: 1rem; }
  .cn-headtools { order: 3; flex: 1 0 100%; margin-left: 0; }
  .cn-headtools .cn-typebar { flex-wrap: wrap; gap: 6px; }
  .cn-headtools .cn-typebar button {
    flex: 1 1 calc(50% - 3px); min-width: 0; white-space: normal;
    /* A thumb, not a mouse pointer. */
    min-height: 40px;
  }
  .modal .modal-x { width: 40px; height: 40px; }
  /* Save Draft is `small` and came out 10px shorter than the Sign and Bill
     beside it — the one button in the foot you press to keep your work was
     the hardest of the three to hit. */
  /* `.modal .actions > button.small` sets 34px at (0,3,1), so a plainer
     selector here lost the tie. Matched, and later in the file. */
  .modal .actions button, .modal .actions > button.small { min-height: 42px; }

  /* The camera row: the spacer has nothing to push against here, and the two
     fields are easier to type into at full width. */
  .cn-toptools .cn-gap { display: none; }
  .cn-toptools .cn-toprow button { min-height: 40px; }
  .cn-toptools .cn-by { flex: 1 1 100%; }
  .cn-toptools .cn-by input, .cn-toptools .cn-by select {
    flex: 1 1 auto; max-width: none; min-height: 38px; font-size: .9rem;
  }

  /* Writing surfaces and the things you tap on them. */
  .cn-line { min-height: 40px; }
  .cn-sheet textarea.cn-line { min-height: 76px; }
  .cn-sheet .pct-trio .pct-box { min-height: 38px; width: 62px; }
  .cn-sheet .cn-pick { min-height: 44px; }
  button.cn-sugg { min-height: 32px; padding: 4px 12px; font-size: .8rem; }
  /* 16px stops iOS zooming the page when a field takes focus. */
  .cn-sheet input.cn-line, .cn-sheet textarea.cn-line { font-size: 16px; }
}
.cn-toprow { align-items: center; }
.cn-by { display: flex; align-items: center; gap: 6px; }
.cn-by label { margin: 0; font-size: .72rem; color: var(--muted); white-space: nowrap; }
.cn-by input, .cn-by select { width: auto; max-width: 170px; min-height: 34px; padding: 4px 9px; font-size: .84rem; }
.note-sec-ic { font-size: .9rem; line-height: 1; }
/* ONE label column for the whole card (Dr Daniel, 2026-09-14: "why is the case
   notes so messy"). Each section used to size its own, so the answers started
   at a different place in every one of them; a fixed column lines them all up
   down the card. */
.note-fields {
  display: grid; grid-template-columns: 210px minmax(0, 1fr);
  gap: 5px 16px; margin: 0; font-size: .87rem; align-items: baseline;
}
/* Current bedding is two columns, because it is two things. */
.note-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 24px; }
@container (max-width: 640px) { .note-cols { grid-template-columns: minmax(0, 1fr); } }
.note-subsec {
  margin: 0 0 4px; font-size: .72rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
/* An answer whose heading already named it: no label column at all, and the
   line breaks the clinician typed are kept (Dr Daniel, 2026-09-14). */
.note-solo {
  margin: 0 0 2px; font-size: .87rem; white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* Inside a column the label track is narrower — the words are shorter there. */
.note-fields-sub { grid-template-columns: 150px minmax(0, 1fr); column-gap: 12px; }
.note-fields dt { color: var(--muted); }
.note-fields dt .ic { vertical-align: -2px; }
/* An icon-only action in a modal's foot keeps a target to press. */
.actions button.iconlabel { min-width: 40px; }
/* The state and the version sit at the right end of the title row. */
.modal-head .note-headstate {
  flex: none; margin-left: auto; display: inline-flex; align-items: center; gap: 8px;
}
.note-fields dd { margin: 0; white-space: pre-wrap; font-weight: 600; }
@container (max-width: 460px) { .note-fields { grid-template-columns: 1fr; gap: 0 0; }
  .note-fields dd { margin-bottom: 6px; } }
/* Uploads on a past note (Dr Daniel, 2026-09-07): thumbnails in a row,
   tap to open full size; non-image files as link chips. */
.note-atts { margin-top: 10px; }
.note-atts-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; }
.note-atts-row img {
  max-height: 150px; max-width: 220px; border-radius: 10px; display: block;
  border: 1px solid var(--line); cursor: zoom-in;
}
/* An upload tile in the editor carries its own ✕ remove button, top-right
   (Dr Daniel, 2026-09-07) — drafts only; the server refuses once signed. */
.att-tile { position: relative; text-align: center; }
.att-tile .att-del {
  position: absolute; top: -6px; right: -6px; z-index: 2;
  width: 22px; height: 22px; min-height: 0; padding: 0; border-radius: 50%;
  background: #fff; color: #99291f; border: 1px solid #e6c9c5;
  font-size: .78rem; font-weight: 800; line-height: 1; box-shadow: 0 1px 4px rgba(0,0,0,.18);
}
.att-tile .att-del:hover { background: #fbe4e2; }
/* Full-size image viewer with a way out (Dr Daniel, 2026-09-07: "Allow us
   to exit from the full page") — ✕, backdrop click, or Esc. */
.imgview {
  position: fixed; inset: 0; z-index: 900; background: rgba(20, 12, 24, .92);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
}
.imgview-body { display: flex; align-items: center; justify-content: center; width: 100%; flex: 1; padding: 46px 16px 0; }
.imgview-body img { max-width: 96vw; max-height: 82vh; border-radius: 8px; box-shadow: 0 18px 50px rgba(0,0,0,.5); }
.imgview-cap { color: #e9e2ec; font-size: .8rem; padding-bottom: 14px; text-align: center; }
.imgview-x {
  position: absolute; top: 14px; right: 16px; z-index: 2;
  width: 40px; height: 40px; min-height: 0; padding: 0; border-radius: 50%;
  background: #fff; color: var(--text); border: none; font-size: 1.05rem; font-weight: 800;
  box-shadow: 0 4px 16px rgba(0,0,0,.35); cursor: pointer;
}
.imgview-x:hover { background: #f0ebf3; }
.note-older { margin-top: 12px; border-top: 1px dashed var(--line); padding-top: 8px; }
.note-older > summary { cursor: pointer; font-size: .8rem; }

/* ---- appointment popup: who they are, and what they have bought ------- */
/* The customer's name is a link into their record (2026-08-28). */
.cust-link { color: var(--brand); text-decoration: none; border-bottom: 1px solid transparent; }
.cust-link:hover { border-bottom-color: currentColor; }
.appt-buys { margin-top: 14px; border-top: 1px solid var(--line); padding-top: 10px; }
.buys-head { display: flex; align-items: baseline; gap: 10px; }
.buys-head h3 { margin: 0; flex: 1; }
.buys-total { font-size: .82rem; font-weight: 700; color: var(--brand); white-space: nowrap; }
.buys-table { width: 100%; margin-top: 6px; }
.buys-table td { padding: 5px 6px; border-bottom: 1px solid var(--line); font-size: .85rem; }
.buys-table tr:last-child td { border-bottom: none; }
.buys-table tr.click { cursor: pointer; }
.buys-table tr.click:hover td { background: var(--brand-tint); }
/* A pre-pilot purchase has no invoice here to open — say where it lives. */
.buys-kumo {
  margin-left: 6px; padding: 1px 6px; border-radius: 6px;
  background: var(--neutral-bg); color: var(--muted);
  font-size: .64rem; font-weight: 700; letter-spacing: .04em;
}
.buys-owed { display: block; font-size: .68rem; font-weight: 700; color: #b45309; }
/* The KumoDoc sales-order number, kept under its invoice number. */
.so-sub { font-size: .68rem; letter-spacing: .02em; }

/* ---- the online channel's calendar: which branch am I reading? --------- */
/* Only ever rendered in CS (2026-08-28). It sits beside Today in the header,
   nudged away from it so the two do not read as one control. */
.cal-branch { align-items: center; gap: 4px; margin-right: 4px; }
.cal-branch .cb-label {
  font-size: .68rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); margin-right: 2px;
}
/* One column per branch: the header carries the branch's own colour as a
   band, so a column is identifiable without reading its label. */
.cal-branches .hd.br-hd { color: #fff; border-bottom: none; }
.cal-branches .hd.br-hd strong { font-size: .88rem; letter-spacing: .03em; }
.cal-branches .hd.br-zp { background: #470054; }
.cal-branches .hd.br-8tr { background: #0e7490; }
.cal-branches .hd.br-ru { background: #a13d63; }
/* The bookings count rides the SAME row as the branch name, tucked in
   the right corner (Dr Daniel, 2026-09-06: "xx bookings dun use up a
   row"). */
.cal-branches .hd.br-hd { position: relative; }
.cal-branches .hd .hd-count {
  position: absolute; top: 6px; right: 8px;
  font-size: .62rem; font-weight: 600; opacity: .82; letter-spacing: .02em;
}

/* ---- register popup: allocation chips + payment rows (2026-08-27) ---- */
.alloc-chips { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* No fill (Dr Daniel, 2026-09-02): the name is a label, not a slab. */
.alloc-chip {
  display: inline-flex; align-items: center; gap: 7px;
  background: none; color: var(--brand); font-weight: 700;
  border-radius: 999px; padding: 6px 14px 6px 0; font-size: .88rem;
}
/* As a button it must lose the global button chrome, or a pill turns back into
   a slab (Dr Daniel, 2026-08-29). The pencil sits a shade back so the name
   stays the thing you read. */
button.alloc-chip {
  border: none; box-shadow: none; min-height: 0; cursor: pointer;
  font-family: inherit; line-height: 1.3;
}
button.alloc-chip:hover { background: var(--brand-tint); box-shadow: none; }
.alloc-chip .ic:last-child { opacity: .65; }
button.alloc-chip:hover .ic:last-child { opacity: 1; }
.pay-row {
  display: flex; align-items: center; gap: 12px;
  border: 1px solid var(--line); border-radius: 12px;
  padding: 8px 12px; margin-bottom: 6px;
}
.pay-row .pay-method { font-weight: 700; }
.pay-row .pay-amt { margin-left: auto; font-weight: 800; }

/* ---- embedded pages in popups (2026-08-27) ---- */
.embed-frame { width: 100%; height: 76vh; border: 0; display: block; border-radius: 8px; }
/* A full-screen popup: the whole viewport, no rounded card, and the frame
   takes every pixel under the title bar (2026-09-13). The BACKDROP carries
   its own class — an earlier `:has(> .modal.full)` selector was silently not
   matching, which left the 4vh padding on and the window short. */
/* Almost the whole screen, but still a CARD (Dr Daniel, 2026-09-13: "make it
   still rounded corner") — a hair of backdrop all round so the corners read. */
.modal-backdrop.full { padding: 10px; overflow: hidden; }
.modal.full {
  max-width: none; width: 100%; height: 100%; max-height: 100%; margin: 0;
  border-radius: 18px; padding: 0 14px 10px; display: flex; flex-direction: column;
}
.modal.full .modal-head { margin-bottom: 8px; flex: none; }
.embed-frame.full { flex: 1 1 auto; height: auto; min-height: 0; border-radius: 12px; }
body.embed .sidebar, body.embed .topbar { display: none !important; }
body.embed .main { margin-left: 0 !important; }
body.embed .shell.rail .main { margin-left: 0 !important; }
body.embed .content { padding: 8px 14px; }
/* A page inside a popup fills the popup (Dr Daniel, 2026-09-13, of the
   full-screen Pressure Mapping window: "dun have space here ... make
   everything extended to this area"). The ordinary 1320px reading width
   left a dead band down the right of a full-screen popup; in the narrow
   960px popups the cap never bound anyway, so nothing else moves. */
body.embed .content { max-width: none; }

/* ---- calendar header line (2026-08-27) ---- */
.cal-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.cal-head h1 { margin: 0; }
/* Three-zone calendar header (Dr Daniel, 2026-09-05): title + branch chips
   left, the date stepper dead centre, customer search right. The whole row
   is inset by the rail's width so all three sit WITHIN the calendar's own
   width (Dr Daniel, later that day) — left edge over the time column,
   right edge flush with the last branch column. */
/* The 224px left margin that used to clear the rail is GONE with the rail
   (Dr Daniel, 2026-09-06: "ALL THE WAY TO THE LEFT OF THE CALENDAR"). */
.cal-head3 { display: grid; grid-template-columns: 1fr auto 1fr; gap: 10px; }
.ch-left { display: flex; align-items: center; gap: 14px; min-width: 0; }
/* The page title moved onto the RAIL, above the mini calendar (Dr Daniel,
   2026-09-05: "MOVE THIS HEADER TO BE ABOVE THE MINI CALENDAR") — sized
   for the 224px column, one line, never a wrapped tower. */
.cal-rail h1 {
  margin: 8px 0 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: 1.15rem;
  /* flex:none — the rail is a flex column and a shrinkable h1 with hidden
     overflow collapses to 0px when the rail runs tall. */
  flex: none;
}
/* The compact control cluster on the calendar's top row (Dr Daniel,
   2026-09-06): 1D·3D·1W chips, small By staff / Block / Roster words. */
/* ONE row for the whole control strip (Dr Daniel, 2026-09-06: "Dun all
   stack here, make it in 1 grid row") — tools left, stepper, search. */
.cal-head3.cal-head-flex {
  display: flex; align-items: center; gap: 6px; flex-wrap: nowrap;
  /* FROZEN under the topbar (Dr Daniel, 2026-09-06: "it shouldnt move
     when we scroll up and down") — same treatment as the popups' bars.
     Above the calendar's frozen branch header (100): this toolbar's
     stacking context carries the date popup, which the header briefly
     buried when it rose to 100 ("HALO FIX THIS BUG"). */
  position: sticky; top: 59px; z-index: 110;
  background: var(--bg, #f4f2f7); padding: 3px 0;
}
/* Tools hard LEFT, the date stepper at the exact MIDDLE of the row,
   branch toggles + search right (Dr Daniel, 2026-09-06). */
.cal-head3.cal-head-flex { position: sticky; }
.cal-head3.cal-head-flex .ch-left { flex: 0 1 auto; min-width: 0; }
.cal-head3.cal-head-flex .ch-mid {
  position: absolute; left: 50%; transform: translateX(-50%);
}
.cal-head3.cal-head-flex .ch-right {
  margin-left: auto; min-width: 110px; display: flex; align-items: center;
  justify-content: flex-end; gap: 6px;
}
@media (max-width: 1100px) {
  .cal-head3.cal-head-flex .ch-mid { position: static; transform: none; }
}
.cal-head3.cal-head-flex .ch-right input { max-width: 170px; }
.cal-head3.cal-head-flex .ch-right input { min-height: 32px; font-size: .8rem; }
.cal-head3.cal-head-flex .cal-step-date { font-size: .88rem; padding: 4px 8px; }
/* The CS branch chips squeeze with everything else on the one row. */
.cal-tools .branch-seg .br-chip { padding: 3px 7px; font-size: .66rem; }
@media (max-width: 1100px) { .cal-head3.cal-head-flex { flex-wrap: wrap; } }
.cal-tools { display: flex; align-items: center; gap: 4px; flex-wrap: nowrap; min-width: 0; }
/* The page title rides the control row now that the rail is gone
   (Dr Daniel, 2026-09-06) — one compact line, never a wrapped tower. */
.cal-tools h1 {
  margin: 0 4px 0 0; font-size: .94rem; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 122px; flex: 0 1 auto;
}
.cal-full .cal-main { width: 100%; }
/* The view is ONE small dropdown now (Dr Daniel, 2026-09-06):
   1D / 3D / 1W / By staff. */
.view-sel {
  min-height: 0; width: auto; padding: 4px 6px; font-size: .74rem;
  font-weight: 700; color: #57505e; border-radius: 8px;
  border: 1px solid #e0dce4; background: #f4f3f5; box-shadow: none; cursor: pointer;
}
/* Block wears the blocked band's own light-purple hatch tones. */
.block-btn {
  background: repeating-linear-gradient(45deg, #f5ecf7, #f5ecf7 7px, #e6d7ee 7px, #e6d7ee 14px);
  color: #5b4a68; border: 1px solid #d3bfdf; box-shadow: none; font-weight: 700;
}
.block-btn:hover { filter: brightness(.97); box-shadow: none; }
.cal-tools .cal-tool { min-height: 0; padding: 3px 8px; font-size: .72rem; white-space: nowrap; }
.ch-mid { justify-self: center; position: relative; }
.ch-right { justify-self: end; min-width: 0; }
/* Mid widths: the search row drops under the title/stepper row instead of
   crushing the left column. */
@media (max-width: 1200px) {
  .cal-head3 { grid-template-columns: 1fr auto; }
  .ch-right { grid-column: 1 / -1; justify-self: end; }
}
.cal-branch { flex-wrap: nowrap; }
.cb-sep { width: 1px; align-self: stretch; margin: 3px 5px; background: var(--line); flex: none; }
/* The date button drops a one-month picker right under the stepper. */
button.cal-step-date { background: none; border: none; box-shadow: none; cursor: pointer; color: var(--text); }
button.cal-step-date:hover { background: var(--brand-tint); border-radius: 8px; }
.date-pop {
  /* Slimmed 2026-09-06 ("make the calendar smaller"); bottom kept tight —
     every collapsible calendar now ends on the bare Clear word. */
  position: absolute; top: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  z-index: 120; width: 232px; padding: 8px 12px 6px;
  background: #fff; border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 16px 40px rgba(42, 31, 46, .22);
}
/* The rail's mini-month is sized for a 170px sidebar — 22px cells and .68rem
   digits read as a BLANK WHITE BOX in a floating popup (Dr Daniel,
   2026-09-05: "where's the collapsible calendar?"). The popup's copy is a
   real date picker: big title, 38px day cells, readable digits. */
.date-pop .mc-head { margin-bottom: 4px; }
.date-pop .mc-title { font-size: .8rem; }
.date-pop .mc-nav { font-size: 1.05rem; padding: 1px 9px; }
.date-pop .mc-grid { gap: 2px; }
.date-pop .mc-wd { font-size: .64rem; padding-bottom: 2px; }
/* 28px cells ≈ the column width, so the chosen day reads as a CIRCLE. */
.date-pop .mc-day { height: 28px; font-size: .76rem; border-radius: 999px; }
/* Search customer on the header's right, results dropping over the grid. */
.head-cust { position: relative; }
.head-cust-in { width: 220px; min-height: 36px; }
.head-cust-drop {
  position: absolute; top: calc(100% + 4px); right: 0; z-index: 80; width: 280px;
  background: #fff; border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 14px 34px rgba(42, 31, 46, .18); padding: 6px; margin-top: 0;
}
/* .cust-find's display:flex beats the hidden ATTRIBUTE (same specificity,
   later rule) — the exact trap that once left the factory chat box open.
   Without this, the empty dropdown floats as a white sliver under the
   search bar from the moment the page loads (Dr Daniel, 2026-09-05:
   "Theres bugs"). */
.head-cust-drop[hidden], .date-pop[hidden] { display: none !important; }
/* Order Form title row: smaller chips (Dr Daniel, 2026-09-06: "These
   buttons make it smaller") and the hotel-style range picker. */
.of-title-row .br-chip { padding: 2px 8px; font-size: .66rem; }
.mc-day.inrange { background: var(--brand-tint, #f5ecf7); }

/* Clicking a side tab swaps only the PAGE — the rail must not redraw
   (Dr Daniel, 2026-09-06: "it shall just refresh the page, not the side
   tab"). Cross-document view transitions: the sidebar and topbar carry
   their own names, so their unchanged pixels ride across the navigation
   frozen while the content area alone cross-fades. Browsers without the
   feature simply keep doing full loads. */
@view-transition { navigation: auto; }
.sidebar { view-transition-name: lx-rail; }
.topbar { view-transition-name: lx-topbar; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}
/* The picked CATEGORY tab fills brand purple — a different family from
   the dark branch ALL (Dr Daniel, 2026-09-06). */
.br-chip.on.tab-chip { background: var(--brand, #470054); border-color: var(--brand, #470054); }
.head-cust-drop:empty { display: none; }

/* ---- collapsed-rail alignment (2026-08-27) ----
   Two silent uglifiers: the rail's own vertical scrollbar ate 16px and pushed
   every icon off-centre, and the branch chips — invisible at opacity 0 —
   still held ~120px of dead space under the logo. The scrollbar hides while
   collapsed (the flyout keeps it), invisible things now take no space, the
   icon-label gap collapses so icons sit dead centre, and group separators
   become short centred ticks instead of edge-to-edge lines. */
.shell.rail .sidebar:not(:hover):not(:focus-within),
.shell.rail.rail-hold .sidebar:hover { scrollbar-width: none; }
.shell.rail .sidebar:not(:hover):not(:focus-within)::-webkit-scrollbar,
.shell.rail.rail-hold .sidebar:hover::-webkit-scrollbar { display: none; }
.shell.rail .sidebar:not(:hover):not(:focus-within) .logo .sub,
.shell.rail .sidebar:not(:hover):not(:focus-within) .foot,
.shell.rail.rail-hold .sidebar:hover .logo .sub,
.shell.rail.rail-hold .sidebar:hover .foot { display: none; }
.shell.rail .sidebar:not(:hover):not(:focus-within) nav a,
.shell.rail.rail-hold .sidebar:hover nav a { gap: 0; }
.shell.rail .sidebar:not(:hover):not(:focus-within) nav a .lbl,
.shell.rail.rail-hold .sidebar:hover nav a .lbl { display: none; }
.shell.rail .sidebar:not(:hover):not(:focus-within) nav .nav-group + .nav-group,
.shell.rail.rail-hold .sidebar:hover nav .nav-group + .nav-group {
  border-top: none; margin-top: 3px; padding-top: 3px; position: relative;
}
.shell.rail .sidebar:not(:hover):not(:focus-within) nav .nav-group + .nav-group::before,
.shell.rail.rail-hold .sidebar:hover nav .nav-group + .nav-group::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 22px; height: 2px; border-radius: 2px; background: var(--brand-tint-2);
}

/* ============================================================================
   DESIGN PASS (2026-08-27) — cohesive polish over the whole system.
   Principles: brand purple leads; teal/amber/blue accents give the pages air
   without stealing meaning (red stays reserved for destructive); motion is
   small, quick and one-shot; everything switches off under reduced-motion.
   ========================================================================== */

:root {
  --accent-teal: #0e7490;  --accent-teal-bg: #e6f4f7;
  --accent-amber: #b45309; --accent-amber-bg: #fdf3e3;
  --accent-blue: #0b5fa5;  --accent-blue-bg: #e8f1fa;
  --grad-brand: linear-gradient(135deg, #5c0d6e 0%, #470054 55%, #36003f 100%);
  --shadow: 0 1px 3px rgba(71, 0, 84, .05), 0 6px 18px rgba(71, 0, 84, .06);
  --shadow-lift: 0 6px 14px rgba(71, 0, 84, .10), 0 16px 36px rgba(71, 0, 84, .10);
}

/* A quiet, colourful ground: lavender wash with a teal breath top-right and a
   warm one bottom-left. Fixed, so scrolling content glides over it. */
body {
  background:
    radial-gradient(1100px 480px at 88% -8%, rgba(14, 116, 144, .06), transparent 60%),
    radial-gradient(900px 540px at -8% 108%, rgba(180, 83, 9, .05), transparent 55%),
    linear-gradient(168deg, #f8f4fb 0%, #f3f0f8 55%, #f0f2f8 100%);
  background-attachment: fixed;
}

/* Glassy topbar — content slides beneath it. */
.topbar {
  background: rgba(255, 255, 255, .74);
  backdrop-filter: blur(10px) saturate(1.15);
  -webkit-backdrop-filter: blur(10px) saturate(1.15);
  border-bottom: 1px solid rgba(71, 0, 84, .07);
}

/* Sidebar: a whisper of gradient, links that answer the cursor, the active
   page as a gradient pill. */
.sidebar { background: linear-gradient(178deg, #fff 0%, #fbf8fd 70%, #f8f3fb 100%); }
.sidebar nav a { transition: background .12s, color .12s, transform .12s; border-radius: 10px; }
.sidebar nav a:hover { transform: translateX(2px); }
.shell.rail .sidebar nav a:hover { transform: none; }
.sidebar nav a.active {
  background: var(--grad-brand); color: #fff; font-weight: 600;
  box-shadow: 0 4px 12px rgba(71, 0, 84, .28);
}

/* Headings: the h1 wears the brand gradient; content h2s get a colour tick. */
.content h1 {
  background: var(--grad-brand);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: var(--brand);
  width: fit-content;
}
.content h2 { position: relative; padding-left: 13px; }
.content h2::before {
  content: ""; position: absolute; left: 0; top: .16em; bottom: .16em; width: 5px;
  border-radius: 4px; background: var(--grad-brand);
}
/* Modals keep their own flat headings — the tick belongs to the page. */
.modal h2::before { content: none; }
.modal h2 { padding-left: 0; }

/* Cards: hairline edge, gentle lift when the cursor arrives. */
.card { border: 1px solid rgba(71, 0, 84, .05); }
.card:hover { box-shadow: var(--shadow-lift); border-color: rgba(71, 0, 84, .10); }

/* Buttons: a small rise on hover, settle on press. The month-grid days and
   calendar chips stay still — motion there reads as jitter, not delight. */
button, .btn { transition: background .12s, color .12s, transform .09s, box-shadow .15s, border-color .12s, filter .12s; }
button:hover:not(:disabled):not(.mc-day):not(.mc-nav):not(.pt-chip):not(.br-chip):not(.modal-x):not(.rail-btn):not(.name-link) { transform: translateY(-1px); }
button:active:not(:disabled) { transform: translateY(0) scale(.977); }
button.primary-action { background: var(--grad-brand); box-shadow: 0 6px 18px rgba(61, 0, 71, .38); }
button.primary-action:hover { background: var(--grad-brand); filter: brightness(1.12); }

/* Pick buttons and header tiles float a little on hover. */
.pickbtn:hover { box-shadow: 0 6px 16px rgba(71, 0, 84, .10); }
button.hdr-tile:hover { box-shadow: 0 8px 20px rgba(71, 0, 84, .10); }

/* Tables: headers in muted brand, a soft wash under the pointed-at row. */
.content th { color: var(--brand-light); }
.content table tr:hover > td { background: rgba(71, 0, 84, .022); }

/* Inputs: a clear brand focus ring. */
input:focus, select:focus, textarea:focus {
  border-color: var(--brand-light);
  box-shadow: 0 0 0 3px rgba(71, 0, 84, .10);
  outline: none;
}

/* Content entrance: a quick staggered fade — opacity only, so the frequent
   in-place re-renders (saving a draft, moving a booking) stay calm. */
@keyframes rise-in { from { opacity: 0; } }
.content > * { animation: rise-in .2s ease-out both; }
.content > *:nth-child(2) { animation-delay: .04s; }
.content > *:nth-child(3) { animation-delay: .08s; }
.content > *:nth-child(4) { animation-delay: .12s; }
.content > *:nth-child(5) { animation-delay: .15s; }
.content > *:nth-child(n+6) { animation-delay: .18s; }

/* Toast arrives with a small spring. */
@keyframes toast-in {
  from { opacity: 0; transform: translateY(12px) scale(.95); }
  70% { transform: translateY(-1px) scale(1.01); }
}
.toast { animation: toast-in .28s cubic-bezier(.2, .8, .3, 1.1); }

/* Slim lavender scrollbars inside the app. */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
  background: #d9cbe0; border-radius: 8px;
  border: 2px solid transparent; background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background-color: #c2a9cf; }
::-webkit-scrollbar-track { background: transparent; }

/* Print stays ink-true: no washes, no gradient text. */
@media print {
  body { background: #fff !important; }
  .content h1 { -webkit-text-fill-color: #000; color: #000; background: none; }
}

/* Anyone who asked their OS for less motion gets exactly that. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---- factory stage bar (2026-08-27): five steps beside the customer name,
   filled in the current stage's colour; on hold turns red. ---- */
.fo-cust { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* The customer's name with the clinician who served the sale under it
   (Dr Daniel, 2026-09-12) — quiet, so the name still leads the card. */
.fo-who { display: flex; flex-direction: column; line-height: 1.25; }
/* Same weight and size as the invoice line under the FPO number (Dr Daniel,
   2026-09-12: "less bold … same font as the invoice number") — it is a note
   about the sale, not a heading. */
.fo-served { font-size: .74rem; font-weight: 400; color: var(--muted); }
/* Icon progression beside the name (2026-09-02): done filled, current
   ringed, future pale; each icon names its stage on hover. Calm tones. */
.fo-steps { display: inline-flex; gap: 4px; align-items: center; flex: none; margin-left: 10px; }
.fo-steps .fo-step {
  font-style: normal; font-size: .78rem; line-height: 1;
  width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; background: #f3f0f5; border: 1.5px solid transparent;
  filter: grayscale(1); opacity: .45; cursor: default;
}
.fo-steps .fo-step.done { background: #efeaf7; filter: none; opacity: 1; }
.fo-steps .fo-step.now {
  background: #fff; border-color: var(--brand, #470054);
  filter: none; opacity: 1; box-shadow: 0 0 0 2px rgba(71, 0, 84, .12);
}
.fo-steps.hold .fo-step.now { border-color: #a3352c; box-shadow: 0 0 0 2px rgba(163, 53, 44, .15); }
/* The portal's two filter rows pin to the top of the scroll (2026-09-02). */
.fo-filters {
  position: sticky; top: 0; z-index: 40;
  background: var(--bg);
  /* Bleed across the .content padding so no strip of the scrolling list
     peeks out at the corners (Dr Daniel, 2026-09-02: "white gap on the
     left upper corner"). */
  margin: 0 -22px;
  /* Slimmest breathing room top and bottom (Dr Daniel, 2026-09-06). */
  padding: 2px 22px 2px;
  box-shadow: 0 8px 10px -10px rgba(71, 0, 84, .25);
}
@media (max-width: 900px) {
  .fo-filters { margin: 0 -14px; padding: 2px 14px 2px; }
}
.fo-filters .toolbar { margin: 4px 0; }
/* The first section heading keeps its USUAL 16px under the pinned bar
   (Dr Daniel, 2026-09-06: "make it the usual gap. A bit too tight here"
   — reversing the same morning's shave). */
/* Mochi editor: identity row pinned while the page scrolls (2026-09-03) —
   pairs .fo-filters (sticky, bleed, shadow) with a flex layout. */
.mochi-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.mochi-head h1 { margin: 0; font-size: 1.25rem; }

/* Print: worded, no fill (Dr Daniel, 2026-09-05: the bare icon read as
   missing). */
.fo-actbar button.fo-print {
  background: none; border: 1px solid var(--line); box-shadow: none;
  font-size: .9rem; font-weight: 600; padding: 8px 14px; min-height: 0; border-radius: 10px;
}
.fo-actbar button.fo-print:hover { background: rgba(71, 0, 84, .08); }
/* Material chips coloured like their preview bands (2026-09-02). The chosen
   one keeps its material colour and takes a dark ink + its own outline. */
.st-chip.mat-latex_topper, .st-chip.mat-latex { background: var(--mat-latex); }
.st-chip.mat-memory_foam { background: var(--mat-memory); }
.st-chip.mat-soft_foam { background: var(--mat-soft); }
.st-chip.mat-medium_foam { background: var(--mat-medium); }
.st-chip.mat-hard_foam { background: var(--mat-hard); }
.st-chip[class*="mat-"] { color: #4a4150; }
.st-chip.mat-latex_topper.on, .st-chip.mat-latex.on { color: #6b5c20; }
.st-chip.mat-memory_foam.on { color: #2f5c39; }
.st-chip.mat-soft_foam.on { color: #7a5e12; }
.st-chip.mat-medium_foam.on { color: #53417c; }
.st-chip.mat-hard_foam.on { color: #2c567f; }
.st-chip[class*="mat-"].on { background: inherit; font-weight: 700; border-color: transparent; }
.st-chip.mat-latex_topper.on, .st-chip.mat-latex.on { background: var(--mat-latex); }
.st-chip.mat-memory_foam.on { background: var(--mat-memory); }
.st-chip.mat-soft_foam.on { background: var(--mat-soft); }
.st-chip.mat-medium_foam.on { background: var(--mat-medium); }
.st-chip.mat-hard_foam.on { background: var(--mat-hard); }
/* The CHOSEN chip must be unmistakable (Dr Daniel, 2026-09-05: "the option
   buttons didnt show obvious colour for the chosen one") — the unchosen
   ones fade to a pale, calm wash of their material colour, and the chosen
   one keeps the full colour plus a ring in its own ink. */
.st-chip.mat-latex_topper:not(.on), .st-chip.mat-latex:not(.on) { background: color-mix(in srgb, var(--mat-latex) 26%, #fff); }
.st-chip.mat-memory_foam:not(.on) { background: color-mix(in srgb, var(--mat-memory) 26%, #fff); }
.st-chip.mat-soft_foam:not(.on) { background: color-mix(in srgb, var(--mat-soft) 26%, #fff); }
.st-chip.mat-medium_foam:not(.on) { background: color-mix(in srgb, var(--mat-medium) 26%, #fff); }
.st-chip.mat-hard_foam:not(.on) { background: color-mix(in srgb, var(--mat-hard) 26%, #fff); }
.st-chip[class*="mat-"]:not(.on) { color: #938a99; }
.st-chip.mat-latex_topper:not(.on):hover, .st-chip.mat-latex:not(.on):hover { background: color-mix(in srgb, var(--mat-latex) 55%, #fff); color: #4a4150; }
.st-chip.mat-memory_foam:not(.on):hover { background: color-mix(in srgb, var(--mat-memory) 55%, #fff); color: #4a4150; }
.st-chip.mat-soft_foam:not(.on):hover { background: color-mix(in srgb, var(--mat-soft) 55%, #fff); color: #4a4150; }
.st-chip.mat-medium_foam:not(.on):hover { background: color-mix(in srgb, var(--mat-medium) 55%, #fff); color: #4a4150; }
.st-chip.mat-hard_foam:not(.on):hover { background: color-mix(in srgb, var(--mat-hard) 55%, #fff); color: #4a4150; }
.st-chip[class*="mat-"].on { box-shadow: inset 0 0 0 2px currentColor; }

/* Add-on rows: the remove X rides the item-name line (2026-09-02). */
.addon-row .comp-summary { display: flex; align-items: center; gap: 8px; }
.addon-row .comp-summary strong { flex: 1; min-width: 0; }
/* The whose-cart dock: floats pinned to the page foot (2026-09-02). */
/* Pinned to the foot of the scrolling preview card (2026-09-02). */
/* The phone box and its Add to Cart are what stays pinned — not a white
   strip across the foot of the card (Dr Daniel, 2026-09-12: "no need freeze
   whole column, just freeze the Customer phone number and button"). The
   sticky layer is invisible and lets the pointer through; the lilac pill it
   carries is the only thing that floats, and the only thing that catches a
   click. */
.cart-dock {
  position: sticky; bottom: 0; z-index: 5;
  margin: 12px 0 0; padding: 0 0 10px;
  background: none; box-shadow: none; pointer-events: none;
}
.cart-dock-inner {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: var(--brand-tint, #f5ecf7); border-radius: 14px; padding: 8px 12px;
  pointer-events: auto; box-shadow: var(--shadow-lift);
}
/* Flat add button: no fill, no outline (2026-09-02). */
button.flat-add {
  background: none; border: none; box-shadow: none;
  color: var(--brand); font-size: 1.05rem; padding: 4px 8px;
}
button.flat-add:hover { background: rgba(71, 0, 84, .08); }
/* The add-level slot: unmissable, drawn like the empty level it becomes. */
button.add-level {
  display: block; width: 100%; margin: 8px 0 4px;
  background: var(--brand-tint, #f5ecf7); color: var(--brand);
  border: none; border-radius: 12px;
  padding: 12px; font-weight: 700; font-size: .95rem; box-shadow: none;
}
button.add-level:hover { background: #eee0f2; }
/* Calm go-action: soft green tint with green ink (2026-09-02). */
button.mochi-go { background: #e5f4e9; color: #166534; border: none; box-shadow: none; font-weight: 700; }
button.mochi-go:hover { background: #d5ecdc; }
/* Archive: flat text-only danger, no fill, no outline (2026-09-02). */
.fo-actbar button.mochi-flat, button.mochi-flat {
  background: none; border: none; box-shadow: none; color: #b3261e;
}
.fo-actbar button.mochi-flat:hover, button.mochi-flat:hover { background: rgba(179, 38, 30, .08); }
/* Mochi register row actions: bare icons, no fill (2026-09-02). */
button.mochi-rowact, button.mochi-rowact.danger {
  background: none; border: none; box-shadow: none;
}
button.mochi-rowact:hover { background: rgba(71, 0, 84, .08); }
/* Stage filter: flat text, underline marks the choice (2026-09-02). */
button.stage-flt {
  background: none; border: none; box-shadow: none; color: var(--muted);
  font-weight: 600; padding: 6px 4px; border-radius: 0;
  border-bottom: 2.5px solid transparent;
}
button.stage-flt:hover { color: var(--brand, #470054); background: none; }
button.stage-flt.on {
  color: var(--brand, #470054);
  border-bottom-color: var(--brand, #470054);
}
/* Borderless flag chips on the factory card (2026-09-02). */
.fo-card .prio-asap, .fo-card .prio-later, .fo-card .edited-chip { border: none; }
/* Courier tracking save: small and calm (2026-09-02). */
.fo-card button.fo-save {
  background: #efeaf7; color: #5b4a86; border: none; box-shadow: none;
  padding: 7px 14px; font-weight: 700;
}
.fo-card button.fo-save:hover { background: #e4dbf1; }
/* The card wears its ITEM TYPE as a calm accent colour instead of a label:
   mattress lavender, topper sky, delivery slate. */
.fo-card.type-mattress { border-left: 6px solid #b9a5d6; }
.fo-card.type-topper { border-left: 6px solid #9dbedd; }
.fo-card.type-delivery { border-left: 6px solid #b7bec9; }

.fo-bar { display: inline-flex; gap: 3px; align-items: center; flex: none; }
.fo-bar i {
  width: 15px; height: 6px; border-radius: 4px; background: var(--line);
  transition: background .25s ease;
}
.fo-bar i.on { background: var(--bar-c, var(--brand)); }
.fo-bar.hold i.on { background: repeating-linear-gradient(45deg, #a3352c, #a3352c 4px, #d9584f 4px, #d9584f 8px); }

/* Two-calendar range picker on the register (2026-08-27). */
.range-cals { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.range-cals .mc-day { min-height: 30px; font-size: .8rem; }
.flt-chip {
  width: auto; min-height: 38px; padding: 6px 14px; border-radius: 999px;
  font-size: .84rem; background: var(--card); color: var(--brand);
  border: 1px solid var(--line); box-shadow: none; font-weight: 700;
}
.flt-chip:hover { border-color: var(--brand); background: var(--brand-tint); box-shadow: none; }
@media (max-width: 640px) { .range-cals { grid-template-columns: 1fr; } }

/* 0.5 cm value buttons in the Mochi measurement pickers (2026-08-27). */
.pick-grid.nums { grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); gap: 6px; }
.pickbtn.num { min-height: 46px; padding: 8px 4px; font-variant-numeric: tabular-nums; }

/* Mochi measurements display (2026-08-27): label · value pairs that never
   wrap — values size to their content and sit right after their label, so
   "0.5 cm" can never break onto two lines. */
.meas-kv {
  /* Labels stay tight (max-content, capped) with each value directly beside
     its label; the spare width lands in the value columns AFTER the value
     text, so nothing stretches to the card edge and nothing can overflow.
     The narrow fallback goes to one pair per line. */
  grid-template-columns: minmax(max-content, 230px) minmax(70px, 1fr) minmax(max-content, 230px) minmax(70px, 1fr);
  column-gap: 14px; row-gap: 8px; font-size: .92rem; align-items: baseline;
}
.meas-kv dd { white-space: nowrap; text-align: left; }
/* The card's own width decides one pair or two per line — the page lays
   cards in columns, so the viewport width says nothing about the card. */
.meas-wrap { container-type: inline-size; }
@container (max-width: 580px) {
  .meas-kv { grid-template-columns: minmax(max-content, 230px) minmax(70px, 1fr); }
}

/* The Mochi register on a phone (Dr Daniel, 2026-09-14: "Make the mochi table
   smaller so that it fits into the mobile view"). Nine columns will not fit
   375px at any type size, so the phone keeps the four that answer "who, when,
   what state, and how do I open it" and folds the rest away. Nothing is lost:
   the clinician moves under the customer's name, and the skin, the heights
   and the extra invoice are all on the card the row opens. */
.mochi-servedby { display: none; }
@media (max-width: 760px) {
  .mochi-table { font-size: .78rem; }
  .mochi-table th, .mochi-table td { padding: 6px 5px; }
  .mochi-table th { font-size: .62rem; letter-spacing: .02em; }
  /* 1 invoice · 2 customer · 3 served by · 4 date · 5 status
     6 skin · 7 heights · 8 extra invoice · 9 open */
  .mochi-table th:nth-child(3), .mochi-table td:nth-child(3),
  .mochi-table th:nth-child(6), .mochi-table td:nth-child(6),
  .mochi-table th:nth-child(7), .mochi-table td:nth-child(7),
  .mochi-table th:nth-child(8), .mochi-table td:nth-child(8) { display: none; }
  .mochi-table .mochi-servedby { display: block; font-size: .7rem; }
  .mochi-table td:first-child { font-size: .72rem; word-break: break-all; }
}
/* Narrower still — the base invoice number goes too, and the row leads with
   the person. It is on the card, one tap away. */
@media (max-width: 430px) {
  .mochi-table th:first-child, .mochi-table td:first-child { display: none; }
  .mochi-table th:nth-child(4), .mochi-table td:nth-child(4) { font-size: .68rem; white-space: nowrap; }
}

/* ---- Customers list (2026-08-28): compact rows so more of the book fits on
   one screen, with lifetime spend reading as money. ---- */
.cust-table th, .cust-table td { padding: 5px 10px; font-size: .85rem; }
.cust-table td { vertical-align: middle; }
.cust-spend { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* An emoji inflates the line box: the same button reads 32px with plain text
   and 51px with a leading emoji, which was quietly making every row half as
   dense again. An explicit height and line-height clamp it. */
.cust-table button.soft {
  padding: 4px 14px; min-height: 0; height: 30px; line-height: 1;
  font-size: .82rem; white-space: nowrap;
}

/* Delivery Order revamp (Dr Daniel, 2026-09-04): the submit button is BIG —
   the reviewer's final act after checking the delivery details. */
/* Aesthetic yet unmissable (Dr Daniel, 2026-09-05): a soft green gradient
   pill with a gentle lift on hover. */
button.submit-final {
  background: linear-gradient(135deg, #23904d, #146032);
  background-color: #1b7a41; color: #fff; font-weight: 800; font-size: 1rem;
  padding: 13px 26px; letter-spacing: .6px; width: 100%; justify-content: center;
  border: none; border-radius: 999px;
  box-shadow: 0 4px 14px rgba(23, 105, 56, .28);
  transition: transform .08s ease, box-shadow .12s ease, filter .12s ease;
}
button.submit-final:hover {
  filter: brightness(1.06); transform: translateY(-1px);
  box-shadow: 0 6px 18px rgba(23, 105, 56, .36);
}
button.submit-final:active { transform: translateY(0); }
/* Title-bar variant (Dr Daniel, 2026-09-05: "must be as obvious as
   possible") — the same unmissable green, sized for the bar, with a soft
   breathing glow. */
button.submit-final.head {
  width: auto; font-size: .85rem; padding: 9px 18px; margin-left: 8px;
  white-space: nowrap; animation: submit-pulse 2s ease-in-out infinite;
}
@keyframes submit-pulse {
  0%, 100% { box-shadow: 0 4px 14px rgba(23, 105, 56, .28); }
  50% { box-shadow: 0 4px 22px rgba(23, 105, 56, .6); }
}

/* The three category tabs on Delivery Order / Ready for Delivery (Dr Daniel,
   2026-09-04): big pills, the picked one filled brand purple. */
button.cat-tab {
  background: #f4eef6; border: none; color: #6b5c72; font-weight: 700;
  font-size: 1rem; padding: 10px 22px; border-radius: 999px; line-height: 1.2;
}
button.cat-tab:hover { background: #eadff0; color: var(--brand, #470054); }
button.cat-tab.on {
  background: var(--brand, #470054); color: #fff;
  box-shadow: 0 3px 10px rgba(71, 0, 84, .28);
}

/* ASAP / LATER priority buttons on the Order Form card (Dr Daniel,
   2026-09-05): green when rushing, amber when the customer waits. */
button.prio-btn {
  /* Slimmed 2026-09-06 ("Make these smaller, noneed use up so much space"). */
  border: 1.5px solid var(--line); background: #fff; color: var(--muted);
  font-weight: 700; padding: 4px 6px; border-radius: 999px; min-height: 30px;
  white-space: nowrap; justify-content: center; width: 100%; font-size: .78rem;
}
/* Calm tones (Dr Daniel, 2026-09-05): soft washes with deep text instead
   of loud solid fills — same language as the payment-method chips. */
button.prio-btn.normal.on { background: #e3f0e6; border-color: #b9d8c2; color: #1d5a33; }
button.prio-btn.asap.on { background: #fbe4e2; border-color: #edc3bf; color: #99291f; }
button.prio-btn.later.on { background: #f9eedb; border-color: #e9d4ad; color: #8a5a10; }

/* Yes / No / TBC chips for "Old Mattress Disposal?" (2026-09-05). */
button.tri-chip {
  /* Slimmed 2026-09-06 alongside the priority buttons. */
  border: 1.5px solid var(--line); background: #fff; color: var(--muted);
  font-weight: 700; padding: 3px 12px; border-radius: 999px; min-height: 28px;
  font-size: .78rem;
}
button.tri-chip.yes.on { background: #1d7a3f; border-color: #1d7a3f; color: #fff; }
button.tri-chip.no.on { background: #b3261e; border-color: #b3261e; color: #fff; }
button.tri-chip.tbc.on { background: #b26a00; border-color: #b26a00; color: #fff; }
button.tri-chip:not(.on):hover { border-color: var(--brand); color: var(--brand); background: #fff; }
button.prio-btn:not(.on):hover { border-color: var(--brand); color: var(--brand); background: #fff; }

/* A superseded specification is SLASHED OUT in red (Dr Daniel, 2026-09-05);
   hovering it says when and who amended it (the title tooltip). */
.spec-old { position: relative; opacity: .62; }
.spec-old::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top right,
    transparent 48.6%, #b3261e 48.6%, #b3261e 51.4%, transparent 51.4%);
}

/* WhatsApp-style order chat (Dr Daniel, 2026-09-05). LUNOX letters and the
   factory's replies share one thread; the two banners now have their own
   colours — NEWLY EDITED stays amber, the chat is purple-headed. */
.chat-box {
  border: 1.5px solid var(--line); border-radius: 14px; padding: 10px;
  background: #f7f4f9; margin-top: 10px;
}
.chat-head {
  font-weight: 800; color: var(--brand, #470054); margin-bottom: 6px;
  display: flex; align-items: center; gap: 8px;
}
.chat-unread {
  background: #b3261e; color: #fff; border-radius: 999px; font-size: .7rem;
  padding: 2px 8px; font-weight: 700;
}
.chat-scroll { max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; }
.chat-bub {
  max-width: 85%; padding: 7px 10px; border-radius: 12px; font-size: .88rem;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .08); width: fit-content;
}
.chat-bub.recv { background: #fff; align-self: flex-start; border-top-left-radius: 4px; }
.chat-bub.sent { background: #dcf3d8; align-self: flex-end; border-top-right-radius: 4px; }
.chat-bub.unread { outline: 2px solid #b3261e33; background: #fff6f5; }
.chat-author { font-size: .72rem; font-weight: 700; color: var(--brand, #470054); }
.chat-meta { font-size: .68rem; color: var(--muted); text-align: right; margin-top: 2px; display: flex; gap: 6px; justify-content: flex-end; }
.chat-ticks { color: #9aa1ab; font-weight: 700; letter-spacing: -2px; }
.chat-ticks.read { color: #2f7fd1; }
.chat-box textarea { min-height: 38px; }

/* The floating order chat (Dr Daniel, 2026-09-05): a 💬 button pinned to
   the modal's bottom right, red unread badge, expanding to a floating box. */
.chat-fab {
  position: fixed; right: 26px; bottom: 22px; z-index: 60;
  width: 54px; height: 54px; border-radius: 50%; border: none;
  background: var(--brand, #470054); color: #fff; font-size: 1.45rem;
  box-shadow: 0 6px 18px rgba(71, 0, 84, .35); min-height: 0; padding: 0;
  display: flex; align-items: center; justify-content: center;
}
.chat-fab:hover { background: #5b1a6e; }
.chat-fab-badge {
  position: absolute; top: -4px; right: -4px; background: #b3261e; color: #fff;
  border-radius: 999px; min-width: 20px; height: 20px; font-size: .7rem;
  font-weight: 800; display: flex; align-items: center; justify-content: center;
  padding: 0 5px; border: 2px solid #fff;
}
.chat-float {
  position: fixed; right: 26px; bottom: 22px; z-index: 220;
  border: 1.5px solid var(--line);
  width: min(400px, calc(100vw - 40px)); max-height: min(520px, calc(100vh - 60px));
  display: flex; flex-direction: column; background: #f7f4f9;
  box-shadow: 0 10px 34px rgba(0, 0, 0, .28);
}
.chat-float .chat-scroll { max-height: none; min-height: 180px; }
/* The class's display:flex ties with [hidden]'s display:none on specificity
   and wins on order — restate it so the collapsed state actually collapses. */
.chat-float[hidden] { display: none; }

/* Factory-portal chat (2026-09-05, overlay retired the same day: it sat on
   top of the card's icons): the 💬 button lives INLINE in the card's button
   bar, and only the expanded box floats — fixed at the viewport corner,
   opaque and above everything. */
.chat-fab-inline .chat-fab-badge { top: -7px; right: -7px; }
/* The Order Form's title-row chat button wears the badge on its corner. */
.fo-msg-btn .chat-fab-badge { top: -8px; right: -8px; }

/* Clickable header chips (2026-09-05): they read as buttons on hover. */
.chip-door { cursor: pointer; }
.chip-door:hover { filter: brightness(.93); text-decoration: underline; }

/* The allocation cell is one big button (2026-09-05). */
td.alloc-click { cursor: pointer; }
/* The item column as a click target (Dr Daniel, 2026-09-05): the whole cell
   opens the item's edit popup, so it hints like the allocation cell does. */
/* The whole row is the click target (Dr Daniel, 2026-09-05: "click anywhere
   in the item column, not just the description"). No hover wash — the
   pointer and the tooltip carry the hint (Dr Daniel: "No need purple
   highlight pls"). */
td.item-td-click { cursor: pointer; }
tr.row-click { cursor: pointer; }
td.alloc-click:hover .alloc-name { color: var(--brand, #470054); text-decoration: underline dotted; }

/* The row's factory-message door (Dr Daniel, 2026-09-05): a proper round
   Messenger-blue button, clearly apart from the status badge. */
button.row-msg {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; min-height: 0; padding: 0; border: none; border-radius: 50%;
  background: #0084ff; color: #fff; font-size: 1.15rem;
  margin: 8px 0 0 2px; box-shadow: 0 2px 8px rgba(0, 132, 255, .35);
}
button.row-msg:hover { background: #006fd6; }
button.row-msg .chat-fab-badge { top: -5px; right: -5px; }

/* The PIC's Note-to-Self pad (2026-09-05): ruled-paper feel, list of notes.
   Slimmed down 2026-09-06 ("make it smaller") — narrower, tighter, quieter. */
.self-notes {
  margin-top: 8px; background: #f7f7f5; border: 1px solid #e3e3de;
  border-radius: 10px; padding: 6px 10px 8px; max-width: 300px; font-size: .8rem;
}
.self-notes strong { font-size: .76rem; }
.self-notes input {
  background: transparent; border: none; border-bottom: 1.5px dashed #cfcfc8;
  border-radius: 0; font-size: .78rem; min-height: 0; padding: 3px 2px;
}
.self-notes button { min-height: 0; padding: 2px 8px; }
.self-note { border-bottom: 1px dashed #e0e0da; padding: 4px 2px; font-size: .8rem; }
.self-note:last-child { border-bottom: none; }

/* The Order Form's title-row chat button wears Messenger blue, matching
   the row icon (Dr Daniel, 2026-09-05). */
button.fo-msg-btn {
  background: #0084ff; color: #fff; border: none; font-weight: 700;
  border-radius: 999px; padding: 7px 16px;
}
button.fo-msg-btn:hover { background: #006fd6; color: #fff; }

/* "All" buttons stand apart (Dr Daniel, 2026-09-05): brand-purple pill,
   filled when active, outlined when not. */
button.all-btn {
  border: 1.5px solid var(--brand, #470054); background: #fff;
  color: var(--brand, #470054); font-weight: 800; border-radius: 999px;
}
button.all-btn.on { background: var(--brand, #470054); color: #fff; }
button.all-btn:hover { background: #f2e7f6; }
button.all-btn.on:hover { background: #5b1a6e; color: #fff; }

/* Order numbers highlight and copy (2026-09-05) — the card head is a
   toggle, but the numbers themselves select like ordinary text. */
.fo-head .copyable { user-select: text; cursor: text; }
/* The suspended-preview panel a Revised Draft wears on the portal (Dr
   Daniel, 2026-09-07): draft-yellow, clearly a pause — not an error. */
.fo-amending {
  background: #fdf6df; border: 1px dashed #e5c878; border-radius: 10px;
  padding: 12px 14px; color: #7a5b12; font-size: .92rem;
}
.fo-amending strong { color: #b26a00; }
/* Superseded builds under the live preview (Dr Daniel, 2026-09-07):
   collapsed rows, red DO-NOT-PRODUCE wording, and the old drawing washes
   out so it can never be mistaken for the build in force. */
.fo-oldvers { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
.fo-oldver { border: 1px dashed var(--line); border-radius: 10px; padding: 6px 10px; background: #faf8fb; }
.fo-oldver summary { cursor: pointer; font-size: .82rem; font-weight: 800; color: #99291f; }
.fo-oldver summary .muted { font-weight: 600; font-size: .78rem; }
.fo-oldver .fo-oldver-body { opacity: .55; margin-top: 6px; }
/* Flat undo in the PO popup (2026-09-05): plain muted text, no button box. */
button.flat-undo { background: none; border: none; box-shadow: none; color: var(--muted); font-weight: 600; }
button.flat-undo:hover { color: var(--brand, #470054); background: none; }

/* The invoice's Order Form button (2026-09-05): the nav tab's icon on a
   light greyish-white pill. */
button.order-form-btn {
  background: #f5f5f2; border: 1.5px solid #e3e3de; color: var(--text);
  font-weight: 600; box-shadow: none; /* flat 2D (Dr Daniel, 2026-09-05) */
}
button.order-form-btn:hover { background: #ecece8; box-shadow: none; }

/* Order Form rows hover greyish-white, not brand purple (Dr Daniel,
   2026-09-07: "Mouse hover purple highlight in orderform, change it to
   light grey … greyish white"). Draft rows keep their yellow wash and
   delivered rows their fade — only the purple goes. */
.fo-list tr.click:hover { background: #f5f5f4; }
.fo-list tr.click:hover td { background: #f5f5f4; }
.fo-list tr.row-draft.click:hover td { background: #f9edc7; }

/* Invoice register — Summary sub-tab (Dr Daniel, 2026-09-07): the range's
   total, what was sold as a donut, and how the money arrived. Cards flow
   into one column on a narrow screen. */
.sum-wrap { display: flex; flex-direction: column; gap: 14px; }
.sum-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.sum-head h2 { margin: 0; font-size: 1.05rem; }
.sum-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; align-items: start; }
.sum-card h3 { margin: 0 0 8px; font-size: .82rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.sum-total { font-size: 1.9rem; font-weight: 800; color: var(--brand); line-height: 1.1; }
.sum-bars { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.sum-bar-row { display: grid; grid-template-columns: 1fr 2fr auto; gap: 8px; align-items: center; font-size: .82rem; }
.sum-bar-track { background: var(--line); border-radius: 999px; height: 10px; overflow: hidden; }
.sum-bar-fill { display: block; height: 100%; border-radius: 999px; }
.sum-bar-v { font-weight: 700; white-space: nowrap; }
.sum-donut-wrap { display: flex; justify-content: center; padding: 6px 0 12px; }
.sum-donut {
  width: 168px; height: 168px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
.sum-donut-hole {
  width: 108px; height: 108px; border-radius: 50%; background: var(--card, #fff);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
}
.sum-donut-v { font-weight: 800; font-size: 1.05rem; }
.sum-donut-l { font-size: .72rem; color: var(--muted); }
.sum-legend { display: flex; flex-direction: column; gap: 6px; }
.sum-leg { display: grid; grid-template-columns: 12px 1fr auto; gap: 8px; align-items: center; font-size: .82rem; }
.sum-dot { width: 10px; height: 10px; border-radius: 50%; }
.sum-leg-l { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sum-leg-v { font-weight: 700; white-space: nowrap; }

/* The WhatsApp customer-copy preview (Dr Daniel, 2026-09-07): the document
   on the left, the editable message on the right; one column on a narrow
   screen. */
.cc-preview { display: grid; grid-template-columns: 1.15fr 1fr; gap: 16px; align-items: start; }
.cc-preview > div > label { display: block; font-weight: 700; font-size: .82rem; margin-bottom: 6px; }
@media (max-width: 820px) { .cc-preview { grid-template-columns: 1fr; } }

/* Whose outreach map signed a partner — the register and QR cards colour
   the two apart (Dr Daniel, 2026-09-08: "put clement and daniel tan
   different color"). Deliberately NOT the status palette: a map is an
   origin, not a state. */
.badge.map-clement { background: #e7f0fd; color: #1c56ae; }
.badge.map-dtan { background: #fdefdd; color: #9a5b00; }

/* The Type/Map FILTER CHIPS wear identity colours (Dr Daniel, 2026-09-08:
   "distinct color for clement and daniel tan, and also distinct color for
   centre and personal"). Resting = soft tint; selected = solid fill of the
   SAME colour, so a picked chip keeps its identity instead of turning brand
   purple. The neutral chips (All / Both / Unclassified / Other) keep the
   brand treatment. Specificity matches .br-chip.on.tab-chip and these sit
   later in the sheet, so they win the tie. */
.br-chip.tab-chip.chip-clement { background: #e7f0fd; color: #1c56ae; border-color: #c8dcf7; }
.br-chip.on.tab-chip.chip-clement { background: #1c56ae; color: #fff; border-color: #1c56ae; }
.br-chip.tab-chip.chip-dtan { background: #fdefdd; color: #9a5b00; border-color: #f3ddb7; }
.br-chip.on.tab-chip.chip-dtan { background: #9a5b00; color: #fff; border-color: #9a5b00; }
.br-chip.tab-chip.chip-centre { background: #e0f2f1; color: #00695f; border-color: #bfe5e2; }
.br-chip.on.tab-chip.chip-centre { background: #00695f; color: #fff; border-color: #00695f; }
.br-chip.tab-chip.chip-personal { background: #fce7f0; color: #ad2a63; border-color: #f5c8db; }
.br-chip.on.tab-chip.chip-personal { background: #ad2a63; color: #fff; border-color: #ad2a63; }

/* ---- Production Detail Confirmation on paper (2026-09-06) --------------
   LAST in the sheet on purpose: these print sizes must outrank the screen
   .po-doc rules above, and with equal specificity the later rule wins.
   Sizes mirror the approved PDF's point values so the printed page IS the
   approved page; long orders continue cleanly onto page 2 rather than
   clipping (panels and section heads never split). */
@media print {
  /* Paper never animates: entry animations start at opacity 0, and a print
     snapshot taken at frame 0 (headless, or a very fast Ctrl+P) would ship
     a blank page. */
  *, *::before, *::after { animation: none !important; transition: none !important; }
  /* The browser prints its date/title header and URL footer INTO the page
     margins — a zero-margin named page on the WHOLE BODY leaves it nowhere
     to write and forces no break; the document carries its own margins. */
  body:has(.po-doc) { page: podoc; }
  @page podoc { margin: 0; }
  .po-doc { padding: 9mm 11mm 7mm; font-size: 10.5px; line-height: 1.32; }
  .po-doc .po-hd { margin-bottom: 7px; }
  .po-doc .po-logo { width: 110px; }
  .po-doc .po-hd-meta { font-size: 8.3px; gap: 2px; }
  .po-doc .po-hd-title { font-size: 8px; }
  .po-doc .po-hero { border-radius: 12px; padding: 13px 16px 10px; margin-bottom: 10px; }
  .po-doc .po-hero h1 { font-size: 23px; margin-bottom: 4px; }
  .po-doc .po-hero-hi { font-size: 12px; margin-bottom: 5px; }
  .po-doc .po-hero-sub { font-size: 9.5px; }
  .po-doc .po-sec-head { margin: 11px 0 5px; gap: 9px; break-after: avoid; }
  .po-doc .po-num { width: 24px; height: 24px; border-radius: 8px; font-size: 9px; }
  .po-doc .po-sec-head h2 { font-size: 13px; }
  .po-doc .po-panel { border-radius: 8px; padding: 8px 12px; }
  .po-doc .po-grid2 { gap: 7px 16px; }
  .po-doc .po-lab { font-size: 7.2px; }
  .po-doc .po-val { font-size: 10.5px; }
  .po-doc .po-size-val { font-size: 13px; }
  .po-doc .po-callout { padding: 1px 0; }
  .po-doc .po-crow { padding: 7px 12px; }
  .po-doc .po-callout-name { font-size: 14px; }
  .po-doc .po-callout-sub { font-size: 8.5px; }
  .po-doc .po-matt { gap: 12px; }
  .po-doc .po-matt-left { gap: 8px; }
  .po-doc .po-matt-right img { max-width: 190px; }
  .po-doc .po-fig-labs > div { font-size: 10px; }
  .po-doc .po-fig-labs strong { font-size: 7px; }
  .po-doc .fl-dot { width: 6px; height: 6px; }
  .po-doc .po-items-row { gap: 9px; margin-bottom: 7px; }
  .po-doc .po-items { padding: 7px 11px; border-radius: 8px; }
  .po-doc .po-items h4 { font-size: 7.2px; margin-bottom: 4px; }
  .po-doc .po-item { font-size: 9px; padding: 1.5px 0; }
  .po-doc .po-none { font-size: 8.5px; }
  .po-doc .po-cta { margin-top: 10px; padding: 8px 12px; border-radius: 8px; }
  .po-doc .po-cta h3 { font-size: 11px; margin-bottom: 2px; }
  .po-doc .po-cta p { font-size: 8.4px; }
  .po-doc .po-before { margin-top: 7px; }
  .po-doc .po-terms-p { font-size: 8.3px; margin-bottom: 0; }
  .po-doc .po-panel, .po-doc .po-items, .po-doc .po-items-row,
  .po-doc .po-matt, .po-doc .po-end, .po-doc .po-item { break-inside: avoid; }
  .po-doc .po-hero, .po-doc .po-num, .po-doc .po-panel, .po-doc .po-items,
  .po-doc .po-callout, .po-doc .po-cta, .po-doc .fl-dot {
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }
}

/* Mochi assessment (Dr Daniel, 2026-09-09) */
/* The action bar freezes at the viewport foot so Save and the stage action
   are always in reach on a long assessment. */
.mochi-actbar {
  position: sticky; bottom: 0; z-index: 6;
  background: var(--bg);
  padding: 10px 0; margin-bottom: 0;
  border-top: 1px solid var(--line);
}
/* All four scents share one grid row; min-width 0 lets the long names wrap
   inside their cells instead of pushing a chip onto a second line. */
.aroma-row { display: grid; grid-template-columns: repeat(4, 1fr); }
.aroma-row button { min-width: 0; }

/* Topbar message bell (Dr Daniel, 2026-09-09) */
.bell-drop {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
  min-width: 230px; background: var(--card); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: 0 8px 24px rgba(42, 31, 46, .18); padding: 4px 0 6px;
}
.bell-row {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: 7px 12px; color: var(--text); text-decoration: none; font-size: .85rem;
}
.bell-row:hover { background: var(--brand-tint); }
.bell-count {
  background: #b3261e; color: #fff; border-radius: 999px;
  min-width: 20px; text-align: center; font-size: .72rem; font-weight: 800; padding: 1px 6px;
}
.topbar .iconbtn { position: relative; }

/* ---- Pressure Lab (2026-09-10) ---------------------------------------- */
.pm-cols { display: grid; grid-template-columns: minmax(0, 1.75fr) minmax(280px, .85fr); gap: 14px; align-items: start; }
/* Save and Cancel are frozen along the foot (Dr Daniel, 2026-09-13): the row
   rides with the page instead of scrolling away with the header, and it is
   the same strip whether the fitting is a page of its own or the full-screen
   popup inside a case note. */
.pm-actionbar {
  position: sticky; bottom: 0; z-index: 6;
  display: flex; justify-content: flex-end; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 14px; padding: 10px 12px;
  background: color-mix(in srgb, var(--card) 94%, transparent);
  backdrop-filter: blur(6px);
  border-top: 1px solid var(--line);
  border-radius: 0 0 14px 14px;
}
.pm-actionbar > span { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
@media (max-width: 1100px) { .pm-cols { grid-template-columns: 1fr; } }
/* The mat gets the room (Dr Daniel, 2026-09-13: "make the pressure map
   bigger"): map and colour bar across the top, the readings in a row
   UNDERNEATH rather than in a column stealing 150px of width. */
.pm-stage { display: grid; grid-template-columns: minmax(0, 1fr) 78px; gap: 10px; align-items: start; }
@media (max-width: 900px) {
  .pm-stage { grid-template-columns: 1fr; }
  /* Stacked, the colour bar is an ordinary column again: its figures sit in
     the flow rather than hanging over the foot of the map above it. */
  .pm-vbar-out { position: static; transform: none; margin: 3px 0; }
}
.pm-map { display: flex; flex-direction: column; gap: 6px; align-items: stretch; min-width: 0; }
.pm-canvas { width: 100%; aspect-ratio: 9 / 4; height: auto; border-radius: 10px; border: 1px solid #e5e7eb; background: #f8f7fb; }
.pm-canvas-zoomable { cursor: zoom-in; }
.pm-canvas-zoomable:hover { border-color: var(--brand-tint-2, #c9a6d4); }
/* The map on its own, as big as the screen will give it. */
.pm-zoom { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; }
.pm-zoomcanvas {
  width: 100%; height: 100%; object-fit: contain; max-height: 100%;
  border-radius: 12px; border: 1px solid #e5e7eb; background: #f8f7fb;
}
.pm-profile { width: 100%; height: auto; aspect-ratio: 1080 / 64; border-radius: 6px; border: 1px solid #e5e7eb; background: #fff; }
/* The colour bar is as tall as the map beside it and no taller (Dr Daniel,
   2026-09-14: "make it smaller so it fits within the pressure map preview").
   The strip takes whatever the row gives it rather than setting a floor of
   its own, and the two number boxes are the size of the figures in them, not
   the house's full 42px — that alone was 84px of the overhang. */
/* The column is the height of its own contents now, not of the row: the strip
   is set to the map's height in script and the two figure boxes stand outside
   it (Dr Daniel, 2026-09-14). The column is now the tallest thing in the row,
   so it starts level with the map and the lower box hangs below it; pulling the
   strip up to the map's own top edge would drive the upper box into the
   toolbar above. */
.pm-vbar {
  display: flex; flex-direction: column; align-items: center;
  align-self: start; position: relative;
}
/* mmHg and its two figures live outside the map's height, which is where Dr
   Daniel says they may be; inside it there is nothing but the gradient. */
.pm-vbar-out {
  position: absolute; left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 2px;
}
.pm-vbar-top { bottom: 100%; margin-bottom: 4px; }
.pm-vbar-bot { top: 100%; margin-top: 4px; }
.pm-vbar-body { display: flex; gap: 4px; align-items: stretch; flex: none; min-height: 118px; }
.pm-vbar-strip { width: 17px; border-radius: 6px; border: 1px solid #e5e7eb; }
.pm-vbar-ticks {
  display: flex; flex-direction: column; justify-content: space-between;
  font-size: .6rem; color: var(--muted); font-weight: 700; line-height: 1;
}
.pm-scale-in {
  width: 50px; padding: 2px 4px; min-height: 24px; font-size: .74rem;
  border-radius: 8px; text-align: center;
}
.pm-kpis {
  grid-column: 1 / -1; display: grid; gap: 8px; margin-top: 2px;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
}
.pm-kpi { padding: 8px 10px; }
.pm-kpi .v { font-size: 1.35rem; }
.pm-kpi.hot .v { color: #b91c1c; }
.pm-playback { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; background: #eef2ff; border: 1px solid #c7d2fe; border-radius: 8px; padding: 6px 10px; margin-bottom: 8px; }
.pm-layers { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-height: 34px; }
.pm-chip { display: inline-flex; align-items: center; gap: 4px; background: var(--brand-tint, #f3e8f7); color: var(--brand, #470054); border-radius: 999px; padding: 3px 8px; font-size: .82rem; }
.pm-chip .x { border: none; background: transparent; color: inherit; cursor: pointer; font-size: 1rem; line-height: 1; padding: 0 2px; }
.pm-inline { display: inline-flex; align-items: center; gap: 4px; font-size: .85rem; }
/* The capture panel is no longer a card of its own: it is the right half of
   the working section, so it starts level with the mat and is divided from it
   by a hairline rather than by a gap and a second shadow (Dr Daniel,
   2026-09-13: "Make this 2 align, and combine into 1 section"). Stacked on a
   narrow screen the divider becomes a rule across the top instead. */
.pm-form { border-left: 1px solid var(--line); padding-left: 16px; }
/* Everything in this panel is built small (Dr Daniel, 2026-09-14: "the
   buttons in this component make it smaller, so this whole component take up
   lesser space for the pressure map") — the chips, the headings and the gaps
   between them. The mat beside it keeps the width it gains. */
.pm-form .pm-formhead { margin: 0 0 4px; font-size: 1rem; }
/* Whose half this panel is building, on its own line under the heading and in
   a different hand — lighter, wider, and in that side's colour, the same
   colour the person's chip and the chosen build wear (Dr Daniel, 2026-09-14:
   "make it at the second row, remove the brackets, use different font to
   distinguish it"). */
.pm-formwho {
  display: block; margin-top: 1px;
  font-size: .76rem; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted);
}
.pm-formwho[data-side="embroidery"] { color: #6d28d9; }
.pm-formwho[data-side="label"] { color: #0f766e; }
.pm-form label { font-size: .74rem; }
.pm-form .st-chips { grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 5px; margin: 2px 0 1px; }
.pm-form .st-chip {
  padding: 4px 8px; font-size: .73rem; min-height: 26px; border-radius: 8px; border-width: 1px;
}
.pm-form input, .pm-form textarea, .pm-form select { padding: 5px 9px; font-size: .82rem; }
.pm-form fieldset { padding: 7px 9px 8px; margin: 0 0 6px; }
.pm-form legend { font-size: .72rem; }
/* Remove belongs to the level and sits at its far right, on the line its name
   is written on (Dr Daniel, 2026-09-14: "on the right of the component, not
   directly next to the word Level 1"). It rides the fieldset's top border, so
   it is pinned rather than laid out. */
fieldset.pm-lev { position: relative; }
button.pm-levx {
  position: absolute; top: -18px; right: 6px;
  padding: 0 5px; min-height: 18px; line-height: 1; font-size: .78rem;
  background: #fff; box-shadow: none; border: none; color: var(--bad, #a3352c);
}
button.pm-levx:hover { background: var(--bad-bg, #fde8e6); border-radius: 6px; }
/* Annotate and Void wear their icon and nothing else — no fill, no box: a
   border round a single glyph reads as a missing label. */
button.pm-capicon, button.pm-capicon:hover {
  background: transparent; border: none; box-shadow: none;
  padding: 2px 5px; font-size: .95rem; line-height: 1.1;
}
button.pm-capicon:hover { filter: brightness(.86); }
.pm-capicons { display: flex; align-items: center; gap: 2px; margin-left: auto; }
/* One section per base (Dr Daniel, 2026-09-14): everything tried on the same
   base is together, and the base is named over it. */
.pm-basegroup + .pm-basegroup { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 12px; }
.pm-baselabel { font-size: .82rem; color: var(--muted); margin: 0; white-space: nowrap; }
.pm-baselabel strong { color: var(--text); }
/* Save closes the captures card, hard against its right edge. */
.pm-saverow { margin: 12px 0 0; }
/* The whole capture chip opens it. */
.pm-cap-open { cursor: pointer; }
.pm-cap-open button, .pm-cap-open .pm-rename, .pm-cap-open input { cursor: auto; }
.pm-cap-open button { cursor: pointer; }
.pm-form .pm-buildhead { margin: 8px 0 4px; }
.pm-form > * + * { margin-top: 5px; }
@media (max-width: 1100px) {
  .pm-form { border-left: none; border-top: 1px solid var(--line); padding-left: 0; padding-top: 14px; }
}
/* Captures sit side by side along the row (Dr Daniel, 2026-09-13), so each
   one is a tile with its map on top and its figures under it, and three
   trials can be read against each other without scrolling. */
.pm-capgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 10px; align-items: start; }
.pm-cap { display: flex; flex-direction: column; gap: 8px; padding: 8px; border: 1px solid #e5e7eb; border-radius: 10px; background: #fff; }
.pm-cap.final { border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand-tint) inset; }
.pm-cap.voided { opacity: .55; }
.pm-cap img { width: 100%; aspect-ratio: 9 / 4; height: auto; border-radius: 8px; border: 1px solid #e5e7eb; cursor: zoom-in; object-fit: cover; }
.pm-cap-body { min-width: 0; }
.pm-stat-line { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: .84rem; margin: 3px 0; }
/* Rename a capture where you read it (2026-09-13). */
.pm-rename { cursor: text; border-bottom: 1px dashed transparent; }
.pm-rename:hover { border-bottom-color: var(--brand-tint-2, #c9a6d4); }
.pm-rename-in { width: auto; min-width: 180px; max-width: 100%; padding: 2px 6px; font-size: .9rem; font-weight: 700; }
.pm-stat-line .hot { color: #b91c1c; }
/* A coded build is read letter by letter, so it is set in the same face the
   figures are and never squeezed. */
.pm-code { font-variant-numeric: tabular-nums; letter-spacing: .01em; font-weight: 700; }
.pm-profile-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; }
.pm-profile-cards img { width: 100%; aspect-ratio: 2 / 1; border-radius: 8px; border: 1px solid #e5e7eb; object-fit: cover; cursor: zoom-in; }
.pm-profile-cards .cap { border: 1px solid #e5e7eb; border-radius: 10px; padding: 6px; background: #fff; font-size: .82rem; }
.pm-profile-cards .cap.final { border-color: #d9a441; box-shadow: 0 0 0 2px #fdf3df inset; }
/* Two finals that cannot be billed together are outlined in red, whatever
   else they are (Dr Daniel, 2026-09-14). */
.pm-profile-cards .cap.clash, .pm-profile-cards .cap.final.clash {
  border-color: #b91c1c; box-shadow: 0 0 0 2px #fdeaea inset;
}
.pm-clash { margin-top: 10px; }
/* The badge's display:flex used to beat the hidden attribute, so the bell
   wore a red 0 when there was nothing unread (Dr Daniel's 2026-09-10
   screenshot). */
.chat-fab-badge[hidden] { display: none !important; }

/* Report Centre (Dr Daniel, 2026-09-11) */
.rc-cat { font-size: .95rem; margin: 18px 0 8px; }
.rc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 10px; }
/* Catalogue cards, redesigned 2026-09-17 ("Design the cards abit to make
   it nicer"): icon in a tinted chip, title beside it, the description in a
   quiet line UNDER them — the button base had been sitting title and
   description side by side. A gentle lift on hover instead of a repaint. */
.rc-card {
  display: flex; flex-direction: column; align-items: stretch; gap: 8px;
  text-align: left; background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 14px 16px; cursor: pointer; box-shadow: none;
  min-height: 0; transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
.rc-card:hover {
  border-color: var(--brand-light); background: var(--card);
  box-shadow: 0 4px 14px rgba(71, 0, 84, .09); transform: translateY(-1px);
}
.rc-card-head { display: flex; align-items: center; gap: 9px; min-width: 0; }
.rc-card-ic {
  width: 34px; height: 34px; border-radius: 10px; flex: 0 0 auto;
  background: var(--brand-tint); display: inline-flex;
  align-items: center; justify-content: center; font-size: 1.05rem;
}
.rc-card-t { font-weight: 800; color: var(--brand-dark); font-size: .92rem; line-height: 1.25; }
.rc-star { color: #b8860b; margin-left: auto; flex: 0 0 auto; }
.rc-card-d { color: var(--muted); font-size: .78rem; line-height: 1.45; }
.rc-flt { display: inline-flex; align-items: center; gap: 6px; font-size: .8rem; color: var(--muted); }
.rc-sums { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; margin-bottom: 10px; }
.rc-sum { padding: 10px 12px; }
.rc-sum-v { font-size: 1.15rem; font-weight: 800; color: var(--brand-dark); }
.rc-sum-l { color: var(--muted); font-size: .72rem; margin-top: 2px; }
.rc-table th { position: sticky; top: 0; background: var(--card); }
@media print {
  .sidebar, .topbar, .rc-controls, .foot { display: none !important; }
  .content { padding: 0 !important; }
  .card { box-shadow: none !important; border: none !important; }
}
.rc-cat-row td { background: var(--brand-tint-2); }
.rc-drill table { font-size: .82rem; }

/* A payment opened out of the line above it: same columns, tinted, and
   tighter than a heading row so a long list stays compact
   (Dr Daniel, 2026-09-12: "Why there's so much gap in between?"). */
tr.pay-sub > td { background: var(--brand-tint); padding: 4px 10px; border-bottom: none; font-size: .82rem; }
tr.pay-sub + tr:not(.pay-sub) > td { border-top: 1px solid var(--line); }
td.pay-sub-inv { padding-left: 26px; }

/* ---- Daily Closing (Dr Daniel, 2026-09-12) ----
   The day's title and its two controls ride one frozen row under the topbar;
   the two actions ride a frozen bar at the foot. Neither goes out of reach on
   a long close. Same treatment as the Order Form's filter rows and the mochi
   action bar, bleeding across the .content padding so nothing peeks out at
   the corners as the page scrolls beneath them. */
.dc-head {
  position: sticky; top: 59px; z-index: 20;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--bg); margin: 0 -22px 10px; padding: 8px 22px;
  box-shadow: 0 8px 10px -10px rgba(71, 0, 84, .25);
}
.dc-actbar {
  position: sticky; bottom: 0; z-index: 6;
  /* Copy on the left of the pair, the close on the right, both hard right
     (Dr Daniel, 2026-09-12). */
  display: flex; justify-content: flex-end; align-items: center; gap: 8px;
  background: var(--bg); margin: 10px -22px 0; padding: 10px 22px;
  border-top: 1px solid var(--line);
}
.dc-actbar:empty { display: none; }
/* A card's own title row, with its action sitting on the same line. */
.card-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; flex-wrap: wrap; }
.card-head h2 { margin: 0; }
/* What a part-paid bill still owes, said in words beside its payments
   (Dr Daniel, 2026-09-12). Red like the Balance line it echoes. */
.pay-due {
  font-size: .82rem; font-weight: 700; white-space: nowrap;
  background: var(--bad-bg); color: var(--bad); border-radius: 999px; padding: 3px 11px;
}
.pay-settled {
  font-size: .82rem; font-weight: 700; white-space: nowrap;
  background: var(--ok-bg); color: var(--ok); border-radius: 999px; padding: 3px 11px;
}
/* A payment that no longer counts — corrected or removed — with the Status
   column gone (Dr Daniel, 2026-09-12). The row steps back and its amount is
   struck through, so no cancelled figure can be read as money in hand. */
tr.pay-void td { color: var(--muted); }
tr.pay-void td.num { text-decoration: line-through; }
/* Proof of payment filed with the reference it belongs to (Dr Daniel,
   2026-09-12) — a quiet line, the filename doing the talking. */
.pay-proof { display: flex; align-items: center; gap: 2px; margin-top: 2px; }
.pay-proof a {
  font-size: .76rem; max-width: 190px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 900px) {
  .dc-head { margin: 0 -14px 10px; padding: 8px 14px; }
  .dc-actbar { margin: 10px -14px 0; padding: 10px 14px; }
}

/* A class that sets `display` beats the browser's own [hidden] rule, so an
   element hidden from script stayed on screen as an empty box — the
   playback bar and the custom-size row both did (2026-09-13). */
[hidden] { display: none !important; }

/* The build panel: the configurator's chips, and a heading that reads as one */
.pm-buildhead { margin: 10px 0 2px; font-size: .95rem; }
.pm-addlabel { font-size: .74rem; color: var(--muted); font-weight: 700; }
.pm-details { margin-top: 8px; }
.pm-details > summary { cursor: pointer; font-size: .82rem; }
.pm-details[open] > summary { margin-bottom: 6px; }

/* ---- Pressure Lab: who the mattress is for (2026-09-13) --------------- */
.pm-kinds { display: flex; gap: 10px; flex-wrap: wrap; }
.pm-kind {
  display: flex; align-items: center; gap: 10px; text-align: left;
  padding: 10px 14px; border-radius: 12px; border: 1px solid #e5e7eb;
  background: #fff; color: inherit; cursor: pointer; min-width: 210px;
}
.pm-kind:hover:not([disabled]) { border-color: var(--brand-tint-2, #c9a6d4); }
.pm-kind.on { border-color: var(--brand, #470054); box-shadow: 0 0 0 2px var(--brand-tint, #f3e8f7) inset; }
.pm-kind[disabled] { opacity: .55; cursor: default; }
.pm-kind-ic { font-size: 1.5rem; line-height: 1; }
.pm-kind strong { display: block; font-size: .95rem; }
.pm-kind small { display: block; color: var(--muted); font-size: .74rem; }
.pm-sides { display: flex; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.pm-sides[hidden] { display: none; }
.pm-side {
  flex: 1 1 220px; display: flex; flex-direction: column; gap: 1px; text-align: left;
  padding: 8px 12px; border-radius: 10px; border: 1px solid #e5e7eb;
  border-left: 5px solid #d8dbe2; background: #fff; color: inherit; cursor: pointer;
}
/* The app-wide `button:hover` paints brand purple, which swallowed the side's
   own colour and turned the teal half violet on hover (Dr Daniel,
   2026-09-13). Each side hovers in a pale wash of ITS colour, and an active
   tab does not change at all — there is nothing to preview. */
.pm-side:hover { border-color: var(--brand-tint-2, #c9a6d4); box-shadow: none; }
.pm-side[data-side="embroidery"]:hover { background: #f8f4fe; }
.pm-side[data-side="label"]:hover { background: #f0faf8; }
.pm-side[data-side="embroidery"].on:hover { background: #f3ecfe; }
.pm-side[data-side="label"].on:hover { background: #e4f6f3; }
.pm-side.on { border-left-color: var(--brand, #470054); background: var(--brand-tint, #f3e8f7); }
.pm-side strong { font-size: .84rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
/* A colour per person (Dr Daniel, 2026-09-13: "Make 2 distinct colors for the
   2 sides") — violet for the embroidery half, teal for the label half, and
   the same two carry onto the capture list and every capture card, so a
   picture always says whose side it came from. Colour is never the only
   signal: the side is written out beside it. */
.pm-side[data-side="embroidery"] { border-left-color: #ddd0f7; }
.pm-side[data-side="embroidery"] strong { color: #6d28d9; }
.pm-side[data-side="embroidery"].on { border-left-color: #7c3aed; background: #f3ecfe; }
.pm-side[data-side="label"] { border-left-color: #bfe9e2; }
.pm-side[data-side="label"] strong { color: #0f766e; }
.pm-side[data-side="label"].on { border-left-color: #0d9488; background: #e4f6f3; }
.pm-sidetag[data-side="embroidery"] { color: #6d28d9; }
.pm-sidetag[data-side="label"] { color: #0f766e; }
/* The side's colour marks the build that was CHOSEN for that half, and
   nothing else (Dr Daniel, 2026-09-14). Every chip in the list already belongs
   to the person named above it, so a stripe down all of them said nothing; on
   the one final chip it says which half this build is for. The rest keep the
   plain hairline. */
.pm-cap.final[data-side="embroidery"] {
  border: 1px solid #7c3aed; border-left: 5px solid #7c3aed;
  box-shadow: 0 0 0 2px #f3ecfe inset;
}
.pm-cap.final[data-side="label"] {
  border: 1px solid #0d9488; border-left: 5px solid #0d9488;
  box-shadow: 0 0 0 2px #e4f6f3 inset;
}
.pm-side-who { font-size: 1rem; font-weight: 800; }
/* The register's filters share the title's row, so each takes only the width
   it needs (Dr Daniel, 2026-09-14). */
.pm-reghead h2 { margin: 0; }
.pm-reghead input[type="text"], .pm-reghead input:not([type]) { width: 210px; }
.pm-reghead select { max-width: 150px; }
/* Date and clinician sit at the right end of the fitting's own line, each
   label in front of its control so the row stays one line tall (Dr Daniel,
   2026-09-14). On a narrow screen the toolbar wraps them as a pair. */
/* Date and clinician are one group: they travel together, and when the row is
   tight it is the phone number that gives way, not the pair. */
.pm-headfields { display: flex; align-items: center; gap: 10px; flex: none; flex-wrap: nowrap; }
.pm-headphone { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pm-headfield { display: flex; align-items: center; gap: 6px; }
.pm-headfield select { max-width: 168px; }
.pm-headfield input { width: 132px; }
/* When the row gets tight the phone number goes before the pair does: it is
   on the customer's own page, and the date and the clinician are what this
   screen is about (Dr Daniel, 2026-09-14: "clinician same grid row as the
   date"). */
@media (max-width: 1330px) {
  .pm-headphone { display: none; }
  .pm-headfields { gap: 8px; }
  .pm-headfield select { max-width: 140px; }
  .pm-headfield input { width: 116px; }
  .pm-headfield label { font-size: .68rem; }
}
.pm-headfield label { margin: 0; font-size: .72rem; color: var(--muted); white-space: nowrap; }
.pm-headfield input, .pm-headfield select {
  min-height: 30px; padding: 3px 9px; font-size: .82rem; border-radius: 9px;
}
/* The mat settings box (Dr Daniel, 2026-09-14): the things you set once and
   then forget, in sections, each control on a line of its own rather than
   three dropdowns shoulder to shoulder in a toolbar. */
.pm-settings { display: flex; flex-direction: column; gap: 16px; }
.pm-set-sect { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.pm-set-sect > select, .pm-set-sect > button { width: 100%; }
.pm-set-sect > button { justify-content: center; }
.pm-set-note { margin: 0; font-size: .8rem; }
/* The frozen row that names whose half is on the mat (Dr Daniel, 2026-09-14).
   It sits between the header card and the working card, so it takes the top
   of the screen the moment the two side cards scroll away — and the other
   person's chip on it switches the page to that side. It slides UNDER the
   topbar (z-index 7 against the topbar's 10); in a popup there is no topbar,
   so it rides at the very top. */
/* Whose half is on the mat, at the left end of the frozen foot beside Save
   and Cancel (Dr Daniel, 2026-09-14: "relocate the buttons to the left of the
   bottom row, same row as save and billing button"). No frame of its own — it
   is part of that bar. */
.pm-stick { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-right: auto; }
.pm-stick[hidden] { display: none; }
/* Colour means CHOSEN, and nothing else (Dr Daniel, 2026-09-14: "the
   not-clicked one shall have no colour. When click then the colour shall
   appear"). The one you are not building is plain grey on white; the one you
   are is filled in its own colour, white type, its name in full. */
.pm-stickchip {
  display: inline-flex; align-items: baseline; gap: 7px; min-width: 0;
  border: 1px solid var(--line); background: var(--card); color: var(--muted);
  border-radius: 999px; padding: 5px 13px; font: inherit; cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.pm-stickchip .pm-stick-side {
  font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .04em;
  color: inherit;
}
.pm-stickchip strong { font-size: .95rem; color: inherit; }
.pm-stick-n { font-size: .72rem; color: inherit; opacity: .78; }
.pm-stickchip:not(.on):hover { border-color: var(--brand-tint-2, #c9a6d4); color: var(--ink, #1f2937); }
.pm-stickchip.on { cursor: default; color: #fff; border-color: transparent; }
/* Flip sits with the two people it swaps, in the foot bar — with no fill of
   its own (Dr Daniel, 2026-09-14: "flip sides remove the background colour"),
   because on this bar a filled chip means "this is the half you are building"
   and nothing else. */
/* Bands, Values and Raw bytes are ways of LOOKING at the map, not states, so
   they carry no fill (Dr Daniel, 2026-09-14: "bands & values buttons remove
   the background colour"). Raw bytes goes with them: one tinted button in a
   row of three plain ones reads like an accident. */
/* No outline either (Dr Daniel, 2026-09-14): on this bar a box round a
   button reads as a state, and these are only ways of LOOKING at the map.
   The colour of the words carries the hover and the chosen one. */
button.pm-viewbtn, button.pm-viewbtn:hover,
button.pm-plain, button.pm-plain:hover {
  background: transparent; border: none; color: var(--muted); box-shadow: none;
}
button.pm-viewbtn:hover, button.pm-plain:hover { color: var(--brand); }
button.pm-viewbtn.active, button.pm-plain.active { color: var(--brand); font-weight: 700; }
/* button.pm-flip, not .pm-flip: the house ghost button is written
   button.ghost, which outweighs a bare class and would keep its tint. */
button.pm-flip, button.pm-flip:hover {
  background: transparent; border: none; border-radius: 999px;
  color: var(--muted); box-shadow: none;
}
button.pm-flip:hover { color: var(--brand); }
/* The capture box: the map across the top, its two readings side by side
   under it, and the build's code up in the title row (Dr Daniel, 2026-09-14). */
.pm-capshot { margin-bottom: 12px; }
.pm-capstats { align-items: start; }
.modal-head .pm-headcode { flex: none; margin-left: auto; color: var(--muted); font-size: .86rem; }
/* Freeze in the title row of the full-page map. */
.modal-head .pm-zoomfreeze { flex: none; margin-left: auto; }
.pm-stickchip[data-side="embroidery"].on { background: #7c3aed; box-shadow: 0 2px 8px rgba(124, 58, 237, .35); }
.pm-stickchip[data-side="label"].on { background: #0d9488; box-shadow: 0 2px 8px rgba(13, 148, 136, .35); }
/* On a phone the row keeps one line: the two chips say whose side they are
   without the word in front of them, and the capture count goes — the row is
   there to name the person, and it is eating the top of an 800px screen. */
@media (max-width: 560px) {
  .pm-stick { gap: 6px; padding: 6px 10px; }
  .pm-stick-lead, .pm-stick .pm-stick-n { display: none; }
  .pm-stickchip { flex: 1 1 0; justify-content: center; padding: 5px 8px; }
}
.pm-sizes { display: flex; gap: 8px; flex-wrap: wrap; margin: 4px 0 10px; }
.pm-size {
  padding: 7px 14px; border-radius: 999px; border: 1px solid #e5e7eb;
  background: #fff; color: inherit; cursor: pointer; font-weight: 600;
}
.pm-size.on { background: var(--brand, #470054); color: #fff; border-color: var(--brand, #470054); }
/* The fitting as it reads back on a case note */
.note-pressure { border: 1px solid #e5e7eb; border-radius: 10px; padding: 10px; margin-bottom: 10px; background: #fff; }
.note-pressure-maps { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; }
.note-pressure-maps figure { margin: 0; }
.note-pressure-maps img {
  width: 100%; aspect-ratio: 2 / 1; object-fit: cover; cursor: zoom-in;
  border-radius: 8px; border: 1px solid #e5e7eb;
}
.note-pressure-maps figcaption { font-size: .8rem; margin-top: 2px; }

/* Upsell lines (2026-09-17): stage chips and their remark share one grid
   row; the remark takes what width the chips leave and wraps under them on
   a phone. */
.cn-chipline { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.cn-chipline .cn-chips { display: contents; }
.cn-chipline .cn-chipline-remark { flex: 1 1 140px; min-width: 120px; }
/* The word "Remark" wears the label track's own dress — same size, same
   grey (Dr Daniel, 2026-09-17: "the word remark pls make it same font and
   size and colour as the label track"). What is TYPED stays answer-sized. */
.cn-chipline-remark::placeholder { font-size: .8rem; color: var(--muted); opacity: 1; }
/* Type-first-then-chips rows (2026-09-17, third pass): now that the chips
   are a fixed right rail, the typed line and the bolster picker GROW to
   fill everything up to it (Dr Daniel: "Bolster chip, topper and mattress
   line make it full and stop at right before the educated button"). */
.cn-chipline .cn-stagetext { flex: 1 1 150px; min-width: 150px; width: auto; }
.cn-chipline .cn-opts { flex: 1 1 auto; min-width: 0; }
/* Inside the picker the chips are a real flex box again — the chipline's
   display:contents would otherwise stop the dashed button stretching. */
.cn-chipline .cn-opts .cn-chips { display: flex; }
/* The automatic "they bought it" tick (2026-09-17): a green badge at the
   right end of a Top selection row, lit from the customer's issued bills. */
.cn-bought { display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%; flex: 0 0 auto;
  background: #16a34a; color: #fff; }
.cn-bought svg { width: 12px; height: 12px; }
.cn-bought[hidden] { display: none; }
.cn-chipline .cn-bought { margin-left: auto; }
/* On the read-only card the tick rides at the end of the answer text. */
dd > .cn-bought { margin-left: 8px; vertical-align: -4px; }
/* The rule between what WE did and where THEY stand (2026-09-17:
   "Educated, gave sample pack | Interested, not interested"). */
.cn-chipsep { width: 1.5px; height: 22px; border-radius: 1px;
  background: var(--line); flex: 0 0 auto; margin: 0 3px; }
/* The stage chips form one right-aligned rail down the Top selection
   (Dr Daniel, 2026-09-17: "option chips align to the right of the grid
   row"). The cluster is its own flex box — out of display:contents — so
   it can take the auto margin and still wrap inside itself on a phone. */
.cn-chipline .cn-chips.cn-stagechips { display: flex; flex-wrap: wrap;
  align-items: center; gap: 6px; margin-left: 12px; justify-content: flex-end; }
/* The bought tick then sits right beside the rail, not across a gulf. */
.cn-chipline .cn-stagechips ~ .cn-bought { margin-left: 8px; }
/* The bed-sinking answers (2026-09-17, third pass: "make the buttons
   smaller? 'Pt claimed' 'we observed'. Both need remarks"): two stacked
   lines, each a printed label at a FIXED width (so the ✅/❌ pairs line up
   between the two), a small chip pair, and that answer's own remark
   filling the rest of the line. */
.cn-sinkrow { display: flex; flex-direction: column; gap: 4px; }
.cn-sinkline { display: flex; align-items: center; gap: 6px; min-width: 0; }
.cn-sinkline .cn-chipline-remark { flex: 1 1 60px; min-width: 60px; }
.cn-sinkgrp { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; }
/* No reserved label width — the marks sit right after the words ("the gap
   between subtitle and answer why so far away? Pls minimise it"), and
   "Pt claimed" / "We observed" are near enough the same length that the
   pairs still read as a column. */
.cn-sinklab { font-size: .76rem; font-weight: 600; color: var(--muted); flex: 0 0 auto; }
.cn-sinkgrp .cn-chip { padding: 1px 6px; font-size: .7rem; }
/* The ❌ keeps its red, drawn at the SAME size as the check (2026-09-17:
   "why x chip bigger than the tick?"). */
.cn-sinkgrp .cn-chip.cn-sinkno { color: #a8322a; }
.cn-sinkgrp .cn-chip.cn-sinkno.on { background: #d9584f; border-color: #d9584f; color: #fff; }
/* Rows without a flex line (the pillow picker, 2nd/3rd choice) keep their
   full width; the tick floats at the row's right end instead, and only a
   LIT tick reserves its lane — so it never covers the picker's ✕ clear. */
.cn-boughtwrap { position: relative; display: block; }
.cn-boughtwrap:has(> .cn-bought:not([hidden])) { padding-right: 26px; }
.cn-boughtwrap > .cn-bought { position: absolute; right: 0; top: 50%;
  transform: translateY(-50%); }
