html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
html, body { overflow-x: hidden; }

/* PSBMS HUB — base styles
   Mirrors the visual language of the original single-file app:
   navy sidebar, white content area, sticky table headers, etc.

   Theming: all colours run through CSS variables. Light is the default
   (:root); dark mode is applied by setting <html data-theme="dark"> (done by
   the early theme script in shell.php / login.php). The navy sidebar and the
   brand navy stay the same in both modes; --heading is used for navy-coloured
   TEXT so it lightens on dark backgrounds. */

:root {
  color-scheme: light;
  --navy:        #1e3a5f;
  --navy-dark:   #15273f;
  --navy-light:  #2a4d7a;
  /* R475 — link ink as its OWN token. --navy-light is a brand/CHROME colour
     (sidebar hovers, button hovers, focus rings) and is deliberately NOT
     redefined for dark mode, so every rule that borrowed it for TEXT rendered
     #2a4d7a on a #1a212e card — the near-invisible Invoice/Quotation/Work
     order column links. Light mode keeps the exact same ink; only dark
     changes, to the blue already used for --info-fg. */
  --link:        #2a4d7a;
  /* R381: modal header band — themable in Settings → Themes. */
  --modal-head-bg: #1e3a5f;
  --modal-head-fg: #ffffff;
  --accent:      #f59e0b;
  --bg:          #f4f6f8;
  --card:        #ffffff;
  --field-bg:    #ffffff;   /* R349b: inputs/selects/textareas. Until now these
                               had NO background rule at all — they fell through
                               to the browser default, which is why recolouring
                               --card left every form field stubbornly white. */
  --surface-2:   #f8fafc;   /* subtle secondary surfaces (empty cells, toolbars) */
  --text:        #1f2937;
  /* R363: was #6b7280 — measured 4.46:1 on --bg #f4f6f8, marginally under
     WCAG AA's 4.5. One step darker clears it (4.60) and reads identically. */
  --text-muted:  #69707d;
  --heading:     #1e3a5f;   /* navy-coloured text (headings, titles) */
  --border:      #e5e7eb;
  --row-hover:   #f0f4f8;
  --today-bg:    #fff7ed;   /* "today" highlight in calendars */
  /* Soft tint panels (flip to muted dark equivalents in dark mode). */
  --tint-red:    #fef2f2;
  --tint-amber:  #fef9f4;
  --tint-green:  #eef6ee;
  --tint-blue:   #eef4fb;
  --tint-indigo: #e0e7ff;
  --tint-peach:  #fbe7d6;
  --tint-slate:  #f1f5f9;
  --success:     #059669;
  --danger:      #dc2626;
  --warning:     #d97706;
  /* VC-248 — consolidated design tokens (Visual Consistency plan).
     Fonts: one sans + one mono app-wide. Status hues: exactly one bg + one
     text colour per semantic, themed for light AND dark. Type scale and radii
     replace the scattered literals. */
  --font-sans:   -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono:   ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --info:        #2563eb;
  --purple:      #7c3aed;
  --text-subtle: rgba(31,41,55,0.72);
  --ok-bg:#dcfce7;   --ok-fg:#166534;
  --err-bg:#fee2e2;  --err-fg:#991b1b;
  --warn-bg:#fef3c7; --warn-fg:#92400e;
  --info-bg:#dbeafe; --info-fg:#1e40af;
  --purple-bg:#ede9fe; --purple-fg:#5b21b6;
  --mut-bg:#e2e8f0;  --mut-fg:#475569;
  /* Sidebar text (navy band is identical in both themes). */
  --nav-text:     rgba(255,255,255,0.85);
  --nav-text-dim: rgba(255,255,255,0.72);
  /* Type scale (matches the app's real usage: tables/base 14, small 12/13). */
  --fs-xs:11px; --fs-sm:12px; --fs-base:13px; --fs-md:14px;
  --fs-lg:16px; --fs-xl:20px; --fs-2xl:24px;
  --fw-regular:400; --fw-medium:600; --fw-bold:700;
  /* Radii: sm = inputs/buttons, md = cards/panels, lg = large surfaces, pill = badges/chips. */
  --radius-sm:4px; --radius-md:8px; --radius-lg:12px; --radius-pill:999px;
  --shadow-sm:   0 1px 2px rgba(0,0,0,0.05);
  --shadow:      0 1px 4px rgba(0,0,0,0.08);
}

/* ============ Dark theme ============ */
html[data-theme="dark"], html[data-base="dark"] {
  color-scheme: dark;
  --bg:          #0f141c;
  --card:        #1a212e;
  --field-bg:    #141b27;   /* R349b — sits between --bg and --card so a field
                               reads as recessed. Previously the browser's own
                               dark-mode grey, which never matched the palette. */
  --surface-2:   #222b3a;
  --text:        #e5e7eb;
  --text-muted:  #9aa6b6;
  --heading:     #d6e4f7;
  --border:      #2d3748;
  --row-hover:   #253044;
  --today-bg:    rgba(245,158,11,0.16);
  --tint-red:    #3a1f1f;
  --tint-amber:  #33291a;
  --tint-green:  #163024;
  --tint-blue:   #1c2a40;
  --tint-indigo: #262649;
  --tint-peach:  #3a2a1e;
  --tint-slate:  #222c3c;
  /* VC-248 dark values — text-role hues brighten; bg-role hues darken. */
  --purple:      #8169d8;
  --text-subtle: rgba(229,231,235,0.75);
  --ok-bg:#163024;   --ok-fg:#6ee7b7;
  --err-bg:#3a1f1f;  --err-fg:#fca5a5;
  --warn-bg:#33291a; --warn-fg:#fcd34d;
  --info-bg:#1c2a40; --info-fg:#93c5fd;
  --purple-bg:#262649; --purple-fg:#c4b5fd;
  --mut-bg:#222c3c;  --mut-fg:#9aa6b6;
  --shadow-sm:   0 1px 2px rgba(0,0,0,0.40);
  --shadow:      0 2px 6px rgba(0,0,0,0.50);
  --link:        #93c5fd;   /* R475 — see the :root note; matches --info-fg */
}
/* Elements that use light pastel literals get dark-mode equivalents. */
/* R283: dark-theme toast overrides removed — .toast now uses the semantic
   --ok/--err/--info token pairs, which flip with the theme automatically. */
html[data-theme="dark"] .cal-chip-t, html[data-base="dark"] .cal-chip-t     { background: #0b3d2e; color: #6ee7b7; }
html[data-theme="dark"] .cal-chip-m, html[data-base="dark"] .cal-chip-m     { background: #1e3252; color: #93c5fd; }
html[data-theme="dark"] .rt-btn:hover, html[data-base="dark"] .rt-btn:hover   { background: #2a3446; }

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body { font-family: var(--font-sans); font-size: var(--fs-md); color: var(--text); background: var(--bg); }
/* VC-248 P0: form controls inherit the app font instead of the UA default
   (the source of the harvested bare-Arial / 13.3333px nodes). */
input, select, textarea, button { font-family: var(--font-sans); font-size: inherit; }
/* R349b — text-entry controls follow the theme. Checkboxes, radios, colour
   swatches, ranges and file/button inputs are deliberately excluded: those are
   drawn by the platform and a background would either do nothing or wreck them.
   Anything carrying its own inline background (most modals use var(--card))
   still wins, and those are themed already. */
input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="image"]):not([type="reset"]),
select, textarea { background: var(--field-bg); color: var(--text); }
code, pre, kbd, .mono { font-family: var(--font-mono); }
/* VC-248 P4: heading levels bound to fixed styles (component-scoped rules
   below may still specialise; red/semantic colours are never structural). */
h1 { font-size: var(--fs-2xl); font-weight: var(--fw-bold); color: var(--heading); }
h2 { font-size: var(--fs-xl); font-weight: var(--fw-bold); color: var(--heading); }
h3 { font-size: var(--fs-lg); font-weight: var(--fw-medium); color: var(--heading); }
h4 { font-size: var(--fs-base); font-weight: var(--fw-medium); color: var(--heading); }
a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }

/* R469 — cross-record links inside list cells (Invoice / Quotation columns on
   the Work Orders list). Not underlined at rest, so a dense table stays calm.
   R470 (Piotr): underline restored ON HOVER — it is the clearest signal that
   the thing under the cursor is clickable, and it costs nothing at rest. */
.rec-xlink { color: var(--link); text-decoration: none; font-weight: 600; }
.rec-xlink:hover,
.rec-xlink:focus-visible { text-decoration: underline; color: var(--heading); }

/* ============ Login page ============ */
.login-body { display: grid; place-items: center; min-height: 100vh; background: linear-gradient(135deg, var(--navy) 0%, var(--navy-dark) 100%); }
.login-card { background: var(--card); padding: 36px 32px; border-radius: var(--radius-lg); min-width: 340px; max-width: 380px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); }
.login-brand { text-align: center; margin-bottom: 24px; }
.login-logo { font-size: 28px; font-weight: 800; /* brand lockup — intentional, not part of the type scale */ color: var(--heading); letter-spacing: 2px; }
.login-tag  { font-size: 13px; color: var(--text-muted); margin-top: 4px; }
.field { display: block; margin-bottom: 14px; }
.field span { display: block; font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
.field input { width: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-md); font-size: 14px; }
.field input:focus { outline: none; border-color: var(--focus-border, var(--navy-light)); box-shadow: 0 0 0 3px var(--focus-ring, rgba(42,77,122,0.15)); }
.btn-primary { width: 100%; background: var(--navy); color: white; border: 0; padding: 11px; border-radius: var(--radius-sm); font-size: var(--fs-md); font-weight: var(--fw-medium); cursor: pointer; }
.btn-primary:hover { background: var(--navy-light); }
.error { color: var(--danger); font-size: 13px; margin-top: 10px; }
.login-foot { text-align: center; margin-top: 20px; font-size: 12px; color: var(--text-muted); }

/* Sign-in card + the cross-sell link stack together as one centred group, so
   adding the link below never disturbs the card's centring on the login grid. */
.login-stack { display: flex; flex-direction: column; align-items: center; }

/* R270 (Piotr): the Engineering Software cross-sell block was removed from
   the login page (markup gone from login.php; its styles removed here).
   .login-stack stays — the sign-in card wrapper still uses it. */

/* ============ App shell ============ */
/* overflow:hidden on the grid + min-width:0 on the scrolling main column
   keeps the sidebar at exactly 100vh on every page — without this, a wide
   table or oversized card can push the grid track taller than the viewport
   and stretch the sidebar to match. */
/* R462 (Piotr): sidebar narrowed 220px -> 190px, giving 30px back to every
   page. Measured before choosing: no nav label clips at any width down to
   172px (checked scrollWidth vs clientWidth on every link), the footer icon
   row stays on one line, and the logo scales with its container — at 190px it
   renders 164px against its 170px cap, so it is essentially unchanged.
   Now a token: the width was hardcoded in exactly one place, and this keeps it
   that way. */
:root { --nav-w: 190px; }
.app-body { display: grid; grid-template-columns: var(--nav-w) 1fr; height: 100vh; overflow: hidden; }
.sidebar { background: var(--navy); color: white; display: flex; flex-direction: column; height: 100vh; }
.sidebar-brand { padding: 18px 18px 14px; border-bottom: 1px solid var(--navy-light); }
.brand-mark { font-size: 22px; font-weight: 800; /* brand lockup — intentional, not part of the type scale */ letter-spacing: 1.5px; }
.brand-sub { font-size: 11px; opacity: 0.7; margin-top: 4px; }
.brand-logo { display: block; width: 100%; max-width: 170px; height: auto; }
.nav { flex: 1; padding: 8px 6px; overflow-y: auto; }
.nav-item { display: flex; align-items: center; gap: 9px; padding: 5px 12px; color: var(--nav-text); border-radius: var(--radius-sm); cursor: pointer; font-size: 13px; margin-bottom: 1px; line-height: 1.35; }
/* R333: per-item line icons (display modes retired in R336 — always on;
   set pre-paint by the shell inline script; switch in the sidebar footer). */
.nav-ico { flex: 0 0 auto; width: 16px; height: 16px; display: inline-flex; opacity: 0.9; }
.nav-ico svg { width: 100%; height: 100%; display: block; }
.nav-label { flex: 1 1 auto; min-width: 0; }
.nav-badge { flex: 0 0 auto; }
/* R336: nav icons are always on (R333/R334 display modes retired). The
   footer is two icon rows: theme switch + account actions, with popovers
   for the Landing-page picker and Account security. */
