/* ---------- Design tokens ----------
   Calm, warm and clear. Staff open this app at the start of a shift and again
   at the end of a long one, on a shop tablet under bright lights or a phone
   late at night, so the palette is chosen to be easy on tired eyes first. The
   rules behind it are worth stating, because almost every past colour problem
   came from breaking one of them:

   1. ONE tint for action. The accent means "press this" or "this is the one
      selected" -- a button, a link, the current page, a ticked box -- and
      nothing else. It is a calm teal-blue: restful over a long day, never
      mistaken for a status, and clearly "the thing to press". Neutrals carry
      structure. There is no second decorative colour; a pink beside the old
      purple is what once made everything read as equally important.
   2. Every other colour is a meaning, not a decoration: success, warning and
      error, plus celebrate -- the warm orange of a lit streak or a birthday,
      the one colour whose job is to make somebody feel good. Warning is kept
      for what needs doing now, so it is never worn by plain news.
   3. Soft ink on warm paper. Text is a deep warm grey, never pure black, on an
      off-white ground: pure black on pure white glares under shop lights and
      tires the eyes over a shift. Every text tone still clears 4.5:1 on
      --bg, --surface and --fill.
   4. Elevation is material, not glow. Surfaces separate by lightness and a
      hairline. No coloured halos.
   5. Fills, not gradients. A gradient under white text has two contrast ratios
      and only one of them gets checked; the pink end of an old primary button
      measured 2.65:1.
   6. Every tint is written against a channel triplet -- the --*-rgb tokens --
      with the alpha applied at the point of use, so one channel definition
      drives the whole surface. Shadows included, via --shadow-rgb, so dark
      mode can deepen them without a second set of literals.

   Light is the default and dark follows the device. Dark is a soft charcoal,
   not pure black, for the same reason the light ink is not: white on black
   smears and glares at night. base.html injects --accent and its derivatives
   for BOTH schemes (db.accent_scheme); everything below is scheme-owned and
   not user-editable, so a chosen colour can never strand the tokens around
   it. */

/* Inter, for the devices that have no San Francisco (Windows, Android). It
   was drawn to sit beside SF, so a CA's office PC looks like the Mac the app
   was designed on. Cut to Latin, the rupee sign, punctuation and arrows
   (static/fonts/README.md). A Mac or an iPhone never downloads it: SF comes
   first in --font-sans, and a browser only fetches a font it draws with. */
@font-face {
  font-family: InterVariable;
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/InterVariable.woff2') format('woff2');
}
@font-face {
  font-family: InterVariable;
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/InterVariable-Italic.woff2') format('woff2');
}

:root {
  color-scheme: light dark;

  /* San Francisco on a Mac and an iPhone, Inter everywhere else. Inter must
     come before system-ui, or Windows takes Segoe UI. */
  --font-sans: -apple-system, BlinkMacSystemFont, 'SF Pro Text', InterVariable, 'Segoe UI', system-ui, Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* --- Accent. Overridden per-scheme by base.html from the saved theme. --- */
  --accent: #1a56db;
  --accent-rgb: 11 111 128;
  --accent-hover: #085968;
  --on-accent: #ffffff;

  /* --- Neutrals: warm paper. A hint of warmth rather than the old blue-grey,
         which read as clinical and, beside a purple accent, made the whole
         app look tinted. --- */
  --bg: #f4f4f1;            /* the page ground */
  --surface: #ffffff;       /* cards */
  --fill: #efeeea;          /* inset controls: inputs, secondary buttons */
  --fill-strong: #e4e3de;   /* the same, one step firmer (hover, tracks) */
  --border: #d5d3cd;        /* opaque separator */
  --border-soft: #e6e5e0;   /* the hairline between rows */

  /* Label hierarchy. Every one of these clears 4.5:1 on --surface, --bg and
     --fill. None is pure black: #000 on white is the harshest pairing a
     screen can show, and the one most tiring to read for a whole shift. */
  --text-strong: #1b1d20;
  --text: #2a2d31;
  --text-muted: #555a61;
  --text-faint: #62676e;

  --bg-rgb: 244 244 241;
  --surface-rgb: 255 255 255;
  --muted-rgb: 85 90 97;
  /* A warm near-black, so a shadow on warm paper reads as shade rather than
     as grey dirt. */
  --shadow-rgb: 28 25 23;

  /* --- Semantic colour. Text tones are darkened for AA on light; the fills
         are the same hue at low alpha, so one triplet drives both. --- */
  --success: #18773f;
  --success-rgb: 24 119 63;
  --warning: #8a5300;
  --warning-rgb: 138 83 0;
  --error: #b3261e;
  --error-rgb: 179 38 30;
  /* Celebrate: a lit streak, a birthday, a work anniversary. Orange because
     that is the colour of a flame -- an accent-blue fire would read as a
     button -- and redder than warning, so a party never looks like a
     problem. */
  --celebrate: #bd3f0c;
  --celebrate-rgb: 189 63 12;

  --success-text: var(--success);
  --success-bg: rgb(var(--success-rgb) / .12);
  --warning-text: var(--warning);
  --warning-bg: rgb(var(--warning-rgb) / .12);
  --error-text: var(--error);
  --error-bg: rgb(var(--error-rgb) / .12);
  --celebrate-text: var(--celebrate);
  --celebrate-bg: rgb(var(--celebrate-rgb) / .11);

  --sidebar-w: 260px;
  --topbar-h: 56px;
  --focus-ring: 0 0 0 3.5px rgb(var(--accent-rgb) / .35);

  /* --- Type ramp. The small steps are a point larger than Apple's: they
         carried every hint, badge, table and timestamp in the app at 11-13px,
         which is fine at a desk at ten in the morning and hard work on a
         phone at the end of a shift. --- */
  --fs-caption2: .75rem;     /* 12 */
  --fs-caption: .8125rem;    /* 13 */
  --fs-footnote: .875rem;    /* 14 */
  --fs-subhead: .9375rem;    /* 15 */
  --fs-callout: 1rem;        /* 16 */
  --fs-title3: 1.25rem;      /* 20 */
  --fs-title2: 1.375rem;     /* 22 */
  --fs-title1: 1.75rem;      /* 28 */

  /* --- Radii, tied to control size the way continuous corners are. --- */
  --radius-sm: .5rem;
  --radius-md: .75rem;
  --radius-lg: 1rem;
  --radius-xl: 1.25rem;
  --radius-pill: 9999px;

  /* Light mode separates by lightness, so shadows stay soft -- just enough
     for a card to sit on the paper rather than be printed on it. */
  --shadow-sm: 0 1px 2px rgb(var(--shadow-rgb) / .05), 0 1px 1px rgb(var(--shadow-rgb) / .02);
  --shadow-md: 0 1px 3px rgb(var(--shadow-rgb) / .06), 0 6px 16px -6px rgb(var(--shadow-rgb) / .1);
  --shadow-lg: 0 12px 32px -8px rgb(var(--shadow-rgb) / .16);

  /* Apple's ease-out: quick departure, long settle. No overshoot anywhere —
     interface chrome that bounces reads as a toy, and the old --ease-bounce
     was rotating card icons on hover. */
  --ease: cubic-bezier(.32, .72, 0, 1);
  --dur: .22s;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Soft charcoal, not black, with the cards a step lighter. */
    --bg: #141517;
    --surface: #1e2023;
    --fill: #2a2c30;
    --fill-strong: #35383c;
    --border: #3d4045;
    --border-soft: #2c2e32;

    /* Off-white rather than #fff, for the same reason the light ink is not
       black: white on a dark ground blooms and glares. */
    --text-strong: #f4f5f6;
    --text: #e4e6e9;
    --text-muted: #b5b9bf;
    --text-faint: #9ca1a8;

    --bg-rgb: 20 21 23;
    --surface-rgb: 30 32 35;
    --muted-rgb: 181 185 191;
    --shadow-rgb: 0 0 0;

    /* Dark semantic colours brighten rather than darken, because a dark
       ground needs luminance from the colour, not saturation. Warning goes
       gold rather than orange here, so it stays apart from celebrate. */
    --success: #4ac777;
    --success-rgb: 74 199 119;
    --warning: #f2b632;
    --warning-rgb: 242 182 50;
    --error: #ff6b61;
    --error-rgb: 255 107 97;
    --celebrate: #ff9459;
    --celebrate-rgb: 255 148 89;

    --success-bg: rgb(var(--success-rgb) / .15);
    --warning-bg: rgb(var(--warning-rgb) / .14);
    --error-bg: rgb(var(--error-rgb) / .15);
    --celebrate-bg: rgb(var(--celebrate-rgb) / .14);

    --shadow-sm: 0 1px 2px rgb(var(--shadow-rgb) / .4);
    --shadow-md: 0 2px 6px rgb(var(--shadow-rgb) / .45);
    --shadow-lg: 0 16px 40px -8px rgb(var(--shadow-rgb) / .6);
  }
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  scrollbar-color: var(--border) transparent;
  scrollbar-width: thin;
  -webkit-text-size-adjust: 100%;
}

/* San Francisco first, then Inter (--font-sans): the type should feel native
   on the owner's Mac and look the same on the office's Windows PCs. */
html, body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  font-size: var(--fs-subhead);
  line-height: 1.47;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* Tight tracking on headings only, the way SF Display differs from SF Text. */
h1, h2, h3 { letter-spacing: -.021em; line-height: 1.2; }

a { color: inherit; }
button, input, select, textarea { font-family: inherit; font-size: inherit; }

::selection { background: rgb(var(--accent-rgb) / .28); }

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: var(--radius-pill); border: 2px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); background-clip: content-box; }

:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}

/* The hidden attribute always wins. The browser's own [hidden] rule is
   weaker than any class that sets display, so a `.btn` marked hidden until a
   script decides to show it -- the passkey button on a browser without
   passkeys, the photo upload button once the page uploads by itself -- was
   drawn anyway. Every `hidden` in the app means "not yet", never "show
   regardless". */
[hidden] { display: none !important; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--accent);
  color: var(--on-accent);
  padding: .5rem 1rem;
  z-index: 200;
  border-radius: 0 0 var(--radius-sm) 0;
  font-weight: 600;
}
.skip-link:focus { left: 0; }

/* ---------- App shell ---------- */
.app-shell { display: flex; min-height: 100vh; }

/* The sidebar is a material, not a panel: translucent surface over the page
   ground with a hairline edge, so it belongs to the window rather than
   sitting on top of it. */
.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: rgb(var(--surface-rgb) / .72);
  backdrop-filter: blur(24px) saturate(180%);
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  border-right: 1px solid var(--border-soft);
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0; left: 0; bottom: 0;
  z-index: 100;
  transition: transform var(--dur) var(--ease);
}
@supports not (backdrop-filter: blur(1px)) {
  .sidebar { background: var(--surface); }
}

/* Moving between pages keeps the sidebar still and changes only the page
   beside it -- a cross-document view transition. Chrome/Edge 126+ and Safari
   18.2+ run it; every other browser just loads the page as before. Its scroll
   position is put back by the script straight after the sidebar in base.html,
   which runs before the new page is drawn. Off for anyone who asked for less
   motion. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
.sidebar { view-transition-name: sidebar; }
::view-transition-group(*) { animation-duration: .18s; }

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: 1rem 1.1rem;
  font-weight: 600;
  font-size: var(--fs-callout);
  letter-spacing: -.01em;
  color: var(--text-strong);
}
.sidebar-brand .logo-mark {
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.85rem;
  height: 1.85rem;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--on-accent);
  flex-shrink: 0;
}
.sidebar-brand .logo-mark svg { width: 1.15rem; height: 1.15rem; }

.sidebar-nav {
  flex: 1;
  overflow-y: auto;
  padding: .25rem .6rem 1rem;
}

.nav-section-label {
  font-size: var(--fs-caption2);
  font-weight: 650;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 1.1rem .65rem .35rem;
}

/* The current page is a soft wash of the accent with the label and icon in
   the accent itself. It used to be a solid accent block: on every screen, all
   day, it was the loudest thing in the window -- louder than the page's own
   primary button, which is the one place full strength belongs. The wash
   still says "you are here" at a glance. */
.nav-link {
  display: flex;
  align-items: center;
  gap: .65rem;
  padding: .5rem .65rem;
  border-radius: var(--radius-sm);
  color: var(--text);
  text-decoration: none;
  font-size: var(--fs-subhead);
  font-weight: 480;
  margin-bottom: .1rem;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.nav-link:hover { background: rgb(var(--muted-rgb) / .1); color: var(--text-strong); }
.nav-link.active {
  background: rgb(var(--accent-rgb) / .12);
  color: var(--accent);
  font-weight: 620;
}
.nav-link.active:hover { background: rgb(var(--accent-rgb) / .17); color: var(--accent); }
/* A count beside a console link (Organisations 24). Neutral, so a number that
   is only information never reads as an alert; an alert uses .badge. */
.nav-count, .nav-badge { margin-left: auto; }
.nav-count {
  font-size: var(--fs-caption);
  font-weight: 510;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.nav-link.active .nav-count { color: var(--accent); }
/* Which side of the product this is: the console says so beside the brand. */
.sidebar-brand-tag {
  margin-left: auto;
  padding: .1rem .5rem;
  border-radius: var(--radius-pill);
  background: var(--fill);
  color: var(--text-muted);
  font-size: var(--fs-caption2);
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
}
/* Icons a step quieter than their labels, so a long menu reads as words with
   markers rather than as a column of pictures. */
.nav-link .icon { flex-shrink: 0; display: inline-flex; width: 1.15rem; height: 1.15rem; color: var(--text-muted); }
.nav-link .icon svg { width: 100%; height: 100%; }
.nav-link:hover .icon { color: var(--text-strong); }
.nav-link.active .icon { color: var(--accent); }

.sidebar-footer {
  padding: .8rem 1.15rem;
  border-top: 1px solid var(--border-soft);
  font-size: var(--fs-caption2);
  color: var(--text-faint);
}

.main {
  flex: 1;
  margin-left: var(--sidebar-w);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-width: 0;
}

.sticky-header { position: sticky; top: 0; z-index: 50; }

.topbar {
  height: var(--topbar-h);
  border-bottom: 1px solid var(--border-soft);
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: 0 1.25rem;
  background: rgb(var(--bg-rgb) / .8);
  backdrop-filter: blur(24px) saturate(180%);
  -webkit-backdrop-filter: blur(24px) saturate(180%);
}
@supports not (backdrop-filter: blur(1px)) {
  .topbar { background: var(--bg); }
}

.topbar-title {
  font-size: var(--fs-subhead);
  font-weight: 590;
  color: var(--text-strong);
  letter-spacing: -.01em;
  /* Truncate rather than push: a long page title must never be what shoves the
     account controls off the right edge of a phone. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.topbar-spacer { flex: 1; }
.topbar-user { display: flex; align-items: center; gap: .5rem; }

/* ---------- Module tabs ---------- */
/* One tab per module this account can reach (Staff, Practice, …). base.html
   renders the strip only when there is more than one, so a single-module
   owner never gets a switcher with nothing to switch to.

   flex-shrink: 0 is load-bearing. The topbar's own overflow rule is that the
   page title truncates rather than pushing the account controls off a 390px
   phone, so the strip must hold its width and let .topbar-title ellipsis down
   into whatever is left. */
.module-tabs {
  display: flex;
  gap: .125rem;
  padding: .1875rem;
  background: var(--fill);
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}
.module-tab {
  display: inline-flex;
  align-items: center;
  padding: .3rem .7rem;
  border-radius: calc(var(--radius-sm) - .1875rem);
  font-size: var(--fs-footnote);
  font-weight: 510;
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
  transition: color .15s ease, background .15s ease;
}
.module-tab:hover { color: var(--text-strong); }
/* The menu's copy of the strip: hidden until a phone, where it replaces the
   top bar's (see the 600px block). Tabs share the width, so three fit. */
.module-tabs-drawer { display: none; margin: 0 0 .75rem; }
.module-tabs-drawer .module-tab { flex: 1; justify-content: center; }
.module-tab.active {
  background: var(--surface);
  color: var(--text-strong);
  font-weight: 590;
  box-shadow: 0 1px 2px rgb(var(--shadow-rgb) / .12);
}

/* ---------- Back to a routine ----------
   A routine step opened this page (routines.go). The bar is the way back, and
   coming back ticks the step, so it is the loudest thing on the page: a full
   accent bar at the very top, sticky with the rest of the header. */
.routine-return { margin: 0; }
.routine-return-btn {
  display: flex;
  align-items: center;
  gap: .55rem;
  width: 100%;
  min-height: 3rem;
  padding: .55rem 1.25rem;
  border: none;
  background: var(--accent);
  color: var(--on-accent);
  font: inherit;
  font-size: var(--fs-callout);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.routine-return-btn:hover { background: var(--accent-hover); }
.routine-return-btn .icon svg { width: 1.25rem; height: 1.25rem; display: block; }
.routine-return-step { font-weight: 500; opacity: .85; }

/* ---------- Announcements ----------
   The stack sits below the top bar and scrolls away with the page (base.html
   says why). Paper only: a printed bill or payslip never carries one. */
.announcement-stack { display: flex; flex-direction: column; }
@media print { .announcement-stack { display: none; } }

/* Three tones, each a meaning:
   - the plain banner wears warning: a due reminder or a handover note, things
     somebody has to do now;
   - .is-news wears a soft accent wash: an announcement or "a module was
     switched on" -- worth reading, not an alarm. Announcements used to wear
     warning too, so every page opened under one or two amber bars and staff
     learned to stop seeing amber at all;
   - .is-celebration wears celebrate: a birthday, a work anniversary.
   The tints are translucent, so each is laid over the page ground with an
   inset shadow -- one flat fill whatever sits behind it, the same as the
   support bar. */
.announcement-banner {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .6rem 1.25rem;
  background: var(--bg);
  box-shadow: inset 0 0 0 100vmax var(--warning-bg);
  color: var(--warning-text);
  border-bottom: 1px solid rgb(var(--warning-rgb) / .3);
  font-size: var(--fs-footnote);
  font-weight: 500;
  line-height: 1.4;
}
.announcement-banner-text { flex: 1; }
.announcement-banner.is-news {
  box-shadow: inset 0 0 0 100vmax rgb(var(--accent-rgb) / .09);
  color: var(--text-strong);
  border-bottom-color: rgb(var(--accent-rgb) / .2);
}
.announcement-banner.is-news .announcement-banner-close:hover { background: rgb(var(--accent-rgb) / .16); }
.announcement-banner-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  border: none;
  background: transparent;
  color: inherit;
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}
.announcement-banner-close:hover { background: rgb(var(--warning-rgb) / .18); }

/* Birthdays and work anniversaries share the banner, not its colour: a
   celebration is not something to act on, so it wears celebrate -- the warm
   orange of the streak flame -- rather than warning. */
.announcement-banner.is-celebration {
  box-shadow: inset 0 0 0 100vmax var(--celebrate-bg);
  color: var(--text-strong);
  border-bottom-color: rgb(var(--celebrate-rgb) / .28);
}
.announcement-banner.is-celebration .announcement-banner-close:hover {
  background: rgb(var(--celebrate-rgb) / .18);
}
/* The icon takes the banner's own ink, so a bar holds one tint: warning on
   warning, the accent on news, celebrate on a celebration. */
.announcement-banner-icon {
  display: inline-flex;
  flex-shrink: 0;
  color: inherit;
}
.announcement-banner.is-news .announcement-banner-icon { color: var(--accent); }
.announcement-banner.is-celebration .announcement-banner-icon { color: var(--celebrate); }
.announcement-banner-icon svg { width: 1.15rem; height: 1.15rem; }

/* More than two news banners fold behind one slim row (base.html). It wears
   the news wash at half strength, so it reads as part of the stack and never
   as a banner of its own. Warning banners are never folded. */
.announcement-more > summary { list-style: none; }
.announcement-more > summary::-webkit-details-marker { display: none; }
.announcement-more-toggle {
  display: flex;
  align-items: center;
  gap: .35rem;
  padding: .4rem 1.25rem .4rem 3.15rem;
  background: var(--bg);
  box-shadow: inset 0 0 0 100vmax rgb(var(--accent-rgb) / .05);
  border-bottom: 1px solid rgb(var(--accent-rgb) / .2);
  color: var(--accent);
  font-size: var(--fs-caption);
  font-weight: 590;
  cursor: pointer;
  user-select: none;
}
.announcement-more-toggle:hover { box-shadow: inset 0 0 0 100vmax rgb(var(--accent-rgb) / .1); }
.announcement-more-toggle:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.announcement-more-caret { display: inline-flex; }
.announcement-more-caret svg { width: .95rem; height: .95rem; transition: transform var(--dur) var(--ease); }
.announcement-more[open] .announcement-more-caret svg { transform: rotate(90deg); }
.announcement-more:not([open]) .announcement-more-open,
.announcement-more[open] .announcement-more-closed { display: none; }

/* The billing banner (subscriptions.owner_notice): a trial ending or a
   payment due wears the warning tint like any banner asking for something;
   overdue and read-only wear the error tint, because the account is about to
   stop, or has. */
.announcement-banner.is-danger {
  box-shadow: inset 0 0 0 100vmax var(--error-bg);
  color: var(--error-text);
  border-bottom-color: rgb(var(--error-rgb) / .3);
}

/* ---------- Support view ----------
   Super Admin inside a customer's branch (auth._load_platform_scope). A full
   strip above everything, amber while it is read-only and red once changes
   are allowed: the one thing on screen that must never be missed is whose
   live data this is. Its buttons are outlined in the strip's own ink, the
   same rule as the reminder banner, so there is one tint in the bar. */
.support-bar {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-wrap: wrap;
  padding: .5rem 1.25rem;
  /* The tint is translucent and the bar is sticky, so it is laid over the
     page ground with an inset shadow: one flat fill that the page scrolling
     underneath never shows through. */
  background: var(--bg);
  box-shadow: inset 0 0 0 100vmax var(--warning-bg);
  color: var(--warning-text);
  border-bottom: 1px solid rgb(var(--warning-rgb) / .35);
  font-size: var(--fs-footnote);
}
.support-bar.is-live {
  box-shadow: inset 0 0 0 100vmax var(--error-bg);
  color: var(--error-text);
  border-bottom-color: rgb(var(--error-rgb) / .35);
}
.support-bar-icon { display: inline-flex; flex-shrink: 0; }
.support-bar-icon svg { width: 1.2rem; height: 1.2rem; }
.support-bar-text { flex: 1; display: flex; flex-wrap: wrap; align-items: center; gap: .25rem .6rem; min-width: 0; }
.support-bar-text strong { font-weight: 650; }
.support-bar-where { color: var(--text-strong); font-weight: 560; overflow-wrap: anywhere; }
.support-pill {
  display: inline-flex;
  padding: .1rem .55rem;
  border-radius: var(--radius-pill);
  border: 1px solid currentColor;
  font-size: var(--fs-caption);
  font-weight: 560;
  white-space: nowrap;
}
.support-bar-actions { display: flex; gap: .4rem; flex-wrap: wrap; }
.support-bar-actions form { display: inline; }
.support-btn {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .3rem .75rem;
  border-radius: var(--radius-pill);
  border: 1px solid currentColor;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--fs-caption);
  font-weight: 590;
  cursor: pointer;
  white-space: nowrap;
}
.support-btn:hover { background: rgb(var(--shadow-rgb) / .06); }
.support-btn .icon { display: inline-flex; }
.support-btn .icon svg { width: .95rem; height: .95rem; }
.support-btn-strong { background: var(--surface); color: var(--text-strong); border-color: transparent; }
.support-btn-strong:hover { background: var(--fill); }
@media (max-width: 600px) {
  .support-bar { padding: .5rem .75rem; }
  .support-bar-actions { width: 100%; }
  .support-bar-actions form, .support-btn { flex: 1; justify-content: center; }
}

/* A due reminder is the one banner that asks for an action rather than just
   saying something, so it carries two controls of its own. They are outlined
   in the banner's own ink instead of being filled: a solid button inside a
   tinted strip puts two competing tints in one bar. */
.announcement-banner-text a { color: inherit; font-weight: 590; }
.reminder-banner-note { display: block; font-size: var(--fs-caption); opacity: .85; }
.reminder-banner-btn {
  flex-shrink: 0;
  padding: .2rem .65rem;
  border: 1px solid rgb(var(--warning-rgb) / .45);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--warning-text);
  font: inherit;
  font-size: var(--fs-caption);
  font-weight: 510;
  cursor: pointer;
}
.reminder-banner-btn:hover { background: rgb(var(--warning-rgb) / .18); }

.sidebar-toggle {
  display: none;
  background: none;
  border: none;
  color: var(--accent);
  border-radius: var(--radius-sm);
  width: 2rem;
  height: 2rem;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.sidebar-toggle:hover { background: rgb(var(--muted-rgb) / .1); }

.admin-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: var(--radius-pill);
  background: transparent;
  border: none;
  color: var(--accent);
  text-decoration: none;
  transition: background var(--dur) var(--ease);
}
.admin-badge:hover { background: rgb(var(--accent-rgb) / .12); }
.admin-badge svg { width: 1.05rem; height: 1.05rem; }

.username-chip {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: var(--fs-footnote);
  font-weight: 500;
  color: var(--text-strong);
  padding: .2rem .35rem;
  border-radius: var(--radius-pill);
  line-height: 1.25;
}
.username-chip .avatar {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-pill);
  /* A wash, not a solid disc: the top bar is on every page, and a filled
     accent circle there was one more loud spot competing with the page. */
  background: rgb(var(--accent-rgb) / .14);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-caption);
  font-weight: 650;
  text-transform: uppercase;
  flex-shrink: 0;
}
.role-pill {
  font-size: var(--fs-caption2);
  color: var(--text-faint);
  font-weight: 400;
}