.theme-opt svg { width: 16px; height: 16px; display: block; margin: 0 auto; }
.foot-icons { display: flex; gap: 3px; margin: 0 0 10px; background: rgba(255,255,255,0.08); border-radius: var(--radius-md); padding: 3px; }
.foot-ico { flex: 1; background: transparent; border: 0; color: var(--nav-text-dim); padding: 6px 2px; border-radius: var(--radius-sm); cursor: pointer; line-height: 1; }
.foot-ico:hover { color: #fff; background: var(--navy-light); }
.foot-ico svg { width: 17px; height: 17px; display: block; margin: 0 auto; }
.foot-pop { position: fixed; z-index: 1600; background: var(--navy); border: 1px solid rgba(255,255,255,0.22); border-radius: var(--radius-md); padding: 6px; box-shadow: 0 10px 28px rgba(0,0,0,0.45); min-width: 200px; max-height: 55vh; overflow: auto; }
.foot-pop button { display: block; width: 100%; text-align: left; background: none; border: 0; color: #fff; padding: 7px 10px; border-radius: var(--radius-sm); font-size: 13px; cursor: pointer; }
.foot-pop button:hover { background: var(--navy-light); }
.foot-pop button.cur { background: var(--navy-light); font-weight: 600; box-shadow: inset 3px 0 0 var(--accent); }
@media (hover: none), (max-width: 768px) { .foot-ico { min-height: 40px; } .foot-pop button { min-height: 44px; } }
.nav-item:hover { background: var(--navy-light); color: white; text-decoration: none; }
.nav-item.active { background: var(--navy-light); color: white; font-weight: 600; }
/* Collapsible nav groups (R-NAVGROUPS). Headers are buttons so they're
   keyboard-reachable; items inside a group indent slightly so the hierarchy
   reads at a glance. */
/* Group headers read as PARENT categories: a shaded full-width band with an
   amber accent bar + chevron, clearly heavier than the page links nested
   under them. */
.nav-group-head { display: flex; align-items: center; gap: 6px; width: 100%; background: rgba(255,255,255,0.08); border: 0; border-left: 3px solid var(--accent); text-align: left; padding: 6px 8px 6px 7px; margin-top: 10px; color: var(--nav-text); font-size: var(--fs-xs); font-weight: var(--fw-bold); text-transform: uppercase; letter-spacing: 1px; cursor: pointer; border-radius: var(--radius-sm); }
.nav-group-head:hover { background: rgba(255,255,255,0.14); color: #fff; }
.nav-group-chev { font-size: 9px; width: 10px; display: inline-block; color: var(--accent); }
.nav-group-body .nav-item { padding-left: 22px; }
.sidebar-foot { padding: 12px; border-top: 1px solid var(--navy-light); }
.user-chip { padding: 6px 0 10px; }
.user-name { font-weight: 600; font-size: 13px; }
.user-role { font-size: 11px; opacity: 0.7; }
.btn-ghost { width: 100%; background: transparent; color: white; border: 1px solid rgba(255,255,255,0.3); padding: 7px; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--fs-base); }
.btn-ghost:hover { background: var(--navy-light); }

/* Theme switch (Auto / Light / Dark) — lives in the navy sidebar footer. */
.theme-switch { display: flex; gap: 3px; margin: 0 0 10px; background: rgba(255,255,255,0.08); border-radius: var(--radius-md); padding: 3px; }
.theme-opt { flex: 1; background: transparent; border: 0; color: var(--nav-text-dim); font-size: var(--fs-sm); padding: 5px 2px; border-radius: var(--radius-sm); cursor: pointer; line-height: 1.1; }
.theme-opt:hover { color: #fff; }
.theme-opt.active { background: var(--navy-light); color: #fff; font-weight: 600; }

/* R283 (UX audit): coarse-pointer touch targets — sidebar links, group heads
   and theme controls get 44px-tall hit areas on touch devices and mobile
   widths. Desktop mouse density is unchanged. */
@media (hover: none), (max-width: 768px) {
  .nav-item { min-height: 44px; display: flex; align-items: center; padding-top: 8px; padding-bottom: 8px; }
  .nav-group-head { min-height: 44px; }
  .theme-opt { min-height: 40px; }
}

/* R437 (Piotr, 03/08): the mobile drawer was too "relaxed" — only 11 of the
   28 links fitted on a phone, so Money/Company needed a scroll every time.
   Tighten the drawer ONLY (the rules above still govern in-page controls):
   rows 44 → 36px, group heads 44 → 30px, and the brand block loses ~45px.
   36px full-width rows still clear the practical touch floor; the 44px
   guidance is for small isolated targets, not full-width list rows. */
@media (max-width: 768px) {
  .sidebar-brand { padding: 10px 14px 8px; }
  /* R437b (Piotr): the hamburger is fixed at top:8/left:8 and is 44px square,
     so it sat on top of the logo once the brand block was trimmed. Indent the
     LOGO past it (not the whole block — "HUB" and the search box below stay
     aligned to the drawer's left edge).
     R437c: R437's max-width:132px SHRANK the logo (Piotr spotted it) — keep the
     full 170px from the base rule and only shift it. 14 + 44 + 170 = 228px,
     inside the 250px drawer. */
  .brand-logo { margin-left: 44px; }
  .brand-sub { margin-top: 2px; }
  .nav { padding: 5px 6px; }
  .nav-item { min-height: 36px; padding-top: 5px; padding-bottom: 5px; margin-bottom: 0; }
  .nav-group-head { min-height: 30px; margin-top: 6px; padding-top: 4px; padding-bottom: 4px; }
  .nav-group-body .nav-item { padding-left: 20px; }
  .sidebar-foot { padding: 8px 12px 10px; }
  .user-chip { padding: 4px 0 8px; }
  .foot-icons { margin-bottom: 6px; }
}

/* R283 (UX audit): respect the OS "reduce motion" preference — remove
   transitions/animations and smooth scrolling app-wide for users who ask
   their system for less movement. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Landing-page picker (sidebar foot) — the native dropdown list pops up on a
   light OS panel while the options inherit the sidebar's white text, making
   them unreadable in both themes. Force a dark scheme + explicit option
   colours so the list is navy-on-white-text everywhere (the sidebar is navy
   in BOTH light and dark modes). */
#landing-select { color-scheme: dark; }
#landing-select option { background: var(--navy); color: #fff; }

/* Nav toggle (hamburger) + backdrop.
   Desktop (≥1000px): the toggle collapses / restores the sidebar (.nav-collapsed).
   Below 1000px: it opens the off-canvas drawer (.nav-open); the
   max-width:999px block below adjusts size. The backdrop is mobile-only. */
.nav-toggle {
  display: flex; align-items: center; justify-content: center;
  position: fixed; top: 8px; left: 8px; z-index: 1100;
  width: 40px; height: 40px; padding: 0;
  background: var(--navy); color: #fff; border: none; border-radius: var(--radius-md);
  font-size: 21px; line-height: 1; cursor: pointer;
  box-shadow: 0 1px 5px rgba(0,0,0,0.3);
}
.nav-toggle:hover { background: var(--navy-light); }
.nav-backdrop { display: none; }

/* Desktop sidebar collapse. The grid column animates to 0 and the sidebar
   content is clipped; the fixed toggle stays put so the panel can be
   restored. Scoped to desktop so it never fights the mobile drawer rules. */
@media (min-width: 1000px) {
  .app-body { transition: grid-template-columns 0.2s ease; }
  .app-body.nav-collapsed { grid-template-columns: 0 1fr; }
  .app-body.nav-collapsed .sidebar { overflow: hidden; }
  /* Drop the toggle clear of the logo so it doesn't cover the brand. */
  .sidebar-brand { padding-top: 50px; }
  /* When collapsed, keep the page heading clear of the fixed toggle.
     R420 (Piotr, 02/08 — spotted on iPad): this listed ONLY .page-toolbar, so
     the two page headers that are NOT a toolbar were left underneath the
     hamburger — the Record 360 identity strip (.rec-id: its back link started
     at 18px, the toggle spans 8–48px) and the Notes title bar (.np-mtitle, H1
     at 24px). Same failure as R413c: a rule aimed at one class while other
     pages use a different one. Measured across 8 routes; the six
     .page-toolbar pages already cleared at 56px, these two did not.
     Specificity 0,3,0 beats the .np-mtitle rule notes.js injects at runtime,
     so this holds without !important. */
  .app-body.nav-collapsed .page-toolbar,
  .app-body.nav-collapsed .rec-id,
  .app-body.nav-collapsed .np-mtitle { padding-left: 56px; }
}

/* min-width:0 stops a wide child (long table, wide card) from pushing the
   grid column out — without it grid items default to min-content which
   blows wide content past the column. */
.main-col { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }
.main { overflow-y: auto; overflow-x: auto; padding: 0; min-width: 0; flex: 1; }

/* Persistent banner strip (e.g. backup reminder) above the SPA mount. */
.app-banner-strip { display: flex; align-items: center; gap: 10px; padding: 8px 14px;
  background: var(--tint-amber); color: var(--text); border-bottom: 1px solid var(--border); font-size: 13px; }
.app-banner-strip a { font-weight: 700; color: var(--heading); text-decoration: underline; }
.app-banner-dismiss { margin-left: auto; border: 0; background: transparent; cursor: pointer;
  color: var(--text-muted); font-size: 16px; line-height: 1; padding: 6px 10px; min-width: 32px; }
.app-banner-dismiss:hover { color: var(--text); }

/* ============ Page chrome ============ */
.page-toolbar { position: sticky; top: 0; z-index: 6; background: var(--card); padding: 14px 24px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 12px; box-shadow: var(--shadow-sm); }
.page-title { font-size: 20px; font-weight: 700; color: var(--heading); margin: 0; }
.toolbar-spacer { flex: 1; }
.page-body { padding: 18px 24px; }

/* ============ Tables ============ */
.table-wrap { background: var(--card); border-radius: var(--radius-md); box-shadow: var(--shadow); }
table.list { width: 100%; border-collapse: collapse; font-size: 14px; }
.list thead th { position: sticky; top: 52px; z-index: 5; background: var(--navy); color: white; text-align: left; padding: 10px 12px; font-weight: 600; font-size: 13px; }
.list tbody td { padding: 10px 12px; border-bottom: 1px solid var(--border); }
.fm-list tbody td { vertical-align: top; }
.fm-file-view:hover { text-decoration: underline; }
.list tbody tr.clickable { cursor: pointer; }
.list tbody tr.clickable:hover { background: var(--row-hover); }

/* R251 (Piotr): row hairlines + cursor highlight everywhere data is read in
   rows — the same experience as the Invoices list. `.data-table` previously
   had NO styling at all (bare rows on Bookkeeping Transactions / Templates /
   Quarterly VAT, and on Ledger + Audit which share the class); `.list`
   highlighted only rows explicitly marked clickable (so Reconcile/Payables
   had lines but no hover). Mobile card collapses are unaffected — they reset
   td borders/padding with !important. */
.data-table { width: 100%; border-collapse: collapse; font-size: var(--fs-md); }
.data-table th { text-align: left; padding: 8px 10px; font-weight: var(--fw-medium); color: var(--heading); border-bottom: 2px solid var(--border); }
.data-table td { padding: 8px 10px; border-bottom: 1px solid var(--border); }
.data-table tbody tr:hover,
.list tbody tr:hover { background: var(--row-hover); }
.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Numeric column headers — bump specificity so `.num` beats the generic
   `.list thead th { text-align:left }` rule above. Without these selectors,
   headers like Total / Value / Labour / Materials / Mileage / Margin sit
   left-aligned while their values below right-align, looking broken. */
.list thead th.num,
thead th.num,
table th.num { text-align: right; }
/* Sortable list headers (R-SORT) — cursor invites the click; the ▲▼
   indicator span is injected by the app.js delegated handler. */
table.list[data-sortable] thead th { cursor: pointer; user-select: none; }
table.list[data-sortable] thead th:hover { background: var(--navy-light); }
.sort-ind { font-size: 10px; opacity: 0.9; }

/* ============ Toast / messages ============ */
/* Above every overlay (detail views 1500, sheets 1600, modals 1700) so save
   feedback is never buried. */
.toast { position: fixed; right: 20px; bottom: 20px; padding: 12px 16px; border-radius: var(--radius-md); box-shadow: var(--shadow); z-index: 2000; font-size: 14px; }
/* R283 (UX audit): toasts moved onto the VC-248 semantic AA pairs — the
   legacy #d1fae5/#059669-style combinations failed normal-text contrast
   (~3.3:1). Token pairs flip with the theme automatically. */
.toast.success { background: var(--ok-bg);   color: var(--ok-fg); }
.toast.error   { background: var(--err-bg);  color: var(--err-fg); }
.toast.info    { background: var(--info-bg); color: var(--info-fg); }

/* ============ Buttons ============ */
.btn { background: var(--navy); color: white; border: 0; padding: 8px 14px; border-radius: var(--radius-sm); font-size: var(--fs-md); font-weight: var(--fw-medium); cursor: pointer; }
.btn:hover { background: var(--navy-light); }
.btn.secondary { background: var(--card); color: var(--heading); border: 1px solid var(--border); }
.btn.secondary:hover { background: var(--row-hover); }
.btn.danger { background: var(--danger); }
.btn.danger:hover { background: #b91c1c; }

/* ============ Forms ============ */
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
.form-grid .full { grid-column: 1 / -1; }
.form-grid label { display: block; }
.form-grid label > span { display: block; font-size: 12px; font-weight: 600; color: var(--text-muted); margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.3px; }
.form-grid label.cbx { display: flex; align-items: center; gap: 8px; }
.form-grid label.cbx > span { display: inline; font-size: 14px; font-weight: 400; color: var(--text); margin: 0; text-transform: none; letter-spacing: normal; }
.form-grid label.cbx > input[type="checkbox"] { flex: 0 0 auto; width: 16px; height: 16px; margin: 0; }
.form-grid input, .form-grid select, .form-grid textarea { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 14px; font-family: inherit; }
.form-grid textarea { min-height: 80px; resize: vertical; }

/* ============ Modal ============ */
/* 1700: above the mobile DetailView (1500) and BottomSheet (1600) so flows
   launched from them — e.g. the chase-email compose — layer correctly. */
.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1700; display: grid; place-items: center; padding: 20px; overflow-y: auto; }
.modal-card { background: var(--card); border-radius: var(--radius-lg); max-width: 640px; width: 100%; max-height: calc(100vh - 40px); overflow-y: auto; box-shadow: 0 20px 60px rgba(0,0,0,0.3); }
/* R381: modal headers are a solid themable band app-wide (R380's Planner look).
   Colours come from --modal-head-bg / --modal-head-fg (Settings → Themes). */
.modal-head { display: flex; align-items: center; padding: 16px 22px; border-bottom: none; background: var(--modal-head-bg); }
.modal-head h2 { margin: 0; font-size: 18px; color: var(--modal-head-fg); flex: 1; }
.modal-close { background: none; border: 0; font-size: 28px; line-height: 1; color: var(--text-muted); cursor: pointer; padding: 0 6px; }
.modal-close:hover { color: var(--text); }
.modal-head .modal-close { color: color-mix(in srgb, var(--modal-head-fg) 80%, transparent); }
.modal-head .modal-close:hover { color: var(--modal-head-fg); }
/* Buttons that sit ON the band (e.g. Planner's Done) become ghosts. */
.modal-head .btn, .modal-head .btn.secondary { background: transparent; color: var(--modal-head-fg); border: 1px solid color-mix(in srgb, var(--modal-head-fg) 50%, transparent); }
.modal-head .btn:hover, .modal-head .btn.secondary:hover { background: color-mix(in srgb, var(--modal-head-fg) 14%, transparent); }
.modal-card form { padding: 22px; }
.modal-actions { display: flex; gap: 10px; align-items: center; padding-top: 18px; margin-top: 18px; border-top: 1px solid var(--border); }

/* Nested rows inside modal forms (sites/contacts/line items) */
.nested-row input, .nested-row select, .nested-row textarea { padding: 6px 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 13px; font-family: inherit; width: 100%; }
.nested-row .del-row { padding: 5px 10px; min-width: 32px; }

/* ============ Planner ============ */
/* R387: overflow was `hidden`, which made the wrap the sticky containing
   block and the weekday header could never track #main's scroll. Clip is
   dropped; the radius moves onto the header row's corner cells instead. */
.planner-wrap { background: var(--card); border-radius: var(--radius-md); box-shadow: var(--shadow); }
.planner-grid thead th:first-child { border-top-left-radius: var(--radius-md); }
.planner-grid thead th:last-child  { border-top-right-radius: var(--radius-md); }
.planner-grid { width: 100%; border-collapse: collapse; table-layout: fixed; }
/* PLAN-UX P0 fallback (2026-07-09): if the desktop grid ever renders below the
   1000px tier (the JS agenda normally takes over there — this covers the
   resize-without-redraw edge), keep chips readable via contained horizontal
   scroll with a sticky person column instead of crushing 7 columns into 400px. */
@media (max-width: 999px) {
  .planner-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .planner-grid { min-width: 940px; }
  .planner-grid th:first-child, .planner-grid td:first-child {
    position: sticky; left: 0; z-index: 2; background: var(--card);
  }
}
.planner-grid th, .planner-grid td { border: 1px solid var(--border); }

.planner-corner-cell { width: 140px; background: var(--navy); }
.planner-day-head { background: var(--navy); color: white; padding: 8px; text-align: center; font-weight: 600; }
.planner-day-head .day-name { font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; }
.planner-day-head .day-date { font-size: 11px; opacity: 0.85; margin-top: 2px; }
.planner-day-head.today { background: var(--accent); color: var(--navy-dark); }
.planner-day-head.today .day-name { color: var(--navy-dark); }

.planner-name-cell { width: 140px; background: var(--bg); padding: 12px 10px; text-align: left; vertical-align: middle; font-weight: 600; color: var(--heading); border-right: 2px solid var(--navy) !important; }
.planner-name-cell .eng-name { font-size: 14px; }

/* R443 (Piotr, 03/08): the cell used to snap to a hard 2px navy outline in
   100ms — at a glance it read as "selected", not "pointing at". The outline is
   now permanently present but transparent (so its COLOUR can transition; you
   cannot ease an outline that doesn't exist yet), and it eases in at 40%
   strength alongside a row-tint. The item itself carries the real highlight —
   see the R443 block at the foot of this file. */
.planner-cell { height: 90px; vertical-align: top; padding: 4px; cursor: pointer; font-size: 12px; line-height: 1.35; outline: 2px solid transparent; outline-offset: -2px; transition: outline-color 200ms ease, background-color 200ms ease; overflow: hidden; background: var(--card); }
.planner-cell .cell-content { display: flex; flex-direction: column; gap: 3px; height: 100%; }
.planner-cell:hover { outline-color: color-mix(in srgb, var(--navy) 40%, transparent); background: var(--row-hover); }
.planner-cell.empty { background: var(--surface-2); }
.planner-cell.empty:hover { background: var(--row-hover); }
.planner-cell .wo-link { font-weight: 700; text-decoration: underline; color: inherit; }
.planner-cell .wo-link:hover { opacity: 0.7; }
/* R361: solid accent bar down the left, semi-transparent body. The left edge
   is square so the 4px border reads as a crisp stripe rather than a rounded
   sliver; the right stays rounded. Extra left padding keeps text off the bar. */
.planner-entry { padding: 4px 6px 4px 7px; border-radius: 0 var(--radius-sm) var(--radius-sm) 0; word-break: break-word; line-height: 1.25; font-size: 11px; flex: 1 1 auto; }

/* Category chips in the cell-edit modal */
.cat-chips { display: flex; flex-wrap: wrap; gap: 6px; }
/* PLAN-UX P2 (2026-07-09): nowrap stops a chip's label breaking mid-word at
   narrow widths, which read as a "clipped" chip in the audit. */
.cat-chip { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: var(--radius-pill); font-size: 13px; font-weight: 600; cursor: pointer; user-select: none; white-space: nowrap; }
.cat-chip input { margin: 0; cursor: pointer; }
.cat-chip:hover { opacity: 0.85; }
.cat-chip input:checked + span::after { content: ' ✓'; }

/* ============ Dashboard ============ */
.kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 16px; }
/* R397 + a: the 4px navy left border came off — a thick bar on one edge of a
   rounded card fights the corner radius, and a hairline border all round
   matches .rec-card and .dash-card so cards read as one family.
   BUT the first cut of R397 was half a change: Reports, Company Tax and the
   engineer Dashboard passed a SEMANTIC colour into `border-left-color`
   (good/bad/warn per value), so removing the bar demoted the one accent that
   meant something to an invisible 1px tint. R397a moves that colour onto the
   .kpi-value NUMBER instead — stronger than the bar was, and no side-tab.
   Lesson: check what a flagged rule is holding up before you pull it. */
.kpi-card { background: var(--card); padding: 14px 16px; border-radius: var(--radius-md); box-shadow: var(--shadow-sm); border: 1px solid var(--border); }
.kpi-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); }
.kpi-value { font-size: 24px; font-weight: 700; color: var(--heading); margin-top: 4px; }
.kpi-sub   { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
/* R383 — digits align vertically everywhere money is compared. */
.kpi-value { font-variant-numeric: tabular-nums; }
/* R383 — Dashboard hierarchy: ONE hero number + a quiet stat rail.
   (.kpi-row/.kpi-card still serve the engineer view and any future tiles.) */
.kpi-hero { display: block; background: var(--card); border-radius: var(--radius-md);
  box-shadow: var(--shadow); padding: 18px 22px; text-decoration: none; color: inherit; cursor: pointer;
  transition: background 0.12s ease; }
.kpi-hero:hover { background: var(--row-hover); text-decoration: none; }
.kpi-hero .kpi-value { font-size: 34px; margin-top: 4px; letter-spacing: -0.01em; }
.kpi-rail { display: flex; flex-wrap: wrap; gap: 6px 30px; padding: 12px 8px 2px; }
.kpi-rail > a, .kpi-rail > div { display: block; text-decoration: none; color: inherit;
  padding: 4px 6px; margin: -4px -6px 0; border-radius: var(--radius-sm); }
.kpi-rail > a:hover { background: var(--row-hover); text-decoration: none; }
.kpi-rail .kpi-label { display: flex; align-items: center; gap: 6px; }
.kpi-rail .kpi-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.kpi-rail .kpi-value { font-size: 19px; margin-top: 1px; }
@media (max-width: 768px) {
  .kpi-hero { padding: 14px 16px; }
  .kpi-hero .kpi-value { font-size: 28px; }
  /* R384: even two-up grid instead of ragged flex wrap on phones. */
  .kpi-rail { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; padding: 12px 2px 2px; }
  .kpi-rail > a, .kpi-rail > div { margin: 0; padding: 2px; }
  /* R384: the server-health readout (CPU/SSD/RAM/load/backup) is a desk
     metric — on a phone it wrapped the toolbar to three lines. */
  #server-health { display: none; }
}
/* R257 (Piotr): KPI cards light up under the cursor — the same row-hover cue
   as tables. Dashboard cards deep-link on click, so hover signals behaviour;
   the Reports KPI strip shares the component and gets the same feel. */
.kpi-card { transition: background 0.12s ease, box-shadow 0.12s ease; }
.kpi-card:hover { background: var(--row-hover); box-shadow: var(--shadow); }
a.kpi-card:hover { text-decoration: none; }

.dash-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
/* R406 — cards are user-arrangeable, so the grid can no longer assume a fixed
   wide+narrow pair per row. Three equal tracks with explicit spans gives the
   SAME layout for the default order (span 2 + span 1 == the old 2fr + 1fr)
   while letting any sequence of widths flow correctly. */
.dash-card.dc-wide { grid-column: span 2; }
.dash-card.dc-full { grid-column: 1 / -1; }
@media (max-width: 1300px) {
  .dash-card.dc-wide, .dash-card.dc-full { grid-column: 1 / -1; }
}
/* Grid children default to min-width:auto, so a card can never shrink below
   its content — the Chart.js canvas therefore pinned the revenue card (and
   the whole row) at its grown width while every other panel narrowed.
   min-width:0 lets the card shrink; Chart.js (responsive:true) then resizes
   the canvas to follow. */
.dash-grid > .dash-card { min-width: 0; }
.dash-card { background: var(--card); padding: 16px 18px; border-radius: var(--radius-md); box-shadow: var(--shadow); }
.dash-card h3 { margin: 0 0 12px; font-size: 14px; color: var(--heading); text-transform: uppercase; letter-spacing: 0.5px; }
/* DASH-UX R2 Task 1 (2026-07-09): below ~1300px viewport the 1fr side card
   (Top debtors / Top clients) drops under ~320px once the sidebar + padding
   are taken out, which clipped the Overdue + "chase ›" column. Collapse to a
   single column much earlier — cards stack full-width and nothing clips. */
@media (max-width: 1300px) { .dash-grid { grid-template-columns: 1fr; } }

/* ============ Settings ============ */
/* R349b — one width for every Settings card. The cards inside <form id="sf">
   were constrained to 920px by the form itself, while the cards after it
   carried (or lacked) their own inline max-width, so the column visibly
   stepped in and out down the page. 920px matches the form, and setting it
   here means new sections inherit it instead of having to remember. */
.settings-card { background: var(--card); padding: 18px 22px; border-radius: var(--radius-md); box-shadow: var(--shadow); margin-bottom: 14px; max-width: 920px; }
.settings-card h2 { margin: 0 0 14px; font-size: 16px; color: var(--heading); }

/* ============ My Time — calendar grid + day modal ============ */
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; }
.cal-head { background: var(--navy); color: white; padding: 6px 8px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; text-align: center; }
.cal-cell { background: var(--card); min-height: 90px; padding: 6px 8px; cursor: pointer; display: flex; flex-direction: column; transition: background-color 200ms ease; }
.cal-cell:hover { background: var(--row-hover); }
.cal-cell-other { background: var(--surface-2); color: var(--text-muted); opacity: 0.55; }
.cal-cell-today { background: var(--today-bg); border: 2px solid var(--accent); padding: 4px 6px; }
.cal-date { font-size: 12px; font-weight: 600; color: var(--heading); margin-bottom: 4px; }
.cal-cell-today .cal-date { color: var(--accent); }
.cal-chip { display: block; font-size: 11px; padding: 2px 6px; border-radius: var(--radius-sm); margin-bottom: 2px; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cal-chip-t { background: #dcfce7; color: #166534; }   /* hours = green */
.cal-chip-m { background: #dbeafe; color: #1e40af; }   /* mileage = blue */

.day-entry { display: flex; gap: 10px; align-items: center; padding: 8px 10px; background: var(--bg); border-radius: var(--radius-md); margin-bottom: 6px; }
.day-entry-icon { font-size: 18px; }
.day-entry-main { flex: 1; font-size: 13px; }
.day-entry.dragging { opacity: 0.45; outline: 2px dashed var(--navy); }
.day-entry-handle:active { cursor: grabbing; }
.day-add { background: var(--surface-2); border: 1px dashed var(--border); border-radius: var(--radius-md); padding: 12px; }

/* ============ Responsive tiers (Build Plan Phase 2) ============ */
/* Desktop ≥1000px is untouched. Below 1000px the "mobile patterns" apply
   (off-canvas drawer, wrapped toolbars, full-screen editors) for BOTH the
   tablet tier (700–999px) and the phone tier (<700px). The legacy phone
   density rules (single-column forms, hidden table columns, compact
   calendar) keep their proven 768px line — flagged in the phase report. */

/* ---- Mobile patterns: tablet + phone (<1000px) ---- */
@media (max-width: 999px) {
  .app-body { grid-template-columns: 1fr; height: 100dvh; }

  .nav-toggle {
    display: flex; align-items: center; justify-content: center;
    position: fixed; top: 8px; left: 8px; z-index: 1100;
    width: 44px; height: 44px; padding: 0;
    background: var(--navy); color: #fff; border: none; border-radius: var(--radius-md);
    font-size: 22px; line-height: 1; cursor: pointer;
    box-shadow: 0 1px 5px rgba(0,0,0,0.3);
  }

  .sidebar {
    position: fixed; top: 0; left: 0;
    /* R303: 100dvh (dynamic viewport) so the drawer footer + Log out sit above
       the mobile browser's bottom toolbar; 100vh fallback for old engines. */
    width: 250px; max-width: 84vw; height: 100vh; height: 100dvh; z-index: 1090;
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    box-shadow: 2px 0 12px rgba(0,0,0,0.25);
  }
  .app-body.nav-open .sidebar { transform: translateX(0); }

  .nav-backdrop {
    display: block; position: fixed; inset: 0; z-index: 1080;
    background: rgba(0,0,0,0.45);
    opacity: 0; pointer-events: none; transition: opacity 0.25s ease;
  }
  .app-body.nav-open .nav-backdrop { opacity: 1; pointer-events: auto; }

  /* R413 (Piotr, 02/08): the toolbar buttons used to drop to a second row
     because .page-title claimed width:100%. On Files that pushed More +
     Upload below the title and left them dangling on the left.
     R413c — the first attempt added justify-content:flex-end, which pushed
     the TITLE right too on every workbench page (Piotr: "Move Projects title
     to the left"). The reason it looked fine on Files and wrong on Projects:
     Files puts <h1 class="page-title"> straight into the toolbar, while
     wbHeader() wraps title+subtitle in an UNCLASSED <div> — so the .page-title
     rule was styling a grandchild, not the flex item. Grow the first CHILD
     instead, whatever it happens to be, and drop justify-content entirely. */
  .page-toolbar { flex-wrap: wrap; gap: 8px; padding: 10px 12px 10px 60px; }
  .page-toolbar > *:first-child { flex: 1 1 auto; min-width: 0; }
  .page-toolbar .page-title { width: auto; min-width: 0; margin: 0;
                              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .toolbar-spacer { display: none; }
  .page-toolbar .btn, .page-toolbar select { font-size: 13px; padding: 8px 12px; }

  .table-wrap { overflow-x: auto; }
  /* The wrapped mobile toolbar is taller than the desktop 52px sticky offset,
     which left a blank band above the header and hid the first row. Don't stick
     the table header below 1000px. */
  .list thead th { position: static; top: auto; }
  /* R366: same reasoning for the filter row (it isn't injected below 1000px,
     but a desktop->narrow resize would otherwise leave it stuck mid-air). */
  .colfilter-row td { position: static; top: auto; }

  /* Full-screen, comfortable editors (plan: DetailView, not centre modal) */
  .modal-backdrop { padding: 0; }
  .modal-card { max-width: 100%; width: 100%; min-height: 100vh; max-height: 100vh; border-radius: 0; }
  .modal-card form { padding: 16px; }
  .modal-actions { position: sticky; bottom: 0; background: var(--card); }

  /* 16px inputs stop iOS/iPadOS auto-zoom on focus; ≥44px touch targets */
  .form-grid input, .form-grid select, .form-grid textarea { font-size: 16px; padding: 10px 12px; }
  .btn { padding: 10px 16px; min-height: 44px; }

  /* Works Report editor (custom overlay, not .modal-card) — full-screen with
     a wrapping action bar so engineers can finish a report on-site. */
  #wr-editor-overlay { padding: 0 !important; align-items: flex-start !important; -webkit-overflow-scrolling: touch; }
  #wr-editor-overlay > div { width: 100% !important; max-width: 100% !important; min-height: 100vh; border-radius: 0 !important; overflow: visible !important; }
  #wr-editor-overlay form { padding: 14px !important; }
  #wr-editor-overlay .modal-actions { flex-wrap: wrap; }

  /* ------------------------------------------------------------------
     R412 — ONE EDITOR HEADER ON MOBILE (Piotr, 02/08).

     The reference is the invoice / work-order mobile record: a white bar,
     a back chevron on the left, the ref in bold navy, its status chip.
     Every other editor now matches it, so moving between them stops feeling
     like moving between three different apps.

     The old rule here indented each header by 60px to dodge the fixed
     hamburger. That was treating the symptom: an open editor OWNS the mobile
     screen, so the hamburger simply shouldn't be there. It's hidden below
     (body.modal-open / body.rec-page), and the indent is gone with it.
     ------------------------------------------------------------------ */
  body.modal-open .nav-toggle,
  body.rec-page   .nav-toggle,
  body.np-editing .nav-toggle { display: none; }   /* R412a: open note = full screen */

  .modal-card .modal-head,
  #wr-editor-overlay .modal-head {
    padding: 6px 12px !important;
    min-height: 52px;
    gap: 10px;
    background: var(--card);
    border-bottom: 1px solid var(--border);
  }
  .modal-card .modal-head h2,
  #wr-editor-overlay .modal-head h2 {
    color: var(--heading);
    font-size: 17px;
    line-height: 1.25;
    /* Long titles truncate rather than pushing the chevron off-screen. */
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* The × becomes the back chevron: same button, same handler, left-hand side.
     Piotr asked for the X to go on several editors — this answers that without
     leaving a full-screen editor with no way out. */
  .modal-card .modal-head .modal-close,
  #wr-editor-overlay .modal-head .modal-close {
    order: -1;
    width: 44px; min-width: 44px; height: 44px;
    display: flex; align-items: center; justify-content: center;
    font-size: 0;                      /* hides the × glyph itself */
    background: none; border: 0; padding: 0;
    color: var(--heading); opacity: 1;
  }
  .modal-card .modal-head .modal-close::before,
  #wr-editor-overlay .modal-head .modal-close::before {
    content: '\2039';                  /* ‹ */
    font-size: 30px; line-height: 1; color: var(--heading);
  }
  /* R412a: the head is a flex row, so order:-1 alone would still leave a
     priority dot or icon to the LEFT of the chevron on the Task card viewer.
     Push everything else after it. */
  .modal-card .modal-head > *:not(.modal-close) { order: 0; }
  /* Header buttons that aren't the close control keep the band tidy. */
  .modal-card .modal-head .btn,
  #wr-editor-overlay .modal-head .btn {
    min-height: 36px; padding: 6px 10px; font-size: 13px; white-space: nowrap;
    background: var(--surface-2); color: var(--heading); border: 1px solid var(--border);
  }

  /* ---- Record 360 band (Quotes, WOs, Invoices, Projects, WR, RAMS) ----
     Piotr: "header buttons become messy — make them same height and aligned
     to be fully visible on screen". They were different heights because long
     labels wrapped to two and three lines. nowrap fixes the height; the row
     wraps instead, so nothing runs off the edge. */
  .rec-actions { width: 100%; flex-wrap: wrap; justify-content: flex-start; gap: 6px; }
  .rec-actions .btn {
    min-height: 38px; height: 38px; padding: 0 12px; font-size: 13px;
    white-space: nowrap; display: inline-flex; align-items: center; flex: 0 0 auto;
  }

  /* R413: Settings — the backup download is a desktop job (a multi-MB file
     landing in a phone's Downloads folder helps nobody), so hide it here. */
  #backup-btn { display: none; }
}

/* ---- Phone density (legacy 768px line — see tier note above) ---- */
@media (max-width: 768px) {
  /* Files page: too many columns for a phone — hide Uploaded-by + Date so
     Name, Size and the action buttons fit without sideways scrolling. */
  .fm-list th:nth-child(4), .fm-list td:nth-child(4),
  .fm-list th:nth-child(5), .fm-list td:nth-child(5) { display: none; }
  .fm-list td:nth-child(2) { word-break: break-word; overflow-wrap: anywhere; }
  .fm-list td:last-child { white-space: normal !important; }
  .kpi-row { grid-template-columns: 1fr 1fr; gap: 8px; }

  /* Calendar is desktop-shaped; JS switches to the list view on phones, but if
     it is shown, keep the cells compact rather than unusable. */
  .cal-cell { min-height: 54px; padding: 3px 4px; }
  .cal-date { font-size: 11px; }
  .cal-chip { font-size: 10px; }

  /* Single-column forms on phones */
  .form-grid { grid-template-columns: 1fr; gap: 12px; }
  .day-add select[size] { font-size: 15px; }
}

/* Rich-text editor (richtext.js) — R325 polish pass */
.rt-wrap { position: relative; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; background: var(--card); transition: border-color 150ms ease, box-shadow 150ms ease; }
/* Focus lives on the frame (matches .field input:focus) — the old inset ring
   drew a box inside a box. */
.rt-wrap:focus-within { border-color: var(--focus-border, var(--navy-light)); box-shadow: 0 0 0 3px var(--focus-ring, rgba(42,77,122,0.15)); }
.rt-toolbar { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; padding: 5px 7px; background: var(--surface-2); border-bottom: 1px solid var(--border); }
/* Quieter buttons: flat until hover, pressed feedback on :active. */
.rt-btn { background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm); min-width: 26px; height: 26px; font-size: 12px; cursor: pointer; color: var(--heading); padding: 0 7px; line-height: 1; transition: background 120ms ease, border-color 120ms ease, transform 120ms cubic-bezier(0.23, 1, 0.32, 1); }
.rt-btn:hover { background: var(--row-hover); border-color: var(--border); }
.rt-btn:active { transform: scale(0.95); }
/* NOTES-UX P1 (09/07): visible active state for Bold/Italic/Underline. */
.rt-btn.rt-on { background: var(--heading); color: var(--card); border-color: var(--heading); }
/* NOTES-UX P1: comfortable toolbar targets on coarse pointers / phones. */
@media (hover: none), (max-width: 768px) {
  .rt-toolbar { gap: 6px; }
  .rt-btn { min-width: 40px; height: 40px; font-size: 14px; }
  .rt-sw { width: 32px; height: 32px; }
}
/* QUO-UX P2+P4 (09/07): modal footer buttons and the quote-builder ⋮⋮ drag
   handles get real touch targets on coarse pointers / phones. */