.logout-form { display: inline; }
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: var(--radius-pill);
  border: none;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.icon-btn:hover { color: var(--text-strong); background: rgb(var(--muted-rgb) / .12); }
.icon-btn svg { width: 1.05rem; height: 1.05rem; }
/* "Install the app" starts hidden until Chrome says it can install
   (app_install.js). .icon-btn and .btn both set a display, which beats the
   bare hidden attribute, so the pair has to say it again. */
[data-install-app][hidden] { display: none; }

.content { padding: 1.75rem 2rem 3rem; max-width: 1100px; width: 100%; }
/* 1100px is a reading measure, right for every page that is mostly prose, cards
   and a narrow table. A console page is not one of those: the Users table is
   eight columns wide and two of them hold edit forms, so it needs around
   1050px before something is hidden. Opt in with {% block content_class %}. */
.content-wide { max-width: 1500px; padding-left: 1.5rem; padding-right: 1.5rem; }

.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgb(var(--shadow-rgb) / .35);
  z-index: 90;
}

/* ---------- Branch mark ---------- */
.branch-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: rgb(var(--accent-rgb) / .12);
  color: var(--accent);
  font-size: var(--fs-footnote);
  font-weight: 600;
  line-height: 1;
  text-transform: uppercase;
}
/* contain, not cover: a logo is a mark to be read whole, and cropping one to
   fill a square is how you lose half a wordmark. */
.branch-mark img { width: 100%; height: 100%; object-fit: contain; display: block; }
.branch-mark-lg {
  width: 3rem;
  height: 3rem;
  border-radius: var(--radius-md);
  font-size: var(--fs-title3);
}

.branch-chip-mark { display: inline-flex; align-items: center; }

/* The branch / organisation switcher. Only rendered for people who have more
   than one of either, so it is never in the way of the single-shop owner it
   would only confuse. A <details> rather than a scripted menu: it opens,
   closes and keyboard-navigates on its own, and each row is a real form post
   so the pick survives without JavaScript. */
.scope-switcher { position: relative; }
.scope-switcher > summary {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .25rem .5rem .25rem .3rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  list-style: none;
  color: var(--text);
  max-width: 13rem;
}
.scope-switcher > summary::-webkit-details-marker { display: none; }
.scope-switcher > summary:hover { background: var(--surface-hover, rgb(var(--accent-rgb) / .08)); }
.scope-switcher > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.scope-switcher-label {
  font-size: var(--fs-footnote);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.scope-switcher-caret { width: .85rem; height: .85rem; flex: none; opacity: .6; }
.scope-switcher[open] .scope-switcher-caret { transform: rotate(180deg); }

.scope-switcher-menu {
  position: absolute;
  right: 0;
  top: calc(100% + .35rem);
  z-index: 40;
  min-width: 13rem;
  max-height: 60vh;
  overflow-y: auto;
  padding: .35rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.scope-switcher-heading {
  margin: .35rem .5rem .2rem;
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: .05em;
}
.scope-switcher-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: .4rem .5rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text);
  font: inherit;
  font-size: var(--fs-footnote);
  cursor: pointer;
}
.scope-switcher-item:hover { background: rgb(var(--accent-rgb) / .1); }
.scope-switcher-item.is-current { font-weight: 700; color: var(--accent); }
.scope-switcher-item.is-current::after { content: ' \2713'; }

@media (max-width: 640px) {
  .scope-switcher-label { display: none; }
  .scope-switcher > summary { max-width: none; }
}

.branch-header { display: flex; align-items: center; gap: .85rem; }
.branch-header-name {
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: .05em;
  margin-bottom: .1rem;
}

/* ---------- Page header ---------- */
.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
}
.page-header h1 {
  font-size: var(--fs-title1);
  font-weight: 680;
  color: var(--text-strong);
  letter-spacing: -.024em;
}
.page-header .subtitle { color: var(--text-faint); font-size: var(--fs-subhead); margin-top: .25rem; }
.header-actions { display: flex; gap: .5rem; flex-wrap: wrap; }

/* Super Admin's organisation picker (_org_picker.html), on the pages that are
   about one organisation's people and roles. A segmented row of chips, the
   selected one filled with the accent, like the Settings branch picker. */
.org-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-bottom: 1.25rem;
}
.org-picker-label {
  font-size: var(--fs-footnote);
  font-weight: 590;
  color: var(--text-muted);
  margin-right: .25rem;
}
.org-chip {
  display: inline-flex;
  align-items: center;
  padding: .35rem .8rem;
  border-radius: var(--radius-pill);
  background: var(--fill);
  color: var(--text-muted);
  font-size: var(--fs-footnote);
  font-weight: 510;
  text-decoration: none;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.org-chip:hover { color: var(--text-strong); background: var(--fill-strong); }
.org-chip.on { background: var(--accent); color: var(--on-accent); }
.org-chip.on:hover { background: var(--accent-hover); color: var(--on-accent); }
.org-chip-platform { font-style: italic; }

/* ---------- Cards / grid ---------- */
.card {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 1.25rem;
  box-shadow: var(--shadow-sm);
}
/* The heading at the top of a card. Shared rather than page-scoped because a
   dozen pages want it and they had drifted into two versions of the same
   thing: this rule used to live inside admin_settings.html's own head block,
   which left the Order Board's `class="card-heading"` styling nothing at all,
   while eleven other pages hand-wrote the same three declarations inline.
   One definition here is what keeps a card heading the same size on every
   page it appears on. */
.card h2.card-heading,
h2.card-heading,
.card h3.card-heading {
  font-size: var(--fs-callout);
  font-weight: 620;
  color: var(--text-strong);
  margin-bottom: 1rem;
}
/* The line of explanation under one. Pairs with .card-heading so the gap
   between a heading and its hint is set once. */
.card-heading + .hint { margin-top: -.6rem; margin-bottom: 1rem; }

/* ---------- Stat tiles ---------- */
/* A row of single numbers above a table — how many check-ins, how many
   branches. Shared because the pages that want one had each written the label
   and the figure as inline styles, at three different sizes. Auto-fit rather
   than a fixed column count so three tiles and six both fill the row and both
   stack on a phone. */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1rem;
  margin-bottom: 1.5rem;
}
.stat-row > * { min-width: 0; }
.stat-tile .stat-label {
  font-size: var(--fs-caption);
  color: var(--text-faint);
  margin-bottom: .2rem;
}
.stat-tile .stat-value {
  font-size: var(--fs-title2);
  font-weight: 660;
  color: var(--text-strong);
  letter-spacing: -.02em;
  line-height: 1.15;
}
/* A figure that is only worth reading when it isn't zero — an unowned branch,
   an owner running nothing yet. Tinted so it is findable in a row of neutral
   numbers, and left neutral at zero so a clean install isn't covered in
   warnings. */
.stat-tile .stat-value.is-flagged { color: var(--warning); }

/* ---------- A person's record ---------- */
/* Label-over-value pairs: the Employee Status record and the employee's own
   My Profile page draw the same facts, so they share one grid. */
.record-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1rem 1.5rem;
  margin: 0;
}
.record-grid > div { min-width: 0; }
.record-label {
  font-size: var(--fs-caption); color: var(--text-faint);
  text-transform: uppercase; letter-spacing: .04em; margin-bottom: .15rem;
}
.record-value { color: var(--text-strong); font-weight: 590; word-break: break-word; }
.record-value .muted { font-weight: 400; }

/* ---------- Page tabs ---------- */
/* A strip of links between views of one record -- the Practice entity page.
   Links, not buttons: each tab is its own URL, so a tab survives a refresh
   and a save lands back on it. */
.page-tabs { display: flex; gap: .25rem; border-bottom: 1px solid var(--border); margin-bottom: 1.25rem; overflow-x: auto; }
.page-tab {
  padding: .55rem .9rem; font-size: var(--fs-subhead); color: var(--text-muted);
  text-decoration: none; border-bottom: 2px solid transparent; margin-bottom: -1px; white-space: nowrap;
}
.page-tab:hover { color: var(--text-strong); }
.page-tab.on { color: var(--accent); border-bottom-color: var(--accent); font-weight: 590; }

/* A figure that should not wrap or jiggle as it changes: money, hours. */
.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.num-strong { font-variant-numeric: tabular-nums; white-space: nowrap; font-weight: 590; color: var(--text-strong); }

/* ---------- Leave ---------- */
/* One request drawn by _leaves.html on three pages: My Leaves, Leave Requests
   and the Leave section of a person's record. */
.leave-when { font-weight: 590; color: var(--text-strong); white-space: nowrap; }
.leave-sub { font-size: var(--fs-caption); }
.leave-decide { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; }
.leave-decide input[type=text] { min-width: 8rem; flex: 1 1 8rem; }

.grid { display: grid; gap: 1rem; }
/* Grid items default to min-width:auto, which lets a wide table push its own
   column past the layout instead of scrolling inside it — the Users page
   table used to run off the right edge of the window for exactly this reason.
   overflow-x on .table-wrap only works once the track is allowed to shrink. */
.grid > * { min-width: 0; }
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid-sidebar { grid-template-columns: 320px 1fr; }
.grid-sidebar-wide { grid-template-columns: 380px 1fr; }
@media (max-width: 760px) {
  .grid-cols-3, .grid-cols-2, .grid-sidebar, .grid-sidebar-wide {
    grid-template-columns: 1fr;
  }
}

/* ---------- Users page layout ---------- */
/* The Users table is eight columns wide and two of them hold edit forms, so it
   needs the better part of 1050px before something is hidden. It used to sit in
   the middle track of `300px 1fr 280px` inside a 1100px page, which left it
   about 430px on a 1440px laptop: six of the eight columns were reachable only
   by scrolling a box inside the page, while the right-hand third of the window
   stood empty. The fix is that nothing else takes a column beside it — the form
   is a disclosure and the dashboard a strip, both full width. */
.users-layout { display: flex; flex-direction: column; gap: 1rem; }
.add-user-panel { padding: 1rem 1.25rem; }
.add-user-panel[open] { padding-bottom: 1.25rem; }
.add-user-summary {
  cursor: pointer;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text-strong);
  list-style: none;
  display: flex;
  align-items: center;
  gap: .5rem;
}
.add-user-summary::-webkit-details-marker { display: none; }
/* A chevron that turns, rather than the platform triangle, so the panel reads
   as one of the app's own controls. */
.add-user-summary::before {
  content: "";
  width: .5rem;
  height: .5rem;
  border-right: 2px solid var(--text-faint);
  border-bottom: 2px solid var(--text-faint);
  transform: rotate(-45deg);
  transition: transform var(--dur) var(--ease);
}
.add-user-panel[open] .add-user-summary::before { transform: rotate(45deg); }
.add-user-summary:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--radius-sm); }
.add-user-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 0 1.5rem;
  align-items: start;
  margin-top: 1rem;
}
/* Same reason as `.grid > *`: without this the weekday chips inside the
   fieldset set their own track's min-content and push the page sideways on a
   390px phone instead of reflowing. */
.add-user-grid > *, .dashboard-panel > * { min-width: 0; }
/* The weekday row is a set of seven chips, not one field: give it a whole row
   rather than letting it squeeze into a 14rem track. */
.add-user-grid-wide { grid-column: 1 / -1; }

/* Once everything is a full-width block, five stat cards and a creation form
   stand between a phone and the list it came for. The list goes first there. */
@media (max-width: 860px) {
  .users-layout > .users-table-col { order: -1; }
  .users-layout > .dashboard-col { order: 1; }
}

.nav-card {
  display: flex;
  gap: .9rem;
  align-items: center;
  text-decoration: none;
  color: inherit;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.nav-card:hover { background: var(--fill); border-color: var(--border); }
.nav-card:active { background: var(--fill-strong); }
.nav-card .nav-card-icon {
  width: 2.4rem;
  height: 2.4rem;
  border-radius: var(--radius-md);
  background: rgb(var(--accent-rgb) / .12);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.nav-card .nav-card-icon svg { width: 1.3rem; height: 1.3rem; }
.nav-card h2 {
  font-size: var(--fs-subhead);
  font-weight: 590;
  color: var(--text-strong);
  margin-bottom: .1rem;
}
.nav-card p { font-size: var(--fs-footnote); color: var(--text-faint); line-height: 1.4; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  text-decoration: none;
  padding: .55rem 1.05rem;
  border-radius: var(--radius-md);
  font-size: var(--fs-subhead);
  /* A label you can read at a glance: a button is read, not studied. */
  font-weight: 600;
  cursor: pointer;
  border: 1px solid transparent;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), opacity var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
/* Apple presses fade rather than move — a button that translates on :active
   drags the text with it and reads as a wobble at small sizes. */
.btn:active:not(:disabled) { opacity: .72; }
.btn:disabled { opacity: .4; cursor: not-allowed; }

.btn-primary { background: var(--accent); color: var(--on-accent); }
.btn-primary:hover:not(:disabled) { background: var(--accent-hover); }

.btn-secondary { background: var(--fill); color: var(--text-strong); border-color: transparent; }
.btn-secondary:hover:not(:disabled) { background: var(--fill-strong); }

.btn-danger { background: transparent; color: var(--error-text); border-color: rgb(var(--error-rgb) / .4); }
.btn-danger:hover:not(:disabled) { background: var(--error-bg); border-color: rgb(var(--error-rgb) / .6); }

.btn-sm { padding: .35rem .75rem; font-size: var(--fs-footnote); border-radius: var(--radius-sm); }
/* An icon leading a button's label (console: Open, New customer). The icon
   macro draws a bare 24x24 box, so it needs a size wherever it sits. */
.btn > .icon { display: inline-flex; width: 1rem; height: 1rem; flex-shrink: 0; }
.btn > .icon svg { width: 100%; height: 100%; }
.btn-sm > .icon { width: .9rem; height: .9rem; }

/* ---------- Attendance corrections ---------- */
/* The form that corrects a day lives in the row it corrects, so the day being
   changed is never more than a glance from the fields changing it. A <details>
   rather than a dialog: the row simply grows, which needs no positioning and
   behaves the same on a phone as on a desk. */
.correct-cell > summary { list-style: none; display: inline-flex; }
.correct-cell > summary::-webkit-details-marker { display: none; }
.correct-form {
  margin-top: .6rem;
  padding: .8rem;
  min-width: 15rem;
  text-align: left;
  background: var(--fill);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.correct-form .field { margin-bottom: .6rem; }
.correct-form .hint { margin: 0 0 .6rem; }

/* ---------- Pay ---------- */
/* The Pay form opens in the row whose money it records, the same way a
   correction does: Employee Status, Still to pay, a person's paid history and
   the month page all draw it from _salary.html. */
.pay-cell > summary { list-style: none; display: inline-flex; margin-top: .35rem; }
.pay-cell > summary::-webkit-details-marker { display: none; }
.pay-form {
  margin-top: .6rem;
  padding: .8rem;
  min-width: 14rem;
  max-width: 18rem;
  text-align: left;
  background: var(--fill);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.pay-form .field { margin-bottom: .6rem; }
.pay-form input, .pay-form select { width: 100%; }
.pay-amount { font-variant-numeric: tabular-nums; white-space: nowrap; }
.pay-amount-strong { font-weight: 590; color: var(--text-strong); }
.pay-detail { font-size: var(--fs-caption); color: var(--text-faint); }
.pay-lines td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pay-lines .pay-total td { border-top: 1px solid var(--border); font-weight: 700; color: var(--text-strong); }

/* A setting whose second question only exists because of the first: the steps
   are numbered and the second is indented under the answer that reveals it, so
   an owner can see that leaving step 1 alone leaves nothing undone. */
.settings-step-label {
  font-size: var(--fs-subhead);
  font-weight: 590;
  color: var(--text-strong);
  margin: 0 0 .3rem;
}
.settings-step {
  margin: 1.1rem 0 0;
  padding: 1rem 0 0 1rem;
  border-top: 1px solid var(--border);
  border-left: 2px solid rgb(var(--accent-rgb) / .35);
}
@media (max-width: 30rem) { .settings-step { padding-left: .7rem; } }
.btn-block { width: 100%; }

/* ---------- Forms ---------- */
.field { margin-bottom: 1rem; }
.field label {
  display: block;
  font-size: var(--fs-footnote);
  font-weight: 510;
  color: var(--text-muted);
  margin-bottom: .35rem;
}
.field .hint { font-size: var(--fs-caption); color: var(--text-faint); margin-top: .3rem; line-height: 1.4; }
.field .error-text { font-size: var(--fs-caption); color: var(--error-text); margin-top: .3rem; }

/* Every text-like input the app actually uses. tel, time and email used to
   fall through to the browser's native control, which is why the Users table
   had three differently-shaped boxes in one row. An input with no type is a
   text box to the browser, so it is one here too. */
input:not([type]), input[type=text], input[type=password], input[type=file],
input[type=date], input[type=number], input[type=month],
input[type=time], input[type=tel], input[type=email],
input[type=search],
select, textarea {
  width: 100%;
  background: var(--fill);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  padding: .6rem .8rem;
  color: var(--text-strong);
  /* 16px, not the 15px of body text: iOS zooms the whole page into any field
     smaller than that the moment it is tapped, and then leaves it zoomed --
     on a phone that was every form in the app. */
  font-size: 1rem;
  outline: none;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
input:not([type]):hover, input[type=text]:hover, input[type=password]:hover, input[type=date]:hover,
input[type=number]:hover, input[type=month]:hover, input[type=time]:hover,
input[type=tel]:hover, input[type=email]:hover, input[type=search]:hover,
textarea:hover, select:hover { background: var(--fill-strong); }
input:not([type]):focus, input[type=text]:focus, input[type=password]:focus, input[type=date]:focus,
input[type=number]:focus, input[type=month]:focus, input[type=time]:focus,
input[type=tel]:focus, input[type=email]:focus, input[type=search]:focus,
textarea:focus, select:focus {
  background: var(--surface);
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}
textarea { resize: vertical; min-height: 5rem; line-height: 1.5; }

/* A select styled like a text input but with no indicator reads as an input
   you cannot type in. The chevron is an inline SVG data URI because the CSP is
   default-src 'self' — no external asset, and it recolours per scheme. */
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2362676e' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6.5 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right .65rem center;
  background-size: 1rem;
}
@media (prefers-color-scheme: dark) {
  select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%239ca1a8' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6.5 4 4 4-4'/%3E%3C/svg%3E");
  }
}

/* The native date/month glyph is drawn dark. It only needs inverting on a dark
   ground — inverting it unconditionally, as the offer form used to, left a
   white-on-white picker button in light mode. */
@media (prefers-color-scheme: dark) {
  input[type=date]::-webkit-calendar-picker-indicator,
  input[type=month]::-webkit-calendar-picker-indicator,
  input[type=time]::-webkit-calendar-picker-indicator { filter: invert(1) opacity(.7); }
}

/* A row of controls ending in a button — six forms were hand-rolling this as
   an inline style, with the gap and margin drifting between them. */
.form-row {
  display: flex;
  gap: .75rem;
  align-items: flex-end;
  flex-wrap: wrap;
}
.form-row .field { margin-bottom: 0; }

/* Field widths. These were inline styles picking 9rem, 12rem, 14rem and 320px
   with no relationship to each other; three steps cover every case. */
.field-xs { max-width: 8rem; }
.field-sm { max-width: 13rem; }
.field-md { max-width: 20rem; }
.field-xs input, .field-sm input, .field-md input { width: 100%; }

input[type=search] {
  -webkit-appearance: none;
  appearance: none;
}
input[type=search]::-webkit-search-decoration,
input[type=search]::-webkit-search-cancel-button { -webkit-appearance: none; }

input[type=color] {
  width: 3rem;
  height: 2.25rem;
  padding: .2rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--fill);
  cursor: pointer;
}
input[type=color]:hover { background: var(--fill-strong); }
input[type=color]:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* min-width:0 is not tidiness — every browser's UA stylesheet gives fieldset
   `min-inline-size: min-content`, which no amount of `width` or `max-width` on
   it overrides. So a fieldset wrapping anything with a wide min-content (the
   Users page's seven weekday chips) refuses to shrink below that intrinsic
   width and pushes the whole page sideways: /manage/users scrolled to 538px
   on a 390px phone from exactly this. Same class of bug as the `.grid > *`
   rule further down, but a fieldset needs it stated even when it is not a
   grid child. */
fieldset { border: none; min-width: 0; }
legend {
  font-size: var(--fs-footnote);
  font-weight: 510;
  color: var(--text-muted);
  margin-bottom: .45rem;
  padding: 0;
}
.checkbox-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: .4rem; }
@media (max-width: 520px) { .checkbox-grid { grid-template-columns: 1fr; } }

.checkbox-row {
  display: flex;
  align-items: center;
  gap: .55rem;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: .45rem .55rem;
  transition: background var(--dur) var(--ease);
}
.checkbox-row:hover { background: var(--fill); }
.checkbox-row:has(input:checked) { background: rgb(var(--accent-rgb) / .1); }

/* Apple checkboxes fill with the accent and stamp a white glyph — the tick is
   never a second colour, and a checked box never turns green. Green means
   "succeeded", not "selected", and conflating the two was why the old
   permission grids read like a status report. */
.checkbox-row input[type=checkbox], .checkbox-row input[type=radio] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  width: 1.1rem;
  height: 1.1rem;
  flex-shrink: 0;
  margin: 0;
  border: 1.5px solid var(--border);
  border-radius: .3rem;
  background: var(--surface);
  cursor: pointer;
}
.checkbox-row input[type=radio] { border-radius: var(--radius-pill); }
.checkbox-row input[type=checkbox]:hover:not(:disabled),
.checkbox-row input[type=radio]:hover:not(:disabled) { border-color: var(--text-faint); }
.checkbox-row input[type=checkbox]:checked, .checkbox-row input[type=radio]:checked {
  background-color: var(--accent);
  border-color: var(--accent);
}
.checkbox-row input[type=checkbox]::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 46%;
  width: .26rem;
  height: .52rem;
  border: solid var(--on-accent);
  border-width: 0 .13rem .13rem 0;
  transform: translate(-50%, -58%) rotate(45deg) scale(.4);
  opacity: 0;
}
.checkbox-row input[type=radio]::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: .42rem;
  height: .42rem;
  border-radius: var(--radius-pill);
  background: var(--on-accent);
  transform: translate(-50%, -50%) scale(.4);
  opacity: 0;
}
.checkbox-row input[type=checkbox]:checked::after {
  opacity: 1;
  transform: translate(-50%, -58%) rotate(45deg) scale(1);
}
.checkbox-row input[type=radio]:checked::after {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
.checkbox-row input[type=checkbox]:focus-visible, .checkbox-row input[type=radio]:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.checkbox-row input[type=checkbox]:disabled, .checkbox-row input[type=radio]:disabled {
  opacity: .4;
  cursor: not-allowed;
}
.checkbox-row label { margin: 0; font-size: var(--fs-subhead); color: var(--text); font-weight: 400; cursor: pointer; }

@media (prefers-reduced-motion: no-preference) {
  .checkbox-row input[type=checkbox], .checkbox-row input[type=radio] {
    transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
  }
  .checkbox-row input[type=checkbox]::after, .checkbox-row input[type=radio]::after {
    transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
  }
}

/* ---------- Info row ----------
   An icon beside a line of explanation. checkin.html used to build this out of
   .checkbox-row, which meant a static notice inherited the hover and
   :has(:checked) states of a control and looked clickable. */
.info-row {
  display: flex;
  align-items: flex-start;
  gap: .6rem;
  padding: .7rem .8rem;
  border-radius: var(--radius-md);
  background: var(--fill);
  color: var(--text-muted);
  font-size: var(--fs-footnote);
  line-height: 1.45;
}
.info-row > svg, .info-row .info-row-icon svg {
  width: 1.15rem;
  height: 1.15rem;
  flex: none;
  margin-top: .05rem;
}
.info-row-title { font-weight: 510; color: var(--text-strong); }
.info-row-success { background: var(--success-bg); color: var(--success-text); }
.info-row-success .info-row-title { color: var(--success-text); }
.info-row-warning { background: var(--warning-bg); color: var(--warning-text); }
.info-row-warning .info-row-title { color: var(--warning-text); }

/* ---------- Permission groups (parent + gated sub-permissions) ---------- */
.permission-group {
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: .7rem .75rem;
  margin-bottom: .6rem;
  background: transparent;
}
.permission-group-parent { margin-bottom: .35rem; }
.permission-group-children {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: .3rem;
  padding-left: 1.35rem;
  border-left: 1.5px solid var(--border-soft);
  margin-left: .55rem;
}
@media (max-width: 520px) { .permission-group-children { grid-template-columns: 1fr; } }
.permission-group-children .checkbox-row.is-locked { opacity: .45; }
.permission-loose-grid { margin-top: .4rem; }

/* ---------- Weekday checkbox row (Weekoffs) ---------- */
.weekday-grid { grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr)); }
.weekday-grid .checkbox-row { justify-content: center; padding: .5rem .4rem; }