@media (hover: none), (max-width: 768px) {
  .modal-actions .btn { min-height: 44px; }
  .row-handle { font-size: 18px !important; padding: 10px 8px; }
}
.rt-sep { width: 1px; height: 18px; background: var(--border); margin: 0 4px; }
.rt-swatches { display: inline-flex; gap: 3px; align-items: center; }
/* R339 — icon toolbar (data-rt-full editors: Notes) + the content styles for
   headings, checklists and links that mode can produce. */
.rt-btn.rt-ico { min-width: 30px; height: 28px; display: inline-flex; align-items: center; justify-content: center; padding: 0 5px; }
.rt-btn.rt-ico svg { width: 16px; height: 16px; display: block; }
@media (hover: none), (max-width: 768px) { .rt-btn.rt-ico { min-width: 38px; height: 38px; } }
.rt-editor h1, .rt-editor h2, .rt-editor h3 { color: var(--heading); font-weight: 700; line-height: 1.25; margin: 14px 0 6px; }
.rt-editor h1 { font-size: 24px; }
.rt-editor h2 { font-size: 19px; }
.rt-editor h3 { font-size: 16px; }
.rt-editor a { color: var(--info); text-decoration: underline; }
.rt-editor ul.rt-checklist, .np-view ul.rt-checklist { list-style: none; padding-left: 4px; }
.rt-editor li[data-checked], .np-view li[data-checked] { list-style: none; position: relative; padding-left: 26px; margin: 3px 0; }
/* R340: higher-contrast boxes — the 1.5px --border outline was near-invisible
   on the dark theme, and the tick washed out against it. */