/* ---------- Sortable table headers ---------- */
th.sortable { padding: 0; }
th.sortable a {
  display: flex;
  align-items: center;
  gap: .25rem;
  padding: .6rem .9rem;
  color: inherit;
  text-decoration: none;
}
th.sortable a:hover { color: var(--text-strong); }
th.sortable .sort-arrow { font-size: .6rem; color: var(--accent); }

/* ---------- Device badges ---------- */
.device-list { display: flex; flex-direction: column; gap: .3rem; }
.device-chip {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: var(--fs-caption2);
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
  color: var(--text-muted);
  background: var(--fill);
  border: none;
  border-radius: var(--radius-sm);
  padding: .25rem .5rem;
}
.device-chip form { display: inline-flex; }
.device-chip .link-btn { font-size: var(--fs-caption2); }

/* An edit form living inside a table cell: an input or two and its Save. It
   deliberately does not wrap — a table whose min-content exceeds the space it
   is given scrolls inside .table-wrap, which is the answer on a phone, and
   wrapping instead would break the control across lines at every width. */
.cell-form { display: flex; gap: .4rem; align-items: center; }
/* A flex item shrinks to nothing before its row does, and .field-xs sets only a
   maximum, so the phone input was ending up 30px wide — one digit of the number
   it is showing. Give the column something to hold. */
.cell-form input { min-width: 6rem; }

/* ---------- Dashboard widgets (Users section) ---------- */
/* A strip across the page rather than a column beside the table — five stat
   cards stacked in a 280px rail were the reason the table had no room. */
.dashboard-panel {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: .85rem;
  align-items: start;
}
.widget-toggle-bar { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .2rem; grid-column: 1 / -1; }
.widget-toggle-chip {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  font-size: var(--fs-caption);
  color: var(--text-muted);
  background: var(--fill);
  border: none;
  border-radius: var(--radius-pill);
  padding: .28rem .7rem .28rem .5rem;
  cursor: pointer;
  user-select: none;
}
.widget-toggle-chip:hover { background: var(--fill-strong); }
.widget-toggle-chip input { width: .9rem; height: .9rem; margin: 0; cursor: pointer; accent-color: var(--accent); }
.widget-card {
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: .85rem 1rem;
  background: var(--surface);
}
.widget-card h3 {
  font-size: var(--fs-caption);
  font-weight: 590;
  color: var(--text-faint);
  margin-bottom: .4rem;
  letter-spacing: 0;
  text-transform: none;
}
/* Tabular figures so a column of numbers lines up digit for digit. */
.widget-stat {
  font-size: var(--fs-title2);
  font-weight: 660;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
.widget-list { display: flex; flex-direction: column; gap: .3rem; font-size: var(--fs-footnote); }
.widget-list-row { display: flex; justify-content: space-between; gap: .5rem; color: var(--text-muted); }
.widget-list-row strong { color: var(--text); font-weight: 510; }
.widget-empty { color: var(--text-faint); font-size: var(--fs-footnote); }

/* ---------- Tables ----------
   Hairline separators, no zebra. Alternating row fills fight the hover state
   and make a wide table read as a barcode; Apple's lists separate rows with a
   single inset rule and let the hover do the pointing. */
/* position: relative is load-bearing, not decoration. Every table carries a
   .visually-hidden <caption>, which is position:absolute; with a static
   wrapper its containing block was the page, so it escaped this scroll
   container and stretched the document to the full width of the table — the
   Users page scrolled sideways to 917px on a 390px phone while the table
   itself was correctly clipped. */
.table-wrap {
  position: relative;
  overflow-x: auto;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-soft);
  background: var(--surface);
}
table { width: 100%; border-collapse: collapse; font-size: var(--fs-footnote); }
caption { text-align: left; padding: .7rem .9rem; color: var(--text-faint); font-size: var(--fs-footnote); }
thead { background: transparent; }
thead th {
  padding: .6rem .9rem;
  text-align: left;
  font-weight: 510;
  color: var(--text-faint);
  text-transform: none;
  font-size: var(--fs-caption);
  letter-spacing: 0;
  white-space: nowrap;
  border-bottom: 1px solid var(--border-soft);
}
tbody tr { transition: background .12s var(--ease); }
tbody tr + tr { border-top: 1px solid var(--border-soft); }
tbody tr:hover { background: var(--fill); }
tbody td { padding: .6rem .9rem; color: var(--text); vertical-align: middle; font-variant-numeric: tabular-nums; }
tbody td.name, tbody td.strong { font-weight: 510; color: var(--text-strong); }
/* A table cut into groups: one <tbody> per group, each led by a full-width
   <th scope="rowgroup">. Client documents by month use it on the firm's side
   and in the portal, which is why it lives here and not in either page. */
tbody + tbody { border-top: 1px solid var(--border-soft); }
tr.group-row, tr.group-row:hover { background: var(--fill); }
tr.group-row th {
  padding: .45rem .9rem;
  text-align: left;
  font-size: var(--fs-caption);
  font-weight: 590;
  color: var(--text-strong);
}
tr.group-row th .muted { font-weight: 400; }
.muted { color: var(--text-faint); font-size: var(--fs-caption); }

.empty-state {
  text-align: center;
  padding: 3rem 1.5rem;
  color: var(--text-faint);
  font-size: var(--fs-subhead);
}
/* An empty state may lead with one of the shared icons. Sized here rather than
   in the page, because the Practice module has several of these -- no clients,
   no documents, nothing due -- and hand-sized copies would drift. The icon
   macro emits a bare 24x24 viewBox with no width/height, so without this it
   stretches to fill the card. */
.empty-state .icon { display: block; }
.empty-state .icon svg {
  width: 2.5rem;
  height: 2.5rem;
  margin: 0 auto .75rem;
  color: var(--text-faint);
}

/* ---------- Badges / alerts ---------- */
.badge {
  display: inline-block;
  border-radius: var(--radius-pill);
  font-size: var(--fs-caption2);
  font-weight: 590;
  padding: .18rem .55rem;
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
  border: none;
}
.badge-success { background: var(--success-bg); color: var(--success-text); }
.badge-warning { background: var(--warning-bg); color: var(--warning-text); }
.badge-neutral { background: var(--fill); color: var(--text-muted); }
.badge-danger { background: var(--error-bg); color: var(--error-text); }

/* A table cell holding more than one verdict — how the day started and how it
   ended. Badges are nowrap by design, so they need somewhere to wrap to. */
.status-stack { display: flex; flex-wrap: wrap; gap: .3rem; }

.alert {
  padding: .7rem .9rem;
  border-radius: var(--radius-md);
  font-size: var(--fs-footnote);
  font-weight: 450;
  margin-bottom: 1rem;
  border: none;
}
.alert-success { background: var(--success-bg); color: var(--success-text); }
.alert-error { background: var(--error-bg); color: var(--error-text); }
/* Saved, but worth a second look: leave past its allowance, a warning that
   reaches the limit. */
.alert-warning { background: var(--warning-bg); color: var(--warning-text); }

/* ---------- Attendance QR display ---------- */
.qr-card { max-width: 420px; text-align: center; margin: 0 auto; }
/* The QR stays on white in both schemes: scanners read contrast, and an
   inverted code is a support call, not a style choice. */