.rt-editor li[data-checked]::before, .np-view li[data-checked]::before {
  content: ''; position: absolute; left: 0; top: 1px; width: 17px; height: 17px;
  border: 2px solid var(--text-muted); border-radius: 4px; background: var(--card); cursor: pointer;
  box-sizing: border-box;
}
.rt-editor li[data-checked]:hover::before, .np-view li[data-checked]:hover::before { border-color: var(--heading); }
.rt-editor li[data-checked="1"]::before, .np-view li[data-checked="1"]::before {
  background: var(--success, #16a34a); border-color: var(--success, #16a34a);
}
.rt-editor li[data-checked="1"]::after, .np-view li[data-checked="1"]::after {
  content: ''; position: absolute; left: 5.5px; top: 3.5px; width: 5px; height: 10px;
  border: solid #fff; border-width: 0 2.5px 2.5px 0; transform: rotate(45deg);
}
.rt-editor li[data-checked="1"] { color: var(--text-muted); text-decoration: line-through; }
/* R332: full-spectrum text-colour picker in the rich toolbar. */
.rt-colorpick { width: 28px; height: 20px; padding: 0; border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer; background: none; }
@media (hover: none), (max-width: 768px) { .rt-colorpick { width: 36px; height: 32px; } }
.rt-sw { width: 18px; height: 18px; border: 1px solid rgba(0,0,0,0.2); border-radius: var(--radius-sm); cursor: pointer; padding: 0; transition: transform 120ms cubic-bezier(0.23, 1, 0.32, 1); }
.rt-sw:hover { transform: scale(1.12); }
.rt-sw:active { transform: scale(0.95); }
/* Roomier canvas + caret in the house ink. */
/* R429 (Piotr): every rich editor gets the same drag-to-resize grip the plain
   comment textareas have. The 360px cap is replaced by an explicit height set
   at mount time (richtext.js sizes it to the content, capped at 360), because
   a max-height would silently refuse to let the drag grow past it. */
.rt-editor { min-height: 90px; max-height: none; overflow: auto; resize: vertical; padding: 12px 14px; font-size: 13px; line-height: 1.55; outline: none; color: var(--text); caret-color: var(--heading); }
.rt-editor ul, .rt-editor ol { margin: 4px 0; padding-left: 22px; }
.rt-editor p { margin: 0 0 7px; }
/* R325c: inline images (data-URLs; width is % of the content width). */
.rt-editor img { max-width: 100%; height: auto; display: block; margin: 6px 0; border-radius: 3px; cursor: default; }
.rt-editor.rt-dropping { box-shadow: inset 0 0 0 2px var(--navy-light); }
/* Selected-image overlay + corner resize handles (lives in .rt-wrap). */
.rt-imgbox { position: absolute; display: none; pointer-events: none; border: 1.5px solid var(--navy-light); border-radius: 3px; z-index: 5; }
.rt-h { position: absolute; width: 11px; height: 11px; background: var(--card); border: 1.5px solid var(--navy-light); border-radius: 50%; pointer-events: auto; touch-action: none; }
.rt-h-nw { top: -6px; left: -6px; cursor: nwse-resize; }
.rt-h-ne { top: -6px; right: -6px; cursor: nesw-resize; }
.rt-h-sw { bottom: -6px; left: -6px; cursor: nesw-resize; }
.rt-h-se { bottom: -6px; right: -6px; cursor: nwse-resize; }
/* R486a: "Edit sketch" on a selected sketch image. The parent box is
   pointer-events:none so the image stays clickable, hence pointer-events:auto
   here. 44px min-height is the iPad touch target — this is a finger control.
   Sits just below the box so it never covers the drawing it edits. */
.rt-imgedit { display: none; position: absolute; top: 100%; left: 0; margin-top: 6px;
  align-items: center; min-height: 44px; padding: 0 14px; pointer-events: auto;
  background: var(--navy); color: #fff; border: 0; border-radius: var(--radius-md);
  font-size: 13px; font-weight: 600; cursor: pointer; box-shadow: var(--shadow);
  touch-action: manipulation; }
.rt-imgedit:hover { filter: brightness(1.08); }
/* R325b: floating selection toolbar + slash-command menu. Fixed-position
   body-level singletons so they escape the modal's overflow ancestors;
   z-index 1800 = above .modal-backdrop (1700), below .toast (2000).
   Enter: 120ms ease-out from an already-visible scale (never scale(0));
   repositioning never animates left/top — only opacity/transform. */
.rt-float, .rt-slash {
  position: fixed; z-index: 1800; visibility: hidden; opacity: 0; pointer-events: none;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: 0 6px 20px rgba(15, 23, 42, 0.18);
  transition: opacity 120ms cubic-bezier(0.23, 1, 0.32, 1), transform 120ms cubic-bezier(0.23, 1, 0.32, 1), visibility 0s linear 120ms;
}
.rt-float { display: flex; gap: 2px; padding: 4px; transform: translateY(4px) scale(0.97); transform-origin: 50% 100%; }
.rt-float.rt-below { transform: translateY(-4px) scale(0.97); transform-origin: 50% 0%; }
.rt-slash { display: flex; flex-direction: column; gap: 1px; padding: 4px; min-width: 216px; max-height: 264px; overflow: auto; transform: translateY(-3px) scale(0.98); transform-origin: 0 0; }
.rt-float.rt-open, .rt-slash.rt-open {
  visibility: visible; opacity: 1; pointer-events: auto; transform: none;
  transition: opacity 120ms cubic-bezier(0.23, 1, 0.32, 1), transform 120ms cubic-bezier(0.23, 1, 0.32, 1);
}
.rt-slash-it { display: flex; align-items: center; gap: 9px; width: 100%; text-align: left; background: transparent; border: 0; border-radius: var(--radius-sm); padding: 6px 8px; cursor: pointer; color: var(--text); font-size: 13px; line-height: 1.25; }
.rt-slash-it:hover, .rt-slash-it.rt-slash-on { background: var(--row-hover); }
.rt-slash-ic { flex: 0 0 22px; height: 22px; display: grid; place-items: center; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); font-size: 11px; font-weight: 700; color: var(--heading); }
.rt-slash-lb { display: block; font-weight: 600; color: var(--heading); }
.rt-slash-hint { display: block; font-size: 11px; color: var(--text-muted); }
@media (prefers-reduced-motion: reduce) {
  .rt-float, .rt-slash { transform: none; transition: opacity 100ms ease, visibility 0s linear 100ms; }
  .rt-float.rt-open, .rt-slash.rt-open { transition: opacity 100ms ease; }
  .rt-btn, .rt-sw { transition: none; }
  .rt-btn:active, .rt-sw:hover, .rt-sw:active { transform: none; }
}
/* R276: page-break marker (toolbar ⤓ Page break button inserts an <hr>) —
   dashed divider with a centred label so it reads as "new PDF page here". */
.rt-editor hr {
  border: none; border-top: 2px dashed var(--border);
  margin: 14px 0; position: relative; overflow: visible;
}
.rt-editor hr::after {
  content: 'Page break';
  position: absolute; top: -9px; left: 50%; transform: translateX(-50%);
  background: var(--card); padding: 0 8px;
  font-size: 11px; color: var(--text-muted); white-space: nowrap;
}
/* R277: "Default text colour" swatch. #4a596d is the PDF ink colour, so
   text set with it prints exactly like unstyled text; on screen we force it
   to the theme text colour (!important beats the inline colour) so it reads
   as plain text in both light and dark themes. rgb(74, 89, 109) covers the
   browser's normalised inline-style form.
   R278 (Piotr): selectors made GLOBAL (were .rt-editor-scoped) — Notes and
   Tasks display the saved rich HTML outside an editor, where the literal
   #4a596d was showing (too dark on the dark theme). No app UI uses #4a596d
   as an inline text colour, so the global override is safe. */
.rt-sw-def::after { content: 'A'; color: #fff; font-size: 11px; font-weight: 700; line-height: 1; display: block; }
font[color="#4a596d" i],
span[style*="#4a596d" i],
span[style*="rgb(74, 89, 109)"] { color: var(--text) !important; }

/* ---- In-app file preview (Files page + share portal) ---- */
/* Intentionally dark in both themes — a neutral backdrop for viewing files. */
.fviewer-overlay {
  position: fixed; inset: 0; z-index: 4000;
  background: #1c1c1f; display: flex; flex-direction: column;
}
.fviewer-bar {
  flex: 0 0 auto; display: flex; align-items: center; gap: 12px;
  padding: calc(10px + env(safe-area-inset-top, 0px)) 14px 10px;
  background: #11161d; color: #fff; box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
.fviewer-close {
  background: rgba(255,255,255,0.14); color: #fff; border: none;
  border-radius: var(--radius-md); padding: 9px 16px; font-size: 15px; font-weight: 600;
  cursor: pointer; line-height: 1; white-space: nowrap;
}
.fviewer-close:hover { background: rgba(255,255,255,0.26); }
.fviewer-name {
  flex: 1 1 auto; font-size: 14px; opacity: 0.9;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fviewer-dl {
  flex: 0 0 auto; color: #fff; text-decoration: none;
  background: rgba(255,255,255,0.14); border-radius: var(--radius-md);
  padding: 9px 14px; font-size: 14px; white-space: nowrap;
}
.fviewer-dl:hover { background: rgba(255,255,255,0.26); }
.fviewer-body {
  flex: 1 1 auto; overflow: auto; -webkit-overflow-scrolling: touch;
  display: block;
}
.fviewer-img { max-width: 100%; height: auto; margin: 0 auto; display: block; }
.fviewer-open {
  flex: 0 0 auto; color: #fff; background: rgba(255,255,255,0.14); border: none;
  border-radius: var(--radius-md); padding: 9px 14px; font-size: 14px; font-weight: 600;
  white-space: nowrap; cursor: pointer; line-height: 1;
}
.fviewer-open:hover { background: rgba(255,255,255,0.26); }
.fviewer-frame { width: 100%; height: 100%; border: none; background: #fff; }
@media (max-width: 720px) {
  .fviewer-dl { padding: 9px 12px; }
  .fviewer-name { font-size: 13px; }
}

/* ===========================================================================
   MOBILE COMPONENT LIBRARY (Build Plan Phase 2) — class prefix .m-
   Used by the sub-1000px screens; all colours via the theme variables so
   light + dark both work. Tap targets >=44px. Focus always visible.
   =========================================================================== */

.m-lock-scroll { overflow: hidden; }

/* Shared focus visibility for every library control */
.m-chip:focus-visible, .m-card:focus-visible, .m-tab:focus-visible,
.m-select-btn:focus-visible, .m-select-opt:focus-visible, .m-select-filter:focus-visible,
.m-num button:focus-visible, .m-actionbar-more:focus-visible, .m-menu-item:focus-visible,
.m-topbar-back:focus-visible, .m-detail-back:focus-visible, .m-summary-card:focus-visible {
  outline: 3px solid var(--accent); outline-offset: 1px;
}

/* ---- TopBar ---- */
.m-topbar { position: sticky; top: 0; z-index: 40; background: var(--card);
  border-bottom: 1px solid var(--border); box-shadow: var(--shadow-sm); }
.m-topbar-row { display: flex; align-items: center; gap: 10px; padding: 8px 12px; min-height: 52px; }
.m-topbar-row2 { display: flex; align-items: center; gap: 8px; padding: 0 12px 10px; flex-wrap: wrap; }
.m-topbar-back { display: flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; font-size: 26px; color: var(--heading);
  text-decoration: none; border-radius: var(--radius-md); }
.m-topbar-back:hover { background: var(--row-hover); text-decoration: none; }
.m-topbar-spacer { min-width: 44px; }   /* keeps the title clear of the fixed hamburger */
/* R401 (Piotr): the mobile list headers (Planner / Work Orders / Invoices)
   were 16px and normal weight while the Dashboard's .page-title is 20px/700 —
   the same page name looked like two different things depending on the route.
   Matched to .page-title. Safe to change wholesale: no topBar() call site
   passes `ref`, so this class only ever renders a page NAME. */
.m-topbar-title { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px;
  font-size: 20px; font-weight: 700; color: var(--heading); overflow: hidden; }
.m-topbar-title strong { white-space: nowrap; }
.m-topbar-title span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---- Status pill (text + colour, never colour-only) ---- */
.m-pill { background: var(--pill, var(--text-muted)); color: var(--pill-fg, #fff); border: 1px solid transparent; padding: 2px 10px;
  border-radius: var(--radius-pill); font-size: var(--fs-xs); font-weight: var(--fw-medium); white-space: nowrap; }

/* VC-248 P2 — the ONE Badge component. Six semantic variants, single
   size/weight/radius, tinted bg + dark text pairs that pass WCAG AA in both
   themes (token pairs flip with the theme). Applies to .m-pill too when a
   variant class is given, so mobile cards share the exact same look. */
/* ------------------------------------------------------------------ R388
   List Workbench — one page header + one toolbar for every list page.
   The bar sits sticky directly under the page header; ui.js measures the
   stack so the table header freezes below it (never a hardcoded offset). */
.wb-head { align-items: flex-start; }
/* R388b — z-index 7 (above .page-toolbar 6 and .list thead th 5). The bar's
   own z-index makes it a stacking context, so the Columns/overflow popovers
   inside it can never out-paint the frozen table header from within: the bar
   itself has to sit above it. They never overlap, so the raise is invisible
   until a menu is open. */
.wb-bar { position: sticky; z-index: 7; top: 0; display: flex; align-items: center; gap: 8px;
  flex-wrap: wrap; padding: 10px 24px; background: var(--surface-2);
  border-bottom: 1px solid var(--border); }
.wb-search { position: relative; flex: 1 1 260px; max-width: 420px; min-width: 0; }
.wb-search input { width: 100%; box-sizing: border-box; padding: 8px 10px 8px 32px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 14px;
  background: var(--field-bg); color: var(--text); min-height: 38px; }
.wb-search-ico { position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  color: var(--text-muted); display: inline-flex; pointer-events: none; }
.wb-count { display: inline-block; min-width: 18px; padding: 0 5px; border-radius: 999px;
  background: var(--navy); color: #fff; font-size: 11px; font-weight: 700; line-height: 18px; text-align: center; }
.wb-menu { position: relative; }
.wb-pop { position: absolute; right: 0; top: calc(100% + 4px); z-index: 40; min-width: 220px;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow); padding: 4px 0; }
.wb-pop .btn, .wb-pop button.wb-act { display: block; width: 100%; text-align: left; background: none;
  border: 0; padding: 8px 12px; font-size: 13px; color: var(--text); cursor: pointer; font-family: inherit; }
.wb-pop .btn:hover, .wb-pop button.wb-act:hover { background: var(--row-hover); }
.wb-chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 0 0 10px; }
.wb-chip { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text); border-radius: var(--radius-lg);
  padding: 3px 10px; font-size: 12px; cursor: pointer; font-family: inherit; }
.wb-chip:hover { background: var(--row-hover); }
.wb-chip-clear { border: none; background: none; color: var(--heading); font-size: 12px;
  font-weight: 600; cursor: pointer; text-decoration: underline; font-family: inherit; }
.wb-table th.wb-sel, .wb-table td.wb-sel { width: 34px; padding-left: 12px; padding-right: 0; }
.wb-table td.wb-sel input, .wb-table th.wb-sel input { width: 16px; height: 16px; cursor: pointer; }
.wb-compact tbody td { padding: 5px 12px; font-size: 13px; }
.wb-compact thead th { padding: 7px 12px; }
.wb-bulk { position: sticky; bottom: 0; z-index: 6; display: flex; align-items: center; gap: 10px;
  padding: 10px 16px; background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow); margin-top: 10px; }
.wb-drawer { position: fixed; top: 0; right: 0; bottom: 0; width: min(380px, 92vw); z-index: 1500;
  background: var(--card); border-left: 1px solid var(--border); box-shadow: -8px 0 28px rgba(15,34,55,.18);
  display: flex; flex-direction: column; }
/* R388a — a class rule with display:flex outranks the UA sheet's
   [hidden]{display:none}, so both panels have to opt out explicitly or they
   render permanently open. */
.wb-bulk[hidden], .wb-drawer[hidden] { display: none; }
/* DIR-UX P4 (R392) — Directory rows with no activity read quieter. Was an
   inline style on the <tr>; the workbench sets it as a row class instead. */
.wb-table tr.dir-quiet { opacity: 0.65; }
.wb-drawer-body { padding: 16px 20px; overflow-y: auto; flex: 1; }
.wb-drawer-foot { display: flex; align-items: center; gap: 10px; padding: 12px 20px;
  border-top: 1px solid var(--border); }
.wb-drawer .wb-field { margin-bottom: 12px; }
.wb-drawer .wb-field > span { display: block; font-size: 12px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-muted); margin-bottom: 4px; }
/* R394 (Piotr) — filter-drawer option rows as compact as they can be and stay
   comfortably clickable. Bookkeeping's Category list is 19 items: at the old
   5px-padded 14px rows it needed scrolling before you could see the choices.
   One rule here beats an inline style repeated across twelve modules.
 *
 * R394b — THE ACTUAL BUG, found by measuring rather than eyeballing. The
 * `.wb-drawer .wb-field input` rule below (meant for the text/date inputs)
 * also matched every checkbox: width:100% + min-height:40px. The inline
 * `style="width:16px;height:16px"` on each box was the only thing holding it
 * back, so REMOVING those inline styles is what blew the drawer up — giant
 * boxes, labels shoved off the right edge, horizontal scrollbar. Two earlier
 * attempts (column-count, then grid) chased that overflow as if it were a
 * layout problem. It wasn't.
 * Two consequences, both needed:
 *   1. the row is `display:block`, not flex — a flex row's text is an ANONYMOUS
 *      flex item, untargetable by CSS, so it won't shrink and long labels
 *      ("Business Loan Repayment") force the drawer wide
 *   2. checkboxes/radios opt out of the field-input sizing explicitly */
.wb-drawer .wb-check { display: block; font-size: 13px; padding: 1px 0;
  cursor: pointer; line-height: 1.5; }
.wb-drawer .wb-field input[type="checkbox"], .wb-drawer .wb-field input[type="radio"] {
  width: 15px; height: 15px; min-height: 0; padding: 0; margin: 0 8px 0 0;
  vertical-align: -2px; box-sizing: border-box; }
@media (hover: none), (max-width: 768px) {
  /* Touch keeps a ~32px row — still much tighter than before, still hittable. */
  .wb-drawer .wb-check { padding: 6px 0; font-size: 14px; }
}

/* ---------------------------------------------------------------- R396 ----
   RECORD 360 — the shared record shell (record.js). Twin of the .wb-* block
   above: that one is how we draw a LIST, this is how we draw one RECORD.

   Structure, top to bottom, all inside one sticky .rec-band:
     .rec-id     navy identity strip  — back link, ref, title, status, actions
     .rec-facts  the pinned fact rail — client / site / value / margin
     .rec-tabs   the tab strip
   …then .rec-body scrolls underneath. The band uses the SAME navy tokens as
   the R381 modal head, so a record reads as a record whether it opened as a
   page or (on mobile) as a sheet. */
.rec-band { position: sticky; top: 0; z-index: 8; background: var(--card);
  border-bottom: 1px solid var(--border); box-shadow: var(--shadow-sm); }

/* --- identity strip ----------------------------------------------------- */
.rec-id { display: flex; align-items: center; gap: 14px; padding: 10px 18px;
  background: var(--modal-head-bg); color: var(--modal-head-fg); }
.rec-back { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  color: inherit; opacity: .82; text-decoration: none; font-size: 13px;
  padding: 4px 8px; border-radius: var(--radius-sm); white-space: nowrap; }
.rec-back:hover { opacity: 1; background: rgba(255,255,255,0.14); text-decoration: none; }
.rec-idmain { flex: 1 1 auto; min-width: 0; }
.rec-idline { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rec-ref { margin: 0; font-size: 20px; font-weight: 700; letter-spacing: -.01em;
  color: inherit; font-variant-numeric: tabular-nums; }
.rec-title { font-size: 13px; opacity: .8; margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rec-sub { font-size: 12px; opacity: .72; margin-top: 2px; }
.rec-sub a { color: inherit; text-decoration: underline; }
.rec-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }
/* Buttons sit ON the navy, so the page's grey "secondary" would vanish. */
.rec-actions .btn.secondary { background: rgba(255,255,255,0.13); color: inherit;
  border-color: rgba(255,255,255,0.32); }
.rec-actions .btn.secondary:hover { background: rgba(255,255,255,0.22); }

/* --- fact rail ---------------------------------------------------------- */
/* Deliberately OUTSIDE the tabs: these answer "what is this, for whom, worth
   what" — the questions you have BEFORE you know which tab you want. */
.rec-facts { display: flex; flex-wrap: wrap; gap: 0; margin: 0;
  background: var(--surface-2); border-top: 1px solid var(--border); }
.rec-fact { flex: 1 1 160px; min-width: 0; padding: 7px 18px;
  border-right: 1px solid var(--border); }
.rec-fact:last-child { border-right: 0; }
.rec-fact dt { font-size: 10px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-muted); margin: 0 0 1px; }
.rec-fact dd { margin: 0; font-size: 14px; font-weight: 600; color: var(--heading);
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rec-fact-good dd { color: var(--success); }
.rec-fact-bad  dd { color: var(--danger); }
.rec-fact-warn dd { color: var(--warning); }

/* --- tab strip ---------------------------------------------------------- */
.rec-tabs { display: flex; gap: 2px; padding: 0 12px; overflow-x: auto;
  scrollbar-width: thin; background: var(--card); }
.rec-tab { flex: 0 0 auto; border: 0; background: transparent; cursor: pointer;
  padding: 9px 14px; font-size: 13px; font-weight: 600; color: var(--text-muted);
  font-family: inherit; white-space: nowrap;
  border-bottom: 2px solid transparent; }
.rec-tab:hover { color: var(--heading); }
.rec-tab[aria-selected="true"] { color: var(--heading); border-bottom-color: var(--accent); }
.rec-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rec-tab-count { display: inline-block; margin-left: 4px; padding: 0 6px;
  border-radius: var(--radius-lg); background: rgba(127,127,127,0.18);
  font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums; }

/* --- panes and section cards -------------------------------------------- */
.rec-body { padding: 16px 18px 40px; }
.rec-pane:focus { outline: none; }
.rec-pane[hidden] { display: none; }
.rec-card { background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-sm); margin-bottom: 14px; }
.rec-card-head { display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; border-bottom: 1px solid var(--border); }
.rec-card-head h3 { margin: 0; font-size: 14px; color: var(--heading); }
.rec-card-count { display: inline-block; margin-left: 4px; padding: 0 6px;
  border-radius: var(--radius-lg); background: rgba(127,127,127,0.18);
  font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--text-muted); }