/* Block-level and self-centred, not inline-flex: as an inline box it shared a
   line with the status badge, which then sat in the QR's bottom-right corner. */
.qr-frame {
  display: flex;
  width: max-content;
  margin: 0 auto;
  padding: 14px;
  border-radius: var(--radius-lg);
  background: #ffffff;
  box-shadow: var(--shadow-md);
}
.qr-frame img { width: 240px; height: 240px; display: block; }
.qr-url-box {
  margin-top: 1.1rem;
  background: var(--fill);
  border: none;
  border-radius: var(--radius-sm);
  padding: .6rem .75rem;
  font-size: var(--fs-caption2);
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
  color: var(--text-muted);
  word-break: break-all;
}
.qr-actions { display: flex; gap: .6rem; margin-top: 1.25rem; justify-content: center; }
.qr-note { max-width: 420px; margin: 1.25rem auto 0; }

/* ---------- Auth pages (no sidebar) ---------- */
.auth-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: var(--bg);
}
.auth-card {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-xl);
  padding: 2rem 1.75rem;
  width: 100%;
  max-width: 360px;
  box-shadow: var(--shadow-md);
}
.auth-logo {
  color: var(--on-accent);
  margin: 0 auto .8rem;
  width: 3rem;
  height: 3rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-lg);
  background: var(--accent);
}
.auth-logo svg { width: 1.7rem; height: 1.7rem; }
.auth-card h1 {
  text-align: center;
  font-size: var(--fs-title2);
  font-weight: 680;
  color: var(--text-strong);
  letter-spacing: -.022em;
}
.auth-card .sub { text-align: center; color: var(--text-faint); font-size: var(--fs-footnote); margin: .3rem 0 1.5rem; }
.auth-hint {
  margin-top: 1.1rem;
  text-align: center;
  font-size: var(--fs-caption);
  color: var(--text-faint);
  line-height: 1.45;
}
.auth-hint a { color: var(--accent); font-weight: 510; }

/* The "or" rule between the password form and the passkey button. A bare
   horizontal line reads as the end of the card; the word is what says the two
   are alternatives. */
.auth-or {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: 1.1rem 0;
  font-size: var(--fs-caption);
  color: var(--text-faint);
}
.auth-or::before,
.auth-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-soft);
}

/* What the passkey ceremony has to say for itself, on the login page and on
   the passkeys page both -- which is why it is here rather than in either
   page's own head block. */
.passkey-status {
  font-size: var(--fs-caption);
  color: var(--text-faint);
  line-height: 1.45;
  margin-top: .6rem;
}
.passkey-status-error { color: var(--error-text); }
/* "This device is signed up as <name>" on the forgot-password page. */
.auth-confirm {
  background: var(--fill);
  border-radius: var(--radius-md);
  padding: .7rem .8rem;
  margin-bottom: .9rem;
  text-align: center;
  font-size: var(--fs-footnote);
  color: var(--text);
}
.auth-confirm strong { color: var(--text-strong); }
/* Rule with a word sitting on it, between the two ways into the same form. */
.auth-divider {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: 1.2rem 0 .9rem;
  font-size: var(--fs-caption);
  color: var(--text-faint);
}
.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border-soft);
}

/* ---------- Check-in identity strip ---------- */
.checkin-identity {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  background: var(--fill);
  border: none;
  border-radius: var(--radius-md);
  padding: .6rem .75rem;
  margin-bottom: 1rem;
  font-size: var(--fs-footnote);
  color: var(--text-muted);
}
.checkin-identity strong { color: var(--text-strong); font-weight: 590; }

.link-btn {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  font-size: var(--fs-footnote);
  font-weight: 510;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.link-btn:hover { text-decoration: underline; }

/* ---------- Check-in result panel ---------- */
.result-panel {
  margin-top: 1.25rem;
  padding: 1.5rem 1.25rem;
  border-radius: var(--radius-lg);
  text-align: center;
  border: none;
  background: var(--fill);
}
.result-panel-success { background: var(--success-bg); }
.result-panel-late { background: var(--warning-bg); }
.result-icon { display: flex; justify-content: center; margin-bottom: .7rem; }
.result-heading {
  font-size: var(--fs-title3);
  font-weight: 620;
  color: var(--text-strong);
  margin-bottom: .25rem;
  letter-spacing: -.02em;
}
.result-sub { font-size: var(--fs-footnote); color: var(--text-muted); }

/* Animated checkmark (SVG circle + check path draw-in) */
.checkmark { width: 64px; height: 64px; stroke: var(--success-text); }
.checkmark-circle {
  stroke: var(--success-text);
  stroke-width: 2.5;
  stroke-dasharray: 145;
  stroke-dashoffset: 0;
}
.checkmark-check {
  stroke: var(--success-text);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 32;
  stroke-dashoffset: 0;
}

@media (prefers-reduced-motion: no-preference) {
  @keyframes checkmark-circle-draw {
    from { stroke-dashoffset: 145; }
    to   { stroke-dashoffset: 0; }
  }
  @keyframes checkmark-check-draw {
    from { stroke-dashoffset: 32; }
    to   { stroke-dashoffset: 0; }
  }
  .checkmark-circle { stroke-dashoffset: 145; animation: checkmark-circle-draw .5s var(--ease) forwards; }
  .checkmark-check { stroke-dashoffset: 32; animation: checkmark-check-draw .35s var(--ease) .5s forwards; }
}

/* Amber "late" clock icon — distinct visual treatment, same success=true state */
.late-icon { width: 56px; height: 56px; color: var(--warning-text); }

/* ---------- Order board ----------
   Shared by the board page and the full-screen shop display, which is why it
   is here rather than in either template's head: one order card, drawn the
   same way in both, and the same materials list under it. */

/* A segmented picker for the branch and the day, same idea as the Settings
   branch chips: the current one is filled with the accent, the rest sit quiet
   on a neutral fill. */
.board-chips, .board-daynav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem;
  margin-bottom: .9rem;
}
.board-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.2rem;
  padding: .35rem .8rem;
  border-radius: var(--radius-pill);
  background: var(--fill);
  color: var(--text-muted);
  font-size: var(--fs-footnote);
  font-weight: 510;
  text-decoration: none;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.board-chip:hover { color: var(--text-strong); background: var(--fill-strong); }
.board-chip.on { background: var(--accent); color: var(--on-accent); }
.board-chip.on:hover { background: var(--accent-hover); color: var(--on-accent); }

.board-jump { display: flex; align-items: center; gap: .4rem; margin-left: auto; }
.board-jump input[type=date] { width: auto; }

.board-new { margin-bottom: 1.25rem; }

/* The split. --board-split is the left column's share of the row and the
   divider is absolutely positioned on the seam between the two tracks, so the
   panes always add up to exactly 100% however far the divider is dragged --
   a fixed divider track would push the row past the viewport at the extremes
   and scroll the whole page sideways. At 0% or 100% one pane is a zero-width
   track and clips its own content, which is the "drag it out to a full page"
   end state. */
.board-split {
  --board-split: 50%;
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, var(--board-split)) minmax(0, 1fr);
  align-items: start;
  margin-bottom: 1.5rem;
}
.board-pane {
  min-width: 0;
  overflow: hidden;
  padding: 0 .6rem;
}
.board-pane:first-of-type { padding-left: 0; }
.board-pane:last-of-type { padding-right: 0; }

.board-pane-head {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding-bottom: .5rem;
  margin-bottom: .75rem;
  border-bottom: 1px solid var(--border-soft);
}
.board-pane-head h2 {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-callout);
  font-weight: 620;
  color: var(--text-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.board-divider {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--board-split);
  width: 1.2rem;
  margin-left: -.6rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: col-resize;
  touch-action: none;
  border-radius: var(--radius-pill);
}
.board-divider-grip {
  width: 2px;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--border-soft);
  transition: background var(--dur) var(--ease), width var(--dur) var(--ease);
}
.board-divider:hover .board-divider-grip,
.board-divider:focus-visible .board-divider-grip,
.board-split.is-dragging .board-divider-grip {
  background: var(--accent);
  width: 3px;
}
/* Nothing to grab hold of when a pane is collapsed to nothing, so the handle
   stays visible at the very edge instead of sitting half outside the page. */
.board-split.is-right-only .board-divider { margin-left: 0; }
.board-split.is-left-only .board-divider { margin-left: -1.2rem; }
/* A collapsed pane still renders its own padding -- border-box floors the box
   there even on a zero-width track -- which leaves a 10px sliver beside the
   column that is supposed to have the whole page. Take the padding with it. */
.board-split.is-left-only .board-pane:last-of-type,
.board-split.is-right-only .board-pane:first-of-type { padding: 0; }

.board-empty {
  padding: 1.5rem .5rem;
  color: var(--text-faint);
  font-size: var(--fs-footnote);
}

/* ---------- One order ---------- */
.order-card {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: .85rem;
  margin-bottom: .7rem;
  box-shadow: var(--shadow-sm);
}
.order-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
}
.order-no {
  font-size: var(--fs-callout);
  font-weight: 620;
  color: var(--text-strong);
  word-break: break-word;
}
/* The slot is what the counter scans for, so it is the one tinted thing on
   the card -- and tabular figures keep a column of times aligned. */
.order-time {
  flex-shrink: 0;
  padding: .15rem .5rem;
  border-radius: var(--radius-pill);
  background: rgb(var(--accent-rgb) / .12);
  color: var(--accent);
  font-size: var(--fs-caption);
  font-weight: 590;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.order-time-unset { background: var(--fill); color: var(--text-faint); }
.order-customer { font-size: var(--fs-footnote); color: var(--text-muted); margin-top: .15rem; }
.order-customer a { color: var(--accent); text-decoration: none; }
.order-customer a:hover { text-decoration: underline; }

.order-photos { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .6rem; }
.order-photo {
  position: relative;
  flex: 1 1 8rem;
  min-width: 0;
  display: block;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--fill);
}
.order-photo img {
  display: block;
  width: 100%;
  height: 8.5rem;
  object-fit: cover;
}
.order-photo-tag {
  position: absolute;
  left: .35rem;
  bottom: .35rem;
  padding: .1rem .4rem;
  border-radius: var(--radius-sm);
  /* Its own ink, not a token: this label sits on a photograph, where the page
     palette has no idea what is underneath it. */
  background: rgb(0 0 0 / .58);
  color: #ffffff;
  font-size: var(--fs-caption2);
  font-weight: 510;
}