.rec-card-actions { margin-left: auto; display: flex; gap: 8px; }
.rec-card-body { padding: 14px; }
/* Legacy blocks (SOV, claims, works reports, RAMS, attachments) bring their
   own <h3 style="margin-top:18px"> — recRaw() drops them in a card and this
   neutralises the inline margin so their heading reads as the card's. */
.rec-raw > h3:first-child { margin-top: 0 !important; }
.rec-empty { margin: 0; color: var(--text-muted); font-size: 13px; }
/* Read-only field summary — the alternative to a page of disabled inputs. */
.rec-dl { margin: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px 18px; }
.rec-dl-row.full { grid-column: 1 / -1; }
.rec-dl dt { font-size: 10px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-muted); margin-bottom: 2px; }
.rec-dl dd { margin: 0; font-size: 14px; color: var(--text); font-variant-numeric: tabular-nums; }
.rec-dl dd.pre { white-space: pre-wrap; line-height: 1.5; }
.rec-linklist { margin: 0; padding: 0; list-style: none; }
.rec-linklist li { display: flex; justify-content: space-between; gap: 12px;
  padding: 5px 0; border-bottom: 1px solid var(--border); font-size: 13px; }
.rec-linklist li:last-child { border-bottom: 0; }
.rec-linklist span { font-variant-numeric: tabular-nums; color: var(--text-muted); }
/* Two-up where there's room — a wide screen shouldn't render a 400px column
   of fields down the left with nothing beside it. */
.rec-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: 14px; align-items: start; }

@media (max-width: 900px) {
  .rec-id { flex-wrap: wrap; padding: 8px 12px; gap: 8px; }
  .rec-back { order: -1; }
  .rec-actions { width: 100%; justify-content: flex-end; }
  .rec-ref { font-size: 17px; }
  .rec-fact { flex-basis: 50%; padding: 6px 12px; }
  .rec-body { padding: 12px; }
}

/* ---------------------------------------------------------------- R400 ----
   TIMESHEET — MOBILE (mytime-mobile.js). Day-first: the engineer's question on
   site is "what have I logged today", not "how does the month look". Almost
   everything here is layout for the day header; the entry rows, sheet, number
   steppers and action bar all reuse the mobile-ui kit unchanged. */
.mt-m { padding: 0 0 84px; }               /* clears the sticky action bar */
/* R404 (Piotr): "theme is not matching the other pages ... see WORK ORDERS
   for reference." The bespoke navy band is gone — Timesheet now uses the same
   topBar() as Work Orders / Invoices / Planner, so the title is navy-on-card
   like everywhere else AND the kit's .m-topbar-spacer keeps it clear of the
   fixed hamburger. That also fixed the second complaint (engineer picker sat
   under the hamburger) without a bespoke indent: the picker is on row 2 now.
   Lesson: the shared component already solved both problems. */
.mt-m { padding: 0 0 84px; }               /* clears the sticky action bar */
.mt-m-eng-name { font-size: 13px; color: var(--text-muted); }
.mt-m-eng { flex: 1 1 auto; min-height: 44px; padding: 8px 10px; font: inherit; font-size: 15px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); color: var(--text); }
.mt-m-daynav { display: flex; align-items: center; gap: 8px; padding: 8px 12px;
  background: var(--card); border-bottom: 1px solid var(--border); }
.mt-m-arrow { flex: 0 0 auto; min-width: 44px; min-height: 44px; font-size: 20px;
  line-height: 1; padding: 0 !important; }
.mt-m-day { flex: 1 1 auto; min-width: 0; text-align: center; color: var(--heading); }
.mt-m-day strong { display: block; font-size: 16px; font-weight: 700; }
.mt-m-today { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); }
.mt-m-jump { background: none; border: 0; padding: 2px 4px; font: inherit; font-size: 11px;
  color: var(--accent); text-decoration: underline; cursor: pointer; }

.mt-m-list { padding: 10px 12px 0; display: grid; gap: 8px; }
.mt-m-empty { margin: 22px 12px; text-align: center; color: var(--text-muted); font-size: 14px; line-height: 1.6; }
.mt-m-empty span { font-size: 13px; }
.mt-m-monthtot { margin: 0 0 10px; font-size: 13px; color: var(--text-muted); }
.mt-m-dayrow { width: 100%; margin-bottom: 6px; }

/* Sheet fields — the WO picker greys out when "no WO" or an internal code is
   chosen, rather than sitting there looking live but being ignored. */
.m-select.is-disabled { opacity: .45; }
.m-check { display: flex; align-items: center; gap: 10px; padding: 8px 0; font-size: 14px; cursor: pointer; }
.m-check input { width: 20px; height: 20px; flex: 0 0 auto; margin: 0; }
.m-full { width: 100%; margin-top: 14px; }

/* ---------------------------------------------------------------- R398 ----
   FINANCE INBOX — an action queue, not a dashboard.
   Rows are wide and quiet; the only loud things are the group counts and the
   action buttons, because those are the two things you're here for. Cleared
   groups aren't styled at all — they're not rendered (see finance.js rule 1). */
.fin-group { margin-bottom: 18px; }
.fin-group-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 6px; }
.fin-group-head h2 { margin: 0; font-size: 15px; color: var(--heading); }
.fin-count { font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
  padding: 1px 8px; border-radius: var(--radius-lg); background: rgba(127,127,127,0.18); }
.fin-count.fin-bad  { background: var(--danger); color: #fff; }
.fin-count.fin-warn { background: var(--warn-bg); color: var(--warn-fg); }
.fin-total { margin-left: auto; font-size: 13px; font-weight: 600;
  color: var(--text-muted); font-variant-numeric: tabular-nums; }

.fin-rows { list-style: none; margin: 0; padding: 0;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); box-shadow: var(--shadow-sm); overflow: hidden; }
.fin-row { display: flex; align-items: center; gap: 12px; padding: 8px 12px;
  border-bottom: 1px solid var(--border); }
.fin-row:last-child { border-bottom: 0; }
.fin-row:hover { background: var(--row-hover); }
.fin-row.is-busy { opacity: .5; pointer-events: none; }
/* The row body is a button so the whole thing is clickable AND focusable —
   the same contract list rows got in R358, not a div with a click handler. */
.fin-row-main { flex: 1 1 auto; min-width: 0; display: flex; align-items: baseline;
  gap: 10px; background: none; border: 0; padding: 2px 0; margin: 0; cursor: pointer;
  font: inherit; color: inherit; text-align: left; }
.fin-row-main:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
.fin-ref { font-weight: 700; color: var(--heading); font-variant-numeric: tabular-nums;
  flex: 0 0 auto; }
.fin-title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: 14px; }
.fin-row .fin-sub { flex: 0 0 auto; font-size: 12px; color: var(--text-muted); }
.fin-amount { flex: 0 0 auto; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--heading); }
.fin-actions { flex: 0 0 auto; display: flex; gap: 6px; }
.fin-actions .btn { padding: 4px 12px; font-size: 12px; }

.fin-clear { padding: 28px 20px; text-align: center; color: var(--text-muted);
  border: 1px dashed var(--border); border-radius: var(--radius-md); }
.fin-clear strong { display: block; font-size: 16px; color: var(--heading); margin-bottom: 4px; }
.fin-clear span { font-size: 13px; }

@media (max-width: 780px) {
  .fin-row { flex-wrap: wrap; gap: 6px 10px; }
  .fin-row-main { flex: 1 1 100%; flex-wrap: wrap; }
  .fin-title { flex: 1 1 100%; white-space: normal; }
  .fin-actions { margin-left: auto; }
}

/* ---------------------------------------------------------------- R395 ----
   Planner control centre (concept 2 of the 01/08 UX review): the unassigned
   rail, legend and collapsed year strip.
   R395c: the capacity row (.pl-cap-*) and per-resource availability bars
   (.pl-avail-*) are GONE at Piotr's request — planner entries carry no
   duration, so a "4/4 booked" meter turned an ordinary full week red. Any
   future load meter needs a duration/half-day field to measure against. */
/* R479 (Piotr, 30/08) — the rail now sits UNDER the planner at every width,
   not just below 1200px. It used to float to the right of the grid on a wide
   screen, which cost the grid ~276px of the width it most needs and split the
   reading order in two. The markup already puts .pl-main (grid → legend →
   year overview) before the rail, so stacking unconditionally gives the same
   order the narrow layout always had: planner, legend, year overview, then
   Not scheduled. The old max-width:1200px block that used to do this is gone
   — it now describes the default, so keeping it would be dead weight. */
.pl-layout { display: flex; flex-direction: column; gap: 16px; align-items: stretch; }
.pl-main { flex: 1 1 auto; min-width: 0; }

/* The rail — full width beneath the planner (R479). */
.pl-rail { flex: 1 1 auto; width: 100%; box-sizing: border-box;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: 12px; box-shadow: var(--shadow-sm); }
.pl-rail-head { display: flex; align-items: center; gap: 8px; font-size: 14px;
  color: var(--heading); margin-bottom: 8px; }
.pl-rail-count { margin-left: auto; background: var(--warn-bg); color: var(--warn-fg);
  border-radius: 999px; padding: 0 8px; font-size: 12px; font-weight: 700; line-height: 20px; }
.pl-rail-sec { font-size: 12px; font-weight: 600; color: var(--warn-fg);
  border-top: 1px solid var(--border); padding-top: 8px; margin-top: 8px; }
.pl-rail-card { display: block; width: 100%; text-align: left; margin-top: 8px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 8px 10px; cursor: pointer; font-family: inherit; }
.pl-rail-card:hover { background: var(--row-hover); }
.pl-rail-ref { font-weight: 700; font-size: 13px; color: var(--heading); }
.pl-rail-desc { font-size: 12px; color: var(--text); margin-top: 2px; overflow-wrap: anywhere; }
.pl-rail-meta { display: flex; gap: 8px; font-size: 11px; color: var(--text-muted); margin-top: 4px; }
.pl-rail-meta span { margin-left: auto; white-space: nowrap; }
.pl-rail-empty { font-size: 12px; color: var(--text-muted); margin: 4px 0 0; }
.pl-rail-all { width: 100%; margin-top: 12px; font-size: 12px; }

/* Category legend — replaces per-cell tooltips as the way to read the colours. */
.pl-legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 10px 2px 0;
  font-size: 12px; color: var(--text-muted); }
.pl-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.pl-legend-item i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }

/* Year overview, now a disclosure rather than the page's loudest element. */
.pl-year { margin-top: 14px; }
.pl-year > summary { cursor: pointer; font-size: 13px; font-weight: 600;
  color: var(--heading); padding: 6px 2px; }

.wb-drawer .wb-field input, .wb-drawer .wb-field select { width: 100%; box-sizing: border-box;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: 14px; background: var(--field-bg); color: var(--text); min-height: 40px; }
@media (max-width: 768px) {
  .wb-bar { padding: 10px 12px; position: static; }
  .wb-search { flex: 1 1 100%; max-width: none; }
  .wb-bar .btn { min-height: 44px; }
  .wb-drawer { width: 100vw; }
}

.badge { display: inline-flex; align-items: center; gap: 6px; padding: 2px 10px; border-radius: var(--radius-pill);
  font-size: var(--fs-xs); font-weight: var(--fw-medium); line-height: 1.6; white-space: nowrap; }
/* R383 — shape coding: a leading dot in the badge's own text colour, so state
   is readable by shape+position as well as hue (colour-blind safe). m-pill
   (mobile) deliberately keeps its plain look — smaller real estate. */
.badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.badge.b-success, .m-pill.b-success { background: var(--ok-bg);     color: var(--ok-fg); }
.badge.b-danger,  .m-pill.b-danger  { background: var(--err-bg);    color: var(--err-fg); }
.badge.b-warning, .m-pill.b-warning { background: var(--warn-bg);   color: var(--warn-fg); }
.badge.b-info,    .m-pill.b-info    { background: var(--info-bg);   color: var(--info-fg); }
.badge.b-purple,  .m-pill.b-purple  { background: var(--purple-bg); color: var(--purple-fg); }
.badge.b-neutral, .m-pill.b-neutral { background: var(--mut-bg);    color: var(--mut-fg); }
/* Filled navy = "active / current" per the uniform-highlight house rule. */
.badge.b-accent,  .m-pill.b-accent  { background: var(--navy); color: #fff; }

/* ---- SummaryStrip ---- */
.m-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px; padding: 12px; }
@media (max-width: 699px) { .m-summary { grid-template-columns: 1fr; } }
/* R421 (Piotr's call, 02/08): the accent was a 4px solid navy stripe — heavy
   enough to read as decoration rather than structure, and the one genuinely
   ornamental finding in the file. Halved to 2px: still a deliberate accent,
   but it now reads as a hairline rule against the card instead of a slab.
   Kept solid navy rather than a tint — at 2px a tint is invisible against
   --card, which would have removed the cue rather than softened it. */
.m-summary-card { display: block; background: var(--card); border: 1px solid var(--border);
  border-left: 2px solid var(--navy); border-radius: var(--radius-md); padding: 10px 14px;
  text-decoration: none; color: inherit; min-height: 44px; }
.m-summary-card:hover { text-decoration: none; }
a.m-summary-card:hover { background: var(--row-hover); }
.m-summary-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); }
.m-summary-value { font-size: 22px; font-weight: 700; color: var(--heading); }
.m-summary-sub { font-size: 12px; color: var(--text-muted); }
.m-summary-warn   { border-left-color: var(--warning); }
.m-summary-danger { border-left-color: var(--danger); }
.m-summary-danger .m-summary-value { color: var(--danger); }
.m-summary-ok     { border-left-color: var(--success); }

/* ---- FilterChips (the one deliberate horizontal scroller) ---- */
/* INV-UX P1 (09/07): contained scroll — max-width stops the strip poking past
   the viewport; the ::after spacer gives the last chip breathing room; the
   right-edge mask fade signals "more chips this way" (it lands on the spacer,
   not the chip, once fully scrolled). */
.m-chips { display: flex; gap: 8px; padding: 8px 12px; overflow-x: auto;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
  max-width: 100%; box-sizing: border-box;
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 20px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 20px), transparent); }
.m-chips::after { content: ''; flex: 0 0 20px; }
.m-chips::-webkit-scrollbar { display: none; }
.m-chip { flex: 0 0 auto; min-height: 44px; padding: 8px 16px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: var(--card); color: var(--text);
  font-size: 14px; cursor: pointer; white-space: nowrap; }
.m-chip.active { background: var(--navy); border-color: var(--navy); color: #fff; font-weight: 600; }
.m-chip-count { display: inline-block; margin-left: 4px; padding: 1px 8px; border-radius: var(--radius-lg);
  background: rgba(127,127,127,0.18); font-size: 12px; }
.m-chip.active .m-chip-count { background: rgba(255,255,255,0.25); }

/* ---- ListCard ---- */
.m-cardlist { display: flex; flex-direction: column; gap: 8px; padding: 0 12px 12px; }
.m-card { display: flex; align-items: stretch; gap: 0; width: 100%; text-align: left;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 0; cursor: pointer; color: var(--text); font: inherit; min-height: 60px;
  overflow: hidden; }
.m-card:hover { background: var(--row-hover); }
.m-card-cat { flex: 0 0 6px; }
.m-card-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; padding: 10px 14px; }
.m-card-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
.m-card-primary { flex: 1; min-width: 0; font-size: 15px; font-weight: 600; color: var(--heading);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.m-card-value { font-variant-numeric: tabular-nums; font-weight: 700; font-size: 15px;
  color: var(--heading); white-space: nowrap; margin-left: auto; }
.m-card-value.m-danger { color: var(--danger); }
.m-card-secondary { font-size: 13px; color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---- DetailView ---- */
.m-detail { position: fixed; inset: 0; z-index: 1500; background: var(--bg);
  display: flex; flex-direction: column; }
.m-detail-top { flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
  background: var(--card); border-bottom: 1px solid var(--border);
  padding: calc(6px + env(safe-area-inset-top, 0px)) 10px 6px; min-height: 52px; }
.m-detail-back { min-width: 44px; min-height: 44px; border: 0; background: transparent;
  font-size: 26px; color: var(--heading); cursor: pointer; border-radius: var(--radius-md); }
.m-detail-back:hover { background: var(--row-hover); }
.m-detail-title { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
  font-size: 16px; font-weight: 600; color: var(--heading); overflow: hidden; }
.m-detail-body { flex: 1; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch;
  padding-bottom: 90px; /* clearance for the sticky action bar */ }

.m-section { margin: 12px; background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-lg); overflow: hidden; }
.m-section-label { margin: 0; padding: 10px 14px 8px; font-size: 12px; font-weight: 700;
  color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.6px;
  border-bottom: 1px solid var(--border); background: var(--surface-2); }
.m-section-body { padding: 12px 14px; }

/* Read-first key/value rows used in Details sections */
.m-kv { display: flex; gap: 12px; padding: 7px 0; border-bottom: 1px solid var(--border); font-size: 14px; }
.m-kv:last-child { border-bottom: 0; }
.m-kv-k { flex: 0 0 38%; color: var(--text-muted); }
.m-kv-v { flex: 1; min-width: 0; color: var(--text); overflow-wrap: anywhere; }

/* ---- SearchableSelect ---- */
.m-select { position: relative; display: grid; gap: 4px; }
.m-select-btn { display: flex; align-items: center; gap: 8px; width: 100%; min-height: 44px;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); color: var(--text); font-size: 16px; cursor: pointer; text-align: left; }
.m-select-btn[disabled] { opacity: 0.6; cursor: default; }
.m-select-current { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.m-select-empty { color: var(--text-muted); }
.m-select-arrow { color: var(--text-muted); }
.m-select-pop { position: absolute; top: 100%; left: 0; right: 0; z-index: 60;
  margin-top: 4px; background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: 0 8px 30px rgba(0,0,0,0.25); overflow: hidden; }
.m-select-filter { width: 100%; box-sizing: border-box; padding: 10px 12px; border: 0;
  border-bottom: 1px solid var(--border); background: var(--card); color: var(--text);
  font-size: 16px; outline: none; min-height: 44px; }
.m-select-list { max-height: 240px; overflow-y: auto; }
.m-select-opt { display: block; width: 100%; text-align: left; padding: 11px 14px;
  border: 0; background: transparent; color: var(--text); font-size: 15px;
  cursor: pointer; min-height: 44px; }
.m-select-opt:hover, .m-select-opt.hi { background: var(--row-hover); }
.m-select-opt[aria-selected="true"] { font-weight: 700; color: var(--heading); }
.m-select-none { padding: 12px 14px; color: var(--text-muted); font-size: 13px; }

/* ---- BottomSheet ---- */
.m-sheet-backdrop { position: fixed; inset: 0; z-index: 1600; background: rgba(0,0,0,0);
  transition: background 0.18s ease; display: flex; align-items: flex-end; }
.m-sheet-backdrop.open { background: rgba(0,0,0,0.45); }
.m-sheet { width: 100%; max-height: 92vh; background: var(--card);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0; display: flex; flex-direction: column;
  transform: translateY(100%); transition: transform 0.18s ease; touch-action: none; }
.m-sheet-backdrop.open .m-sheet { transform: translateY(0); }
.m-sheet-grab { display: flex; justify-content: center; padding: 8px 0 2px; cursor: grab; }
.m-sheet-grab span { width: 42px; height: 4px; border-radius: var(--radius-sm); background: var(--border); }
.m-sheet-head { padding: 6px 16px 10px; font-size: 16px; font-weight: 700; color: var(--heading);
  border-bottom: 1px solid var(--border); }
.m-sheet-body { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 14px 16px; touch-action: pan-y; }
.m-sheet-foot { flex: 0 0 auto; display: flex; gap: 10px; justify-content: flex-end;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border); background: var(--card); }
.m-sheet-foot .btn { min-height: 44px; }

/* ---- StickyActionBar ---- */
.m-actionbar { position: sticky; bottom: 0; z-index: 45; display: flex; align-items: center;
  gap: 10px; padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--card); border-top: 1px solid var(--border); }
.m-detail .m-actionbar { position: absolute; bottom: 0; left: 0; right: 0; }
.m-actionbar-spacer { flex: 1; }
.m-actionbar-primary { min-height: 44px; }
.m-actionbar-more { min-width: 44px; min-height: 44px; border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--card); color: var(--heading); font-size: 20px;
  cursor: pointer; line-height: 1; }
.m-actionbar-menu { position: absolute; bottom: calc(100% + 6px); left: 10px; z-index: 70;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: 0 8px 30px rgba(0,0,0,0.3); min-width: 200px; overflow: hidden; padding: 4px; }
.m-menu-item { display: block; width: 100%; text-align: left; padding: 12px 14px;
  border: 0; background: transparent; color: var(--text); font-size: 15px;
  border-radius: var(--radius-md); cursor: pointer; min-height: 44px; }
.m-menu-item:hover { background: var(--row-hover); }
.m-menu-item.m-danger { color: var(--danger); }

/* ---- TabStrip ---- */
.m-tabs { display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none;
  background: var(--card); border-bottom: 1px solid var(--border); padding: 0 8px; }
.m-tabs::-webkit-scrollbar { display: none; }
.m-tab { flex: 0 0 auto; min-height: 44px; padding: 10px 14px; border: 0;
  border-bottom: 3px solid transparent; background: transparent; color: var(--text-muted);
  font-size: 14px; font-weight: 600; cursor: pointer; white-space: nowrap; }
.m-tab.active { color: var(--heading); border-bottom-color: var(--accent); }
.m-tab-count { display: inline-block; margin-left: 3px; padding: 0 7px; border-radius: var(--radius-md);
  background: rgba(127,127,127,0.18); font-size: 11px; }

/* ---- Form fields ---- */
.m-field { display: grid; gap: 4px; margin-bottom: 12px; }
.m-label { font-size: 13px; font-weight: 600; color: var(--text); }
.m-req { color: var(--danger); }
.m-hint { font-size: 12px; color: var(--text-muted); }
.m-field input:not([type=hidden]), .m-field select, .m-field textarea {
  font-size: 16px; padding: 10px 12px; border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--card); color: var(--text); min-height: 44px; box-sizing: border-box; }

.m-num { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.m-num input { width: 90px; text-align: center; font-size: 16px; min-height: 44px;
  padding: 8px; border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); color: var(--text); }
.m-num-dec, .m-num-inc { min-width: 44px; min-height: 44px; border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--surface-2); color: var(--heading);
  font-size: 20px; font-weight: 700; cursor: pointer; line-height: 1; }
.m-num-suffix { color: var(--text-muted); font-size: 14px; }
.m-num-quick { display: inline-flex; gap: 6px; margin-left: 4px; }
.m-num-q { min-width: 44px; min-height: 44px; border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--card); color: var(--text); font-size: 15px; cursor: pointer; }
.m-num-q:hover { background: var(--row-hover); }

/* Floating add button (used by Phase 3 Planner) */
.m-fab { position: fixed; right: 16px; bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  z-index: 50; width: 56px; height: 56px; border-radius: 50%; border: 0;
  background: var(--navy); color: #fff; font-size: 28px; line-height: 1; cursor: pointer;
  box-shadow: 0 4px 14px rgba(0,0,0,0.35); }
.m-fab:hover { background: var(--navy-light); }
.m-fab:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* Sticky day sub-headers (Phase 3 agenda) */
.m-dayhead { position: sticky; top: 0; z-index: 30; display: flex; align-items: baseline; gap: 8px;
  padding: 8px 14px; background: var(--bg); border-bottom: 1px solid var(--border);
  font-size: 13px; font-weight: 700; color: var(--heading); }
.m-dayhead.today { color: var(--accent); }
.m-dayhead-count { margin-left: auto; font-weight: 400; color: var(--text-muted); font-size: 12px; }

/* ---- Phase 6: per-column filters + results count (desktop tables) ---- */
.table-results-count { font-size: 12px; color: var(--text-muted); padding: 6px 2px 4px; }
/* R366 (Piotr): the filter row scrolled away while the header labels stayed
   pinned. Sticky here; the per-table `top` offset is measured and set inline
   by initColumnFilters (header-row heights differ between tables), because a
   hardcoded value drifts the moment a header wraps. z-index 4 keeps it under
   the header (5) and toolbar (6) but over the body rows. */
.colfilter-row td { padding: 3px 6px 6px; background: var(--surface-2); border-bottom: 1px solid var(--border); position: sticky; z-index: 4; }
.colfilter { width: 100%; box-sizing: border-box; padding: 4px 8px; font-size: 12px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--card); color: var(--text); }
.colfilter::placeholder { color: var(--text-muted); }

/* ---- Phase 6: accessibility - visible keyboard focus everywhere ---- */
/* :focus-visible only (mouse clicks stay clean); state-level change, no
   layout impact at any width. The .m-* components carry their own rule.
   R327: text-entry controls dropped from this list — Chrome treats text
   fields as :focus-visible on EVERY click, so the 3px amber outline fired
   on routine typing. They now get the quieter navy ring below; amber stays
   for buttons, links and tick-boxes, where it only shows on real keyboard
   navigation. [contenteditable] is covered by .rt-wrap:focus-within. */
button:focus-visible, a:focus-visible, [tabindex]:focus-visible,
input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible {
  outline: 3px solid var(--accent); outline-offset: 1px;
}

/* ============ R327 — interaction polish (Impeccable pass) ============ */
/* Focus tokens: the login-card ring, themed. The light values match the
   existing rgba(42,77,122,0.15) convention; dark gets a brighter blue so
   the ring is actually visible on #1a212e cards. */
:root {
  --focus-border: var(--navy-light);
  --focus-ring: rgba(42, 77, 122, 0.16);
}
html[data-theme="dark"], html[data-base="dark"] {
  --focus-border: #5b8cc9;
  --focus-ring: rgba(147, 197, 253, 0.22);
}
/* One focus language for text entry app-wide (form grids, nested rows,
   column filters, toolbar searches — everything). */
input:not([type="checkbox"]):not([type="radio"]):focus,
select:focus, textarea:focus {
  outline: none;
  border-color: var(--focus-border);
  box-shadow: 0 0 0 3px var(--focus-ring);
}

/* Shared button mechanics: hover states already exist everywhere — they now
   glide (120ms) instead of snapping, and primary actions confirm the press
   with a subtle scale. Elements with their own transition (kpi-card,
   planner-cell, rt-btn) keep it — none of them appear in this list. */
.btn, .btn-primary, .btn-ghost, .theme-opt, .modal-close, .nav-item,
.nav-group-head, .nav-toggle, .cat-chip, .app-banner-dismiss {
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease,
              transform 120ms cubic-bezier(0.23, 1, 0.32, 1);
}
.btn:active, .btn-primary:active, .btn-ghost:active, .nav-toggle:active { transform: scale(0.97); }
.btn:disabled, .btn-primary:disabled { opacity: 0.55; pointer-events: none; }

/* Table rows glide into their hover tint. */
.data-table tbody tr, .list tbody tr { transition: background 100ms ease; }

/* Current page marker — the same amber accent language the nav group heads
   already speak, so "where am I" reads at a glance. */
.nav-item.active { box-shadow: inset 3px 0 0 var(--accent); }

/* Entrances: modals rise 8px from 98% scale (never from nothing), toasts
   rise from the bottom edge they anchor to. One-shot keyframes on DOM
   insertion; the R283 reduced-motion rule zeroes both automatically. */