.order-notes {
  margin-top: .6rem;
  font-size: var(--fs-footnote);
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.order-materials {
  margin-top: .5rem;
  padding: .45rem .55rem;
  border-radius: var(--radius-sm);
  background: var(--fill);
  font-size: var(--fs-footnote);
  color: var(--text-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.order-label {
  display: block;
  font-size: var(--fs-caption2);
  font-weight: 590;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-faint);
}
.order-card-foot { margin-top: .55rem; }

/* The current photo beside its replace field, on the edit form. */
.order-photo-current { margin-top: .5rem; }
.order-photo-current img {
  display: block;
  max-width: 10rem;
  border-radius: var(--radius-md);
  margin-bottom: .4rem;
}

/* ---------- Tomorrow's materials ---------- */
.materials-panel {
  border: 1px solid rgb(var(--accent-rgb) / .3);
  border-radius: var(--radius-lg);
  background: rgb(var(--accent-rgb) / .07);
  padding: .8rem;
  margin-bottom: .9rem;
}
.materials-panel h3 {
  font-size: var(--fs-caption);
  font-weight: 620;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--accent);
  margin-bottom: .5rem;
}
.materials-list { list-style: none; display: flex; flex-direction: column; gap: .45rem; }
.materials-list li { display: flex; flex-direction: column; gap: .1rem; }
.materials-list li + li { border-top: 1px solid var(--border-soft); padding-top: .45rem; }
.materials-ref {
  font-size: var(--fs-caption);
  font-weight: 590;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.materials-text { font-size: var(--fs-footnote); color: var(--text); white-space: pre-wrap; overflow-wrap: anywhere; }

.board-later { margin-bottom: 1.5rem; }

/* ---------- Full-screen shop display ---------- */
/* Read from across the room: bigger type, no chrome, and the two columns
   fixed rather than draggable -- nobody is standing at this screen. */
.board-display {
  min-height: 100vh;
  background: var(--bg);
  padding: 1rem 1.25rem 1.5rem;
}
.board-display-bar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: .75rem;
  margin-bottom: 1rem;
  border-bottom: 1px solid var(--border-soft);
}
.board-display-bar h1 {
  font-size: var(--fs-title1);
  font-weight: 680;
  color: var(--text-strong);
}
.board-display-branch {
  font-size: var(--fs-footnote);
  font-weight: 590;
  color: var(--text-faint);
}
.board-display-cols {
  display: grid;
  grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr);
  gap: 1.5rem;
  align-items: start;
}
.board-display-col { min-width: 0; }
.board-display-col .board-pane-head h2 { font-size: var(--fs-title3); }
.board-display-col .order-no { font-size: var(--fs-title3); }
.board-display-col .order-time { font-size: var(--fs-subhead); }
.board-display-col .order-notes { font-size: var(--fs-subhead); }
.board-display-col .order-photo img { height: 11rem; }
.board-display-materials .materials-list-lg .materials-text { font-size: var(--fs-callout); }
.board-display-materials .materials-list-lg .materials-ref { font-size: var(--fs-footnote); }
.board-display-materials {
  border-radius: var(--radius-xl);
  background: var(--surface);
  border: 1px solid var(--border-soft);
  padding: 1rem;
}
.board-display-note {
  margin-top: .9rem;
  font-size: var(--fs-footnote);
  color: var(--warning-text);
  background: var(--warning-bg);
  border-radius: var(--radius-sm);
  padding: .5rem .6rem;
}

@media (max-width: 860px) {
  /* A phone gets one column after the other. The divider has nothing to
     resize once the panes stack, so it goes away with them. */
  .board-split { grid-template-columns: minmax(0, 1fr); }
  .board-pane { overflow: visible; padding: 0; }
  .board-pane + .board-pane { margin-top: 1.5rem; }
  .board-divider { display: none; }
  .board-jump { margin-left: 0; }
  .board-display-cols { grid-template-columns: minmax(0, 1fr); gap: 1rem; }
}

/* ---------- Responsive ---------- */
@media (max-width: 860px) {
  .sidebar { transform: translateX(-100%); width: min(var(--sidebar-w), 82vw); box-shadow: var(--shadow-lg); }
  body.sidebar-open .sidebar { transform: translateX(0); }
  body.sidebar-open .sidebar-backdrop { display: block; }
  .main { margin-left: 0; }
  .sidebar-toggle { display: flex; }
  .content { padding: 1.25rem 1rem 2.5rem; }
}

@media (max-width: 600px) {
  .topbar { padding: 0 .5rem; gap: .35rem; }
  /* The account cluster is five controls wide on desktop and does not fit a
     390px phone beside a title and a menu button — at 44px touch targets it
     overflowed the viewport. Keep identity (avatar) and the two actions that
     have no other home; the Owner shortcut is a link to a page the menu
     already lists. */
  .username-chip .role-pill { display: none; }
  .username-chip > span { display: none; }
  .username-chip { padding: 0; gap: 0; }
  .admin-badge { display: none; }
  .module-tabs { padding: .125rem; }
  .module-tab { padding: .3rem .55rem; }
  /* Menu button + three module tabs + the account cluster came to ~444px,
     so on a phone the switcher moves into the menu and the title gets the
     room back. */
  .topbar .module-tabs { display: none; }
  .module-tabs-drawer { display: flex; }
  .content { padding: 1rem .9rem 2.5rem; }
  .page-header { margin-bottom: 1.15rem; }
  .page-header h1 { font-size: var(--fs-title2); }
  .card { padding: 1rem; }
  .header-actions { width: 100%; }
  .header-actions .btn { flex: 1; }
}

/* Touch targets reach Apple's 44pt minimum on phones and any coarse pointer. */
@media (max-width: 600px), (pointer: coarse) {
  .btn { min-height: 2.75rem; padding: .65rem 1.1rem; }
  .btn-sm { min-height: 2.4rem; padding: .5rem .9rem; }
  .icon-btn, .sidebar-toggle, .admin-badge { width: 2.75rem; height: 2.75rem; }
  .checkbox-row { padding: .6rem .55rem; }
  .checkbox-row input[type=checkbox], .checkbox-row input[type=radio] { width: 1.35rem; height: 1.35rem; }
  .nav-link { padding: .65rem .55rem; }
  .link-btn { padding: .3rem 0; }
}

/* One switch turns off every transition and animation in the app. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- Kanban: the vocabulary its five pages share ----------
   Priority, a due date and a card's metadata line appear on the board, the
   task, the ticket list, the ticket and the reminders page. Copying them into
   five head blocks is exactly how two pages' input styling drifted apart, so
   they are defined once here; the board's own column layout, which only the
   board has, stays in its template.

   Priority is the one place this section leans on the semantic tokens rather
   than the accent: urgent and high are a state to act on, which is what
   --error and --warning already mean everywhere else in the app. Low and
   normal are deliberately neutral -- if every card shouted, none would. */
.k-pill {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  flex-shrink: 0;
  padding: .12rem .5rem;
  border-radius: var(--radius-pill);
  font-size: var(--fs-caption2);
  font-weight: 590;
  white-space: nowrap;
}
.k-pill-urgent { background: var(--error-bg); color: var(--error-text); }
.k-pill-high { background: var(--warning-bg); color: var(--warning-text); }
.k-pill-normal { background: var(--fill); color: var(--text-muted); }
.k-pill-low { background: var(--fill); color: var(--text-faint); }
.k-pill-accent { background: rgb(var(--accent-rgb) / .12); color: var(--accent); }
.k-pill-done { background: var(--success-bg); color: var(--success-text); }

/* Ticket status, on the list and on the ticket itself. Open wears the warning
   tint because it is the one state that owes somebody an answer; in-progress
   takes the accent (something is happening); waiting is neutral because the
   ball is not in the shop's court; resolved and closed are the success tone. */
.k-status-open { background: var(--warning-bg); color: var(--warning-text); }
.k-status-in_progress { background: rgb(var(--accent-rgb) / .12); color: var(--accent); }
.k-status-waiting { background: var(--fill); color: var(--text-muted); }
.k-status-resolved, .k-status-closed { background: var(--success-bg); color: var(--success-text); }

.k-due {
  font-size: var(--fs-caption);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.k-due-over { color: var(--error-text); font-weight: 590; }

.k-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem;
  margin-top: .5rem;
  font-size: var(--fs-caption);
  color: var(--text-faint);
}
.k-who {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .12rem .5rem .12rem .2rem;
  border-radius: var(--radius-pill);
  background: var(--fill);
  color: var(--text-muted);
  font-size: var(--fs-caption2);
}
.k-who .avatar {
  width: 1.1rem;
  height: 1.1rem;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--on-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-caption2);
  font-weight: 620;
  text-transform: uppercase;
}
.k-who-none { background: transparent; color: var(--text-faint); font-style: italic; }

/* The thread both the task and the ticket page carry. One shape, so a comment
   on a card and a reply on a ticket read as the same kind of thing. */
.k-thread { display: flex; flex-direction: column; gap: .6rem; }
.k-note {
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: .6rem .7rem;
  background: var(--surface);
}
.k-note-internal { background: var(--fill); border-style: dashed; }
.k-note-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .4rem;
  font-size: var(--fs-caption);
  color: var(--text-faint);
  margin-bottom: .25rem;
}
.k-note-head strong { color: var(--text-strong); font-weight: 590; font-size: var(--fs-footnote); }
.k-note-body { font-size: var(--fs-footnote); color: var(--text); white-space: pre-wrap; overflow-wrap: anywhere; }

/* One reminder, on the Reminders page and under a task or ticket. */
.k-reminder {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  padding: .55rem .6rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-soft);
}
.k-reminder + .k-reminder { margin-top: .4rem; }
.k-reminder-due { border-color: rgb(var(--warning-rgb) / .45); background: var(--warning-bg); }
.k-reminder-done { opacity: .6; }
.k-reminder-main { flex: 1; min-width: 10rem; }
.k-reminder-main strong { display: block; font-size: var(--fs-subhead); color: var(--text-strong); font-weight: 510; }
.k-reminder-main span { font-size: var(--fs-caption); color: var(--text-muted); }
.k-reminder-actions { display: flex; flex-wrap: wrap; gap: .3rem; align-items: center; }
.k-reminder-actions form { display: inline-flex; }

/* A row of filter controls that wraps rather than pushing the page sideways. */
.k-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: .5rem;
  margin-bottom: 1rem;
}
.k-toolbar .field { margin-bottom: 0; }
.k-toolbar select, .k-toolbar input { width: auto; min-width: 8rem; }

/* The owner's cross-branch summary on Home. Deliberately plain: it is a
   read-only roll-up, and anything that looked like a control here would be
   promising an action this page does not have. */
.branch-rollup-head { margin-bottom: .85rem; }
.branch-rollup-head .hint { margin: .15rem 0 0; }
.branch-rollup tr.is-active-branch { background: rgb(var(--accent-rgb) / .06); }
.branch-rollup tr.is-active-branch .name { font-weight: 700; }

/* ---------- Cash counter ---------- */
/* One row per note: the label, how many, and what that comes to. A single
   column on purpose -- people count the whole drawer first and take notes out
   after, so the count and the take-out are two stacked steps rather than one
   wide grid whose tab order would run across them. */
.cash-step { margin-top: 1.25rem; }
.cash-step:first-child { margin-top: 0; }
.cash-step-title { font-size: var(--fs-subhead); font-weight: 620; color: var(--text-strong); margin: 0 0 .2rem; }
.cash-step-hint { font-size: var(--fs-caption); color: var(--text-faint); margin: 0 0 .6rem; }
.cash-rows { border-top: 1px solid var(--border-soft); }
.cash-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 5.75rem minmax(4.5rem, auto);
  align-items: center;
  gap: .6rem;
  padding: .45rem 0;
  border-bottom: 1px solid var(--border-soft);
}
.cash-row-label { font-size: var(--fs-subhead); color: var(--text-strong); font-weight: 510; }
.cash-row-label small { display: block; font-size: var(--fs-caption); color: var(--text-faint); font-weight: 400; }
.cash-row input { text-align: right; font-variant-numeric: tabular-nums; }
.cash-row-amount { text-align: right; color: var(--text-muted); font-size: var(--fs-footnote); font-variant-numeric: tabular-nums; white-space: nowrap; }
.cash-row-of { display: flex; align-items: center; justify-content: flex-end; gap: .4rem; font-size: var(--fs-caption); color: var(--text-faint); white-space: nowrap; }
.cash-row.is-empty { display: none; }
.cash-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: .75rem;
  flex-wrap: wrap;
  margin-top: .7rem;
  font-size: var(--fs-subhead);
  color: var(--text-muted);
}
.cash-total strong { font-size: var(--fs-title3); color: var(--text-strong); font-variant-numeric: tabular-nums; }

/* The result read top to bottom, like a cash book: from last night, plus
   sales, minus money out, equals what it should be -- then what was there.
   It reads on a phone without a table, and answers "why short?" by itself. */