@keyframes r327-fade-in { from { opacity: 0; } }
@keyframes r327-rise-in { from { opacity: 0; transform: translateY(8px) scale(0.98); } }
.modal-backdrop { animation: r327-fade-in 150ms ease-out; }
.modal-card { animation: r327-rise-in 180ms cubic-bezier(0.23, 1, 0.32, 1); }
.toast { animation: r327-rise-in 180ms cubic-bezier(0.23, 1, 0.32, 1); }


/* ===========================================================================
   R412a / R413 — MOBILE OVERRIDES THAT MUST OUTRANK THE COMPONENT RULES
   ---------------------------------------------------------------------------
   These live at the END of the file on purpose. A media query adds NOTHING to
   specificity, so the copy of these rules that originally sat in the
   max-width:999px block near the top was being beaten by the plain .rec-id /
   .rec-actions / .fin-rows definitions further down — the chevron appeared
   (new rules, nothing competing) while the band stayed navy (same specificity,
   later source). Source order is the fix; no !important needed.
   =========================================================================== */
@media (max-width: 999px) {
  /* ------------------------------------------------------------------
     R412a — THE RECORD BAND GOES LIGHT ON MOBILE (Piotr, 02/08).

     R412 restyled the modal headers but left .rec-id navy, so a quotation
     still didn't match the work order beside it. These rules make the band
     the same object as .m-topbar: card background, navy heading text, and
     the identical 44px '‹' back control.

     Desktop keeps the navy band — it reads as a header there, against a wide
     page. On a phone the band IS the screen, and navy-on-navy chrome was
     what made the buttons hard to pick out in the first place.
     ------------------------------------------------------------------ */
  .rec-id {
    background: var(--card);
    color: var(--heading);
    gap: 10px;
    padding: 8px 12px;
    min-height: 52px;
    flex-wrap: wrap;
  }
  /* ------------------------------------------------------------------
     R414 (Piotr, 02/08): the band reads TOP-DOWN on a phone —
         row 1   ‹  Q.106.ML  [Accepted] [read-only]
         row 2   the description, in full, wrapping
         row 3   the sub-line (linked work orders / project)
         row 4   the action buttons
     `display:contents` on .rec-idmain dissolves the wrapper so its three
     children become direct flex items of .rec-id and can each claim their own
     row. Without it the whole block is ONE item and the chevron has to centre
     itself against a three-line stack, which is what put it beside the
     description instead of beside the ref.
     ------------------------------------------------------------------ */
  .rec-id { align-items: flex-start; }
  .rec-idmain { display: contents; }
  .rec-back { order: -1; margin-top: 0; }
  .rec-idline { flex: 1 1 0; min-width: 0; align-self: center; }

  /* R414a (Piotr, 02/08): the HEADER is chevron + ref + badges and nothing
     else — the mobile invoice sets that standard. Everything that used to
     crowd in beside it now reads as its own field below, separated the same
     way the fact rail is. Mobile invoices reach that look by routing to a
     different component (InvoicesMobile/topBar); the shared band gets there
     with dividers instead, so every record type benefits rather than only the
     two that have a bespoke mobile module. */
  .rec-idline { padding-bottom: 8px; }
  .rec-title, .rec-sub, .rec-actions {
    flex: 1 0 100%;
    border-top: 1px solid var(--border);
    padding: 10px 0 0;
    margin: 0 0 8px;
  }
  /* The description is the one thing you actually read here, so let it wrap
     instead of truncating it to "…on the Evap Cooling tow…". */
  .rec-title { white-space: normal; overflow: visible; text-overflow: clip;
               color: var(--text); font-size: 13px; }
  .rec-actions { margin-bottom: 0; padding-bottom: 2px; }

  /* R413b (Piotr, 02/08): the ref + chips must sit ON the chevron's line.
     .rec-idmain was flex:1 1 auto, so its base size was its CONTENT width
     (351px) — wider than the 321px left beside the 44px chevron. A flex line
     wraps before it shrinks, so the whole block dropped to row 2 and left the
     chevron stranded on its own. flex-basis:0 makes the base size 0: it can
     never force a wrap, and flex-grow fills the space that's actually there. */
  .rec-idmain { flex: 1 1 0; min-width: 0; }
  .rec-actions { flex: 1 0 100%; }        /* actions keep their own row */
  .rec-ref   { color: var(--heading); font-size: 19px; }
  .rec-title { color: var(--text-muted); opacity: 1; }
  .rec-sub   { color: var(--text-muted); opacity: 1; }
  .rec-sub a { color: var(--heading); }

  /* Same control as .m-topbar-back: a 44px square carrying '‹'. The module's
     own arrow SVG and its label are hidden rather than removed, so the link,
     its href and its aria-label all still work. */
  .rec-back {
    justify-content: center;
    min-width: 44px; min-height: 44px;
    padding: 0; font-size: 0;
    color: var(--heading); opacity: 1;
    border-radius: var(--radius-md);
  }
  .rec-back svg, .rec-back span { display: none; }
  .rec-back::before {
    content: '\2039';
    font-size: 26px; line-height: 1; color: var(--heading);
  }
  .rec-back:hover { background: var(--row-hover); }

  /* The white-on-navy button treatment above would be invisible now. */
  .rec-actions .btn.secondary {
    background: var(--surface-2); color: var(--heading);
    border: 1px solid var(--border);
  }
  .rec-actions .btn.secondary:hover { background: var(--row-hover); }

  /* R415a — FINANCE INBOX, COMPACT (Piotr, 02/08: "make it compact to view
     more items on mobile"). R413's first pass only trimmed padding, which
     barely helped: the height came from the ROW SHAPE, not the spacing. The
     desktop row is one flex line (ref · title · sub · amount · actions); at
     390px every child wrapped onto its own line and the app-wide
     `.btn{min-height:44px}` touch rule made each Open button a 44px block —
     ~125px per item, about six visible.

     Two tidy lines instead:
        PO.015.PS                              £8,089.43
        Itchen Electrical · raised 34d ago       [Open]
     ~56px per item, so roughly twice as many fit. The Open button drops to
     34px — under the 44px touch guideline, but it sits in a 56px row with
     generous horizontal space, and "more items per screen" was the ask. */
  /* R415b — measured the first attempt at 87px, not the ~56 I'd predicted:
     `.fin-row-main` at 100% width pushed the amount and Open onto a THIRD
     line. A grid puts them beside the text block instead, so the row is only
     as tall as its two text lines. */
  .fin-row { display: grid; grid-template-columns: 1fr auto auto;
             align-items: center; gap: 0 10px; padding: 6px 10px; }
  .fin-row-main { display: flex; flex-wrap: wrap; gap: 0 6px;
                  align-items: baseline; min-width: 0; text-align: left; }
  .fin-ref   { flex: 1 1 100%; font-size: 13px; }
  .fin-title { flex: 0 1 auto; font-size: 12px; max-width: 100%;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .fin-row .fin-sub { flex: 0 1 auto; font-size: 11px; }
  .fin-amount  { font-size: 13px; white-space: nowrap; }
  .fin-actions { flex-wrap: nowrap; }
  .fin-actions .btn { min-height: 34px; padding: 4px 14px; font-size: 12px; }
  .fin-group-head { padding: 6px 10px; margin-bottom: 2px; }
  .fin-group-head h2 { font-size: 14px; }
  .fin-group { margin-bottom: 12px; }
}

/* ===========================================================================
   R443 — PLANNER & TIMESHEET ITEM HIGHLIGHT (Piotr, 03/08)
   ---------------------------------------------------------------------------
   "Replace existing Planner item hover effect - add a subtle gradual highlight
   animation to Planner and Timesheet items."

   WHY AN INSET SHADOW AND NOT AN OVERLAY
   The obvious build is a ::after film over the item. It doesn't work here:
   absolutely-positioned pseudo-elements paint in the POSITIONED layer, which
   is above in-flow inline content — so the film would wash the label text as
   well as the background, and on the pale chips that costs real contrast.
   An INSET box-shadow paints above the background and below the content, so
   the highlight blooms behind text that stays exactly as crisp as it was.

   It also sidesteps the specificity problem: a planner entry carries its
   category colour as an INLINE `background:` (planner.js), which no stylesheet
   background-image can override. box-shadow is a different property, so the
   two never argue.

   DARKEN, DON'T LIGHTEN — measured, not guessed
   The first cut washed with white. Sampling the real chips said that was the
   wrong direction: +2.5% to +8.2% luminance, and it desaturates, so the item
   read as FADED rather than picked out — the opposite signal. A translucent
   ink wash at 0.06 lands at about -12% luminance on every category colour in
   the palette: unmistakable under the pointer, quiet enough to sit in a grid
   of twenty. (0.10 measured -19% and 0.14 -26% — both too loud for "subtle".)

   The bloom: spread animates 0 → 240px while the colour fades in, so the wash
   grows inward from the edges rather than flicking on. 260ms on the standard
   decelerate curve — long enough to read as deliberate, short enough not to
   lag the pointer across a dense grid. The global prefers-reduced-motion block
   near the top of this file already collapses this to ~0ms.

   R444 (Piotr, 03/08) — "apply this effect to Timesheet, Tasks, Notes, Files".
   The values now live in TOKENS so there is one place to tune the whole app,
   and the selector list below is the full inventory of "an item you point at".
   240px of spread guarantees the two edges meet in the middle of even the
   widest item; anything smaller leaves an unwashed band down the centre.
   =========================================================================== */
:root {
  --item-hi-ink:  rgba(15, 23, 42, 0.06);
  --item-hi-lift: 0 2px 7px rgba(15, 23, 42, 0.14);
  --item-hi-rest: 0 1px 2px rgba(15, 23, 42, 0.05);   /* task cards at rest */
}
/* Dark themes invert the logic: the items are already near-black, so more ink
   does nothing — lift them instead. Relative luminance is a misleading metric
   this far down the scale, so this is the perceptual equivalent, not the
   arithmetic one. Both selectors — theme AND base — per the inheritance rule. */
html[data-theme="dark"], html[data-base="dark"] {
  --item-hi-ink:  rgba(255, 255, 255, 0.07);
  --item-hi-lift: 0 2px 7px rgba(0, 0, 0, 0.34);
  --item-hi-rest: 0 1px 2px rgba(0, 0, 0, 0.30);
}

/* --- block-level items: wash + 1px lift ---------------------------------- */
.planner-entry,
.cal-chip,
.day-entry,
.np-item {
  box-shadow: inset 0 0 0 0 rgba(15, 23, 42, 0);
  transition: box-shadow 260ms cubic-bezier(0.22, 1, 0.36, 1),
              transform  260ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Task cards carry a resting shadow, so theirs starts from that rather than
   from nothing. R444 moved it OUT of the inline style attribute in
   tasks-board.js — an inline box-shadow outranks any stylesheet rule, so the
   hover simply could not apply while it lived there. */
.tb-card,
.tl-mcard,
.md-ag-card,
.md-rail-card {
  box-shadow: var(--item-hi-rest);
  transition: box-shadow 260ms cubic-bezier(0.22, 1, 0.36, 1),
              transform  260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.planner-entry:hover,
.cal-chip:hover,
.day-entry:hover,
.np-item:hover,
.tb-card:hover,
.tl-mcard:hover,
.md-ag-card:hover,
.md-rail-card:hover {
  box-shadow: inset 0 0 0 240px var(--item-hi-ink), var(--item-hi-lift);
  transform: translateY(-1px);
}

/* The selected note keeps its 3px accent bar THROUGH the hover: listed first,
   so it paints on top of the wash instead of being replaced by it. */
.np-item.active:hover {
  box-shadow: inset 3px 0 0 var(--accent),
              inset 0 0 0 240px var(--item-hi-ink),
              var(--item-hi-lift);
}

/* --- table rows: wash only ----------------------------------------------- */
/* `table.list` is border-collapse:collapse, where a box-shadow on a <tr> is
   not painted at all — and a <tr> cannot be lifted without tearing the
   collapsed borders off its neighbours. So the wash goes on the CELLS, which
   composites identically over whatever background the row already has. */
/* These rows also inherit `.list tbody tr:hover { background: var(--row-hover) }`
   with a 100ms transition. Left as-is the background would arrive well before
   the wash and the two would read as two separate events, so the row is
   re-timed to travel with its cells. */
.fm-file-row,
.fm-folder-row,
.tl-row {
  transition: background-color 260ms cubic-bezier(0.22, 1, 0.36, 1);
}
.fm-file-row > td,
.fm-folder-row > td,
.tl-row > td {
  transition: box-shadow 260ms cubic-bezier(0.22, 1, 0.36, 1);
}
.fm-file-row:hover > td,
.fm-folder-row:hover > td,
.tl-row:hover > td {
  box-shadow: inset 0 0 0 240px var(--item-hi-ink);
}

/* While an item is being dragged (R442/R444 set opacity 0.4 inline) it is a
   payload, not a target — the lift and wash just add noise under the cursor. */
.planner-entry[style*="opacity: 0.4"]:hover,
.cal-chip[style*="opacity: 0.4"]:hover {
  transform: none;
  box-shadow: inset 0 0 0 0 rgba(255, 255, 255, 0);
}

/* --- R446 — right-click context menus (Piotr, 04/08) --------------------- */
/* Every menu row used to flip to --surface-2 the instant the pointer crossed
   it, set as an INLINE background from a JS mouseenter listener (or, in Notes,
   from a rule in a <style> the module injects after app.css). Both of those
   outrank a stylesheet `background`, which is the second reason the wash rides
   on box-shadow — the first being that these buttons already carry an inline
   `background:none`.
   NO LIFT here, deliberately: a row that jumps a pixel while you are tracking
   down a menu fights the pointer. Menus get the wash and nothing else. */
.pl-ctx-item,          /* Planner            */
.ts-ctx-item,          /* Timesheet          */
.exp-ctx-item,         /* Expenses           */
.fm-ctx-item,          /* Files              */
.pm-ctx-item,          /* Planner (mobile long-press) */
.np-menu button {      /* Notes: note / notebook / tag menus */
  box-shadow: inset 0 0 0 0 rgba(15, 23, 42, 0);
  transition: box-shadow 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
.pl-ctx-item:hover,  .pl-ctx-item:focus-visible,
.ts-ctx-item:hover,  .ts-ctx-item:focus-visible,
.exp-ctx-item:hover, .exp-ctx-item:focus-visible,
.fm-ctx-item:hover,  .fm-ctx-item:focus-visible,
.pm-ctx-item:hover,  .pm-ctx-item:focus-visible,
.np-menu button:hover, .np-menu button:focus-visible {
  box-shadow: inset 0 0 0 240px var(--item-hi-ink);
}