.cash-book { margin: 0; }
.cash-book-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: .45rem 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: var(--fs-subhead);
}
.cash-book-row dt { color: var(--text-muted); min-width: 0; }
.cash-book-row dt small { display: block; font-size: var(--fs-caption); color: var(--text-faint); }
.cash-book-row dd { margin: 0; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--text-strong); }
.cash-book-row.is-total { border-bottom: 2px solid var(--border); }
.cash-book-row.is-total dt, .cash-book-row.is-total dd { font-weight: 620; color: var(--text-strong); }
.cash-book-row.is-sub dt { padding-left: 1rem; font-size: var(--fs-footnote); }
.cash-book-row.is-sub dd { font-size: var(--fs-footnote); color: var(--text-muted); }

.cash-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }
.cash-list { list-style: none; margin: 0; padding: 0; }
.cash-list li {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: .75rem;
  padding: .55rem 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: var(--fs-footnote);
}
.cash-list li:last-child { border-bottom: none; }
.cash-list .cash-list-main { min-width: 0; }
.cash-list .cash-list-main strong { color: var(--text-strong); font-weight: 510; }
.cash-list .cash-list-sub { display: block; font-size: var(--fs-caption); color: var(--text-faint); }
.cash-list .cash-list-amount { font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--text-strong); font-weight: 590; }
.cash-list li.is-cancelled .cash-list-main strong,
.cash-list li.is-cancelled .cash-list-amount { text-decoration: line-through; color: var(--text-faint); }
.cash-choices { display: flex; flex-wrap: wrap; gap: .4rem; margin: 0 0 .75rem; border: 0; padding: 0; }
.cash-choices label { display: inline-flex; }
.cash-choices input { position: absolute; opacity: 0; pointer-events: none; }
.cash-choices span {
  display: inline-block;
  padding: .45rem .8rem;
  border-radius: var(--radius-pill);
  background: var(--fill);
  color: var(--text-muted);
  font-size: var(--fs-footnote);
  cursor: pointer;
  border: 1px solid transparent;
}
.cash-choices input:checked + span { background: rgb(var(--accent-rgb) / .14); color: var(--accent); border-color: rgb(var(--accent-rgb) / .45); font-weight: 590; }
.cash-choices input:focus-visible + span { box-shadow: var(--focus-ring); }
.cash-denoms { font-size: var(--fs-caption); }
.cash-denoms td, .cash-denoms th { padding: .4rem .6rem; text-align: right; }
.cash-denoms td:first-child, .cash-denoms th:first-child { text-align: left; }
.cash-denoms tfoot th, .cash-denoms tfoot td { border-top: 1px solid var(--border); font-weight: 590; color: var(--text-strong); }
.cash-denoms .cash-denoms-out td { text-align: left; font-weight: 450; color: var(--text-muted); white-space: normal; }

/* Today, as the day runs: opening cash, morning count, money in and out, the
   close, the POS figure. A dot per step -- a tick when done, the step's
   number while it is to do, dashed for later -- so the page says what is
   next before anybody reads a figure. */
.cash-alert { text-decoration: none; margin-bottom: .75rem; }
.cash-day { list-style: none; margin: 0; padding: 0; }
.cash-day-step {
  display: grid;
  grid-template-columns: 1.6rem minmax(0, 1fr);
  gap: .7rem;
  padding: .8rem 0;
  border-bottom: 1px solid var(--border-soft);
}
.cash-day-step:first-child { padding-top: 0; }
.cash-day-step:last-child { border-bottom: none; padding-bottom: 0; }
.cash-day-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  color: var(--text-faint);
  font-size: var(--fs-caption);
  font-weight: 620;
  font-variant-numeric: tabular-nums;
}
.cash-day-dot svg { width: 1rem; height: 1rem; }
.is-done > .cash-day-dot { background: var(--success-bg); border-color: transparent; color: var(--success-text); }
.is-todo > .cash-day-dot { border-color: var(--accent); color: var(--accent); }
.is-open > .cash-day-dot { background: rgb(var(--accent-rgb) / .14); border-color: transparent; color: var(--accent); }
.is-later > .cash-day-dot, .is-skipped > .cash-day-dot { border-style: dashed; }
.cash-day-body { min-width: 0; }
.cash-day-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .2rem .75rem;
  margin: .15rem 0 0;
}
.cash-day-name { font-size: var(--fs-subhead); font-weight: 590; color: var(--text-strong); }
.is-later .cash-day-name, .is-skipped .cash-day-name { color: var(--text-muted); font-weight: 510; }
.cash-day-figure {
  display: inline-flex;
  align-items: baseline;
  gap: .4rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--text-strong);
  font-weight: 590;
}
.cash-day-sub { margin: .15rem 0 0; font-size: var(--fs-footnote); color: var(--text-muted); }
.card-heading + .cash-day-sub { margin: -.7rem 0 .9rem; }
.cash-day-warn { color: var(--warning-text); }
.cash-day-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .6rem; }
.cash-day-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: .75rem;
  margin: .7rem 0 0;
  padding-top: .55rem;
  border-top: 1px solid var(--border-soft);
  font-size: var(--fs-footnote);
  color: var(--text-muted);
}
.cash-day-total strong { color: var(--text-strong); font-variant-numeric: tabular-nums; white-space: nowrap; }
.cash-day-form { margin-top: .6rem; }
.cash-day-step .correct-cell { margin-top: .4rem; }
h2.cash-day-title { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }

.cash-for { margin: 0 0 1rem; font-size: var(--fs-subhead); color: var(--text-muted); }
.cash-for strong { color: var(--text-strong); }

/* The close's two screens, marked above the heading: count and lock first,
   then the POS figure. */
.cash-progress { list-style: none; display: flex; flex-wrap: wrap; gap: .4rem; margin: 0 0 .6rem; padding: 0; font-size: var(--fs-caption); }
.cash-progress li {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .2rem .7rem .2rem .25rem;
  border-radius: var(--radius-pill);
  background: var(--fill);
  color: var(--text-muted);
}
.cash-progress li > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.2rem;
  height: 1.2rem;
  border-radius: 50%;
  background: var(--surface);
  font-weight: 620;
}
.cash-progress li.is-current { background: rgb(var(--accent-rgb) / .14); color: var(--accent); font-weight: 590; }
.cash-progress li.is-current > span { background: var(--accent); color: var(--on-accent); }
.cash-progress li.is-done > span { background: var(--success-bg); color: var(--success-text); }

/* A day's verdict, first on its record: the result in its badge's colour,
   the one line that says why, then the note if it needs one. */
.cash-result-verdict { margin: 0; font-size: var(--fs-title2); font-weight: 680; color: var(--text-strong); font-variant-numeric: tabular-nums; }
.cash-result.is-success .cash-result-verdict { color: var(--success-text); }
.cash-result.is-warning .cash-result-verdict { color: var(--warning-text); }
.cash-result.is-danger .cash-result-verdict { color: var(--error-text); }
.cash-result-line { margin: .25rem 0 0; font-size: var(--fs-subhead); color: var(--text-muted); }
.cash-result .status-stack { margin-top: .7rem; }
.cash-result-note { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border-soft); }

/* The money form: the amount first and large, then the choices two-up on a
   phone rather than pills wrapping ragged across four lines. */
.cash-amount input { font-size: var(--fs-title3); font-weight: 590; font-variant-numeric: tabular-nums; }
.cash-choices-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); }
.cash-choices-grid label { display: flex; }
.cash-choices-grid span {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  text-align: center;
  padding: .6rem;
}
.cash-tip {
  margin: -.25rem 0 .9rem;
  padding: .55rem .7rem;
  border-radius: var(--radius-sm);
  background: var(--warning-bg);
  color: var(--warning-text);
  font-size: var(--fs-footnote);
}

/* ---------- One person's month ---------- */
/* The streak and the calendar (attendance_month.py, _month_calendar.html).
   Shared by Records, where staff read their own month, and Employee Status,
   where a manager reads somebody else's. Side by side where there is room,
   stacked on a phone. */
.am { display: grid; gap: 1.25rem 2.5rem; margin-bottom: 1.5rem; }
@media (min-width: 1000px) {
  .am { grid-template-columns: minmax(0, 1fr) minmax(0, 23rem); align-items: start; }
}
.am .card-heading { margin-bottom: .75rem; }
/* The streak is the one big number. The flame is lit in celebrate -- a
   flame's own orange -- while a streak runs and grey at nought, so the same
   figure says "keep it" or "start again" before a word is read. */
.am-score { display: flex; gap: 1.1rem; align-items: center; }
.am-flame {
  flex: none; width: 4.75rem; height: 4.75rem; border-radius: var(--radius-pill);
  display: grid; place-items: center;
  background: var(--fill); color: var(--text-faint);
}
.am-flame.is-lit { background: var(--celebrate-bg); color: var(--celebrate); }
.am-flame svg { width: 2.6rem; height: 2.6rem; }
.am-hero-value { display: block; font-size: 3rem; font-weight: 700; line-height: 1; letter-spacing: -.03em; color: var(--text-strong); }
.am-hero-label { display: block; margin-top: .25rem; font-weight: 600; color: var(--text-strong); }
.am-line { margin-top: .3rem; font-size: var(--fs-footnote); color: var(--text-muted); }

.am-weekdays, .am-days {
  list-style: none; display: grid; gap: 4px;
  grid-template-columns: repeat(7, minmax(0, 1fr));
}
.am-weekdays { margin-bottom: .3rem; font-size: var(--fs-caption2); color: var(--text-faint); text-align: center; }
.am-day {
  position: relative; aspect-ratio: 1; border-radius: var(--radius-sm);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .15rem;
  font-size: var(--fs-caption); font-weight: 590; line-height: 1; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.am-day svg { width: .9rem; height: .9rem; }
.am-day[data-detail] { cursor: pointer; }
.am-day[data-detail]:hover, .am-day.is-picked { outline: 2px solid var(--text-faint); outline-offset: 1px; }
/* Today wears the accent ring whatever kind of day it turns out to be.
   Days still to come, or before the first one that counts, are bare
   numbers: there is nothing to say about them yet. */
.am-day.is-now { box-shadow: inset 0 0 0 2px var(--accent); }
.am-day.k-off, .am-day.k-before, .am-day.k-future, .am-day.k-today { color: var(--text-faint); }
.am-readout { margin-top: .6rem; min-height: 1.4em; font-size: var(--fs-footnote); color: var(--text-muted); }

/* One colour per verdict, shared by the days and the legend's keys. Status
   colours, the same three the records table's badges use, and every one
   with its own glyph -- so the day reads by shape too, not colour alone. */
.am-swatch.k-on_time { background: rgb(var(--success-rgb) / .16); }
.am-swatch.k-late, .am-swatch.k-early { background: rgb(var(--warning-rgb) / .16); }
.am-swatch.k-half, .am-swatch.k-absent { background: rgb(var(--error-rgb) / .14); }
.am-swatch.k-off, .am-swatch.k-extra { background: var(--fill); }
.am-swatch.k-on_time svg, .am-swatch.k-extra svg { color: var(--success); }
.am-swatch.k-late svg, .am-swatch.k-early svg { color: var(--warning); }
.am-swatch.k-half svg, .am-swatch.k-absent svg { color: var(--error); }

.am-legend {
  list-style: none; display: flex; flex-wrap: wrap; gap: .45rem 1rem; margin-top: .75rem;
  font-size: var(--fs-footnote); color: var(--text-muted);
}
.am-legend li { display: inline-flex; align-items: center; gap: .35rem; }
.am-legend strong { color: var(--text-strong); font-weight: 620; }
.am-key { flex: none; width: 1.25rem; height: 1.25rem; border-radius: .35rem; display: grid; place-items: center; }
.am-key svg { width: .85rem; height: .85rem; }
