/* Smart DeonTech — design tokens
   Palette: ink #0B1220, paper #F3F6FC, primary royal blue #9E1B1B, accent sky #0EA5E9, black #0B1220, white #FFFFFF
   Type: Bricolage Grotesque (display/headings), Plus Jakarta Sans (body/UI), IBM Plex Mono (codes/numbers)
*/
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700;800&family=Geist+Mono:wght@500;600&display=swap');

/* Stop iOS/Safari from auto-inflating text when it thinks a layout is wide. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

/* Scroll container for matrix/grid tables (KPI entry, attendance grids) so
   they scroll sideways on phones regardless of the card's overflow. */
.rt-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; max-width: 100%; }

:root {
  --ink: #0B1220;
  --paper: #FBF7F6;
  --panel: #FFFFFF;
  --primary: #9E1B1B;   /* GAS crest maroon */
  --primary-dark: #6E0F0F;
  --primary-light: #C24343;
  --accent: #C24343;
  --gas-gold: #C8992E;  /* accent for highlights */
  --gas-green: #00734A; /* positive / paid state */
  --primary-gradient: linear-gradient(135deg, #6E0F0F 0%, #9E1B1B 55%, #C24343 100%);
  --amber: #C8992E;
  --red: #D6362B;
  --rule: #EDE3E3;
  --muted: #6B5F5F;
  --radius: 12px;
  --shadow-sm: 0 1px 2px rgba(11, 18, 32, 0.06);
  --shadow-md: 0 8px 24px rgba(66, 11, 11, 0.10);
  --shadow-glow: 0 0 0 4px rgba(158, 27, 27, 0.14);
  --hover-surface: #FDF6F5;
  --toast-bg: #0B1220;
}

/* ---- Dark theme ----
   Toggled by app.js, which sets data-theme="dark" on <html> automatically
   in the evening (default 7 PM–6 AM local time) or when the user picks
   "Dark" from the sidebar theme toggle. */
html[data-theme="dark"] {
  --ink: #F2E9E9;
  --paper: #171012;
  --panel: #241A1C;
  --primary-light: #E08383;
  --rule: #3A2A2C;
  --muted: #BBA6A6;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.45);
  --hover-surface: #1A2438;
  --toast-bg: #E7ECF5;
}
html[data-theme="dark"] body { color-scheme: dark; }
html[data-theme="dark"] input,
html[data-theme="dark"] select,
html[data-theme="dark"] textarea { background: var(--panel); }
html[data-theme="dark"] .modal,
html[data-theme="dark"] .sheet,
html[data-theme="dark"] .tabbar { background: var(--panel); }
html[data-theme="dark"] .login-card { background: rgba(36, 26, 28, 0.98); }
html[data-theme="dark"] .toast { color: #171012; }
/* Filter chips, the cash/accrual toggle, item thumbnails, and the
   secondary/danger button hover states all had a hardcoded LIGHT
   background paired with text that uses --ink/--muted (which flip to a
   light color in dark mode) — that combination goes light-text-on-light-
   background and disappears. Overriding just the backgrounds here fixes it
   without touching the light-theme look at all. */
html[data-theme="dark"] .chip:not(.active) { background: var(--panel); }
html[data-theme="dark"] .basis-toggle { background: var(--rule); }
html[data-theme="dark"] .basis-toggle button.active { background: var(--panel); }
html[data-theme="dark"] .item-thumb,
html[data-theme="dark"] .item-thumb-placeholder { background: var(--panel); }
html[data-theme="dark"] .btn-secondary:hover { background: var(--hover-surface); border-color: var(--primary-light); }
html[data-theme="dark"] .btn-danger { background: var(--panel); }
html[data-theme="dark"] .btn-danger:hover { background: rgba(214, 54, 43, 0.18); }
/* Same issue as above, found 2026-07-10: the dashboard's Total Receivables
   and Overdue Invoices cards, plus disabled form fields, all had a
   hardcoded light background paired with --ink/--muted text — the figures
   were washing out in dark mode. The colored count inside Overdue (red, or
   green when zero) already uses a fixed non-flipping color, so that signal
   survives even once the card background goes neutral here. */
html[data-theme="dark"] .receivables-card { background: var(--panel); border-color: var(--rule); }
html[data-theme="dark"] .overdue-card { background: var(--panel); border-color: var(--rule); }
html[data-theme="dark"] .overdue-card.zero { background: var(--panel); border-color: var(--rule); }
html[data-theme="dark"] input:disabled,
html[data-theme="dark"] select:disabled,
html[data-theme="dark"] textarea:disabled { background: var(--hover-surface); }

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: 'Geist', sans-serif;
  font-size: 15.5px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  transition: background 0.2s ease, color 0.2s ease;
}

h1, h2, h3, .display {
  font-family: 'Geist', sans-serif;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0;
}

.mono { font-family: 'Geist Mono', monospace; }

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

/* Media never overflows its container. Elements that need a fixed size
   (logos, thumbnails, avatars) set it with higher specificity, so this only
   acts as a safety cap. */
img { max-width: 100%; height: auto; }

/* Global loading bar (driven by api() in app.js). */
.app-loading-bar {
  position: fixed; top: env(safe-area-inset-top, 0); left: 0; right: 0;
  height: 3px; z-index: 300; overflow: hidden; pointer-events: none;
  opacity: 0; transition: opacity 0.2s ease;
}
.app-loading-bar.on { opacity: 1; }
.app-loading-bar .bar {
  height: 100%; width: 40%; border-radius: 0 3px 3px 0;
  background: var(--primary-gradient);
  animation: appLoadSlide 1.1s ease-in-out infinite;
}
@keyframes appLoadSlide {
  0% { margin-left: -40%; }
  100% { margin-left: 100%; }
}

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

/* Frosted band over the top safe-area (status-bar / notch) strip, so scrolled
   content blurs softly behind the clock instead of bleeding through — like the
   iMessage bar. Follows the theme; zero-height (invisible) in a normal browser
   tab where there's no top inset. Solid --paper is the fallback where
   color-mix / backdrop-filter aren't supported. */
/* Status-bar / notch treatment for the INSTALLED app (standalone) only — a
   normal browser tab has its own chrome and needs none of this.
   ::before = a SOLID band the height of the top inset, so the clock always sits
   on a clean, theme-matched background and scrolled content never shows through.
   ::after  = a short gradient just below it that fades content out softly for
   the iMessage-style transition. No backdrop-filter dependency, so it renders
   reliably on iOS. */

.sidebar {
  width: 226px;
  flex-shrink: 0;
  background: linear-gradient(180deg, #3D0A0A 0%, #6E0F0F 100%);
  color: #F6E9E9;
  padding: 24px 16px;
  display: flex;
  flex-direction: column;
}

.sidebar .side-logo {
  height: 64px;
  width: auto;
  background: #fff;
  padding: 8px 10px;
  border-radius: 12px;
  margin-bottom: 12px;
  align-self: flex-start;
}
.sidebar .brand {
  font-family: 'Geist', sans-serif;
  font-weight: 800;
  font-size: 17px;
  color: #fff;
  margin-bottom: 2px;
}
.sidebar .brand-sub {
  font-size: 11px;
  color: #D6A9A9;
  margin-bottom: 28px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.nav-link {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 12px;
  border-radius: 9px;
  color: #E2C7C7;
  font-size: 13.5px;
  font-weight: 500;
  margin-bottom: 3px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, transform 0.1s ease;
  border-left: 3px solid transparent;
}
.nav-link svg { width: 19px; height: 19px; flex-shrink: 0; }
.nav-link:hover { background: rgba(255,255,255,0.07); color: #fff; }
.nav-link.active {
  background: linear-gradient(90deg, rgba(11,95,191,0.35), rgba(14,165,233,0.12));
  color: #fff;
  border-left: 3px solid var(--accent);
}

/* Mobile bottom tab bar (hidden on desktop) */
.tabbar { display: none; }

.sidebar .spacer { flex: 1; }
.theme-toggle-btn {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 9px 12px; margin-bottom: 12px; border-radius: 9px;
  background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1);
  color: #E2C7C7; font-size: 12.5px; font-weight: 600; font-family: inherit;
  cursor: pointer; transition: background 0.15s ease, color 0.15s ease;
}
.theme-toggle-btn svg { width: 16px; height: 16px; flex-shrink: 0; }
.theme-toggle-btn:hover { background: rgba(255,255,255,0.12); color: #fff; }
.sidebar .user-box {
  border-top: 1px solid rgba(255,255,255,0.1);
  padding-top: 14px;
  font-size: 12.5px;
  color: #D6A9A9;
}
.sidebar .user-box .u-name { color: #fff; font-weight: 600; }
.sidebar .logout-btn {
  margin-top: 8px;
  font-size: 12px;
  color: #5EC5F5;
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
}
.sidebar .logout-btn:hover { color: #fff; }

/* flex:1 lets the column grow; max-width caps line length for readability;
   margin-inline:auto centres that capped column in the leftover space on
   ultrawide screens instead of pinning it left with a big empty right gutter.
   min-width:0 lets a flex child shrink below its content's intrinsic width so
   a wide table inside can scroll instead of forcing the whole page wider. */
.main { flex: 1; min-width: 0; padding: 32px 40px; max-width: 1200px; margin-inline: auto; }

.page-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 24px;
}
.page-header h1 { font-size: 26px; letter-spacing: -0.02em; }
.page-header .sub { color: var(--muted); font-size: 13.5px; margin-top: 4px; }

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 17px;
  border-radius: 9px;
  border: 1px solid transparent;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  /* A button label must never break. Table cells set word-break/overflow-wrap
     (for long emails, codes) and those properties are INHERITED, so in any
     narrow or not-yet-stacked column a button like "Edit contact" was breaking
     one letter per line (see the roster modal on phones). Pin these on the
     button itself so it always stays on a single line whatever its container
     does; if the row is tight the buttons wrap as whole pills, never mid-word. */
  white-space: nowrap;
  word-break: normal;
  overflow-wrap: normal;
  transition: transform 0.08s ease, box-shadow 0.15s ease, background 0.15s ease;
}
/* Same guarantee for status pills / chips / tab labels. */
.badge, .chip, .tab, .btn-sm { word-break: normal; overflow-wrap: normal; }
.btn-primary { background: var(--primary-gradient); color: #fff; box-shadow: var(--shadow-sm); }
.btn-primary:hover { box-shadow: var(--shadow-glow); transform: translateY(-1px); }
.btn-secondary { background: var(--panel); border-color: var(--rule); color: var(--ink); }
.btn-secondary:hover { background: #EEF3FC; border-color: var(--primary-light); }
.btn-danger { background: #fff; border-color: var(--red); color: var(--red); }
.btn-danger:hover { background: #FCEBEA; }
.btn-sm { padding: 5px 11px; font-size: 12.5px; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* Cards / tables */
.card {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 20px;
  box-shadow: var(--shadow-sm);
}

/* auto-fit + minmax lets the row hold as many equal cards as fit and wrap the
   rest onto a full new line — so 5 cards never leave a lone half-width orphan
   the way a hard repeat(4,1fr) did, and it needs no per-breakpoint overrides. */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 16px; margin-bottom: 28px; }
.stat-card { background: var(--panel); border: 1px solid var(--rule); border-radius: var(--radius); padding: 18px 20px; box-shadow: var(--shadow-sm); }
.stat-card .label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 8px; }
.stat-card .value { font-family: 'Geist', sans-serif; font-size: 27px; font-weight: 800; }
.stat-card .value.warn { color: var(--amber); }

table { width: 100%; border-collapse: collapse; }
th {
  text-align: left;
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  font-weight: 700;
  padding: 10px 12px;
  border-bottom: 1px solid var(--rule);
}
td { padding: 12px; border-bottom: 1px solid var(--rule); font-size: 14px; }
tr:last-child td { border-bottom: none; }
tr:hover td { background: var(--hover-surface); }

.badge {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 100px;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.badge-paid { background: #E1F0FE; color: var(--primary); }
.badge-partial { background: #EFE6FB; color: #6B3FA0; }
.badge-unpaid { background: #FBF1DE; color: var(--amber); }
.badge-overdue { background: #FCEAE9; color: var(--red); }
.badge-void { background: #EEF1F6; color: #7A8699; }
.badge-low { background: #FCEAE9; color: var(--red); }
.badge-ok { background: #E1F0FE; color: var(--primary); }

/* Forms */
label { display: block; font-size: 12.5px; font-weight: 600; color: var(--muted); margin-bottom: 5px; }
input, select, textarea {
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  font-family: inherit;
  font-size: 13.5px;
  background: #fff;
  color: var(--ink);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--primary); box-shadow: var(--shadow-glow); }
input:disabled, select:disabled, textarea:disabled { background: #F3F4F6; color: var(--muted); cursor: not-allowed; }
.field { margin-bottom: 14px; }
/* Flexible field row. Was a hard 2-column grid, which forced pages that put
   3–6 fields in one .field-row (attendance's M/F counts, the fundraising
   entry modal, etc.) into lopsided 2-per-line stacks with half-empty cells
   and mismatched inline max-widths — the "things out of line / crowding" on
   wider screens. Now fields flow in a wrapping row: each grows to share the
   space, a plain 2-field row still splits into equal halves, extra fields wrap
   onto a new line cleanly, and any inline max-width on a .field still caps it.
   min-width:0 lets fields shrink instead of overflowing. */
.field-row { display: flex; flex-wrap: wrap; gap: 14px; }
.field-row > .field { flex: 1 1 200px; min-width: 0; }

/* Hold-to-reveal password toggle — wrapper + button are injected around
   every input[type=password] by wirePasswordReveal() in app.js. */
.pw-field { position: relative; }
.pw-field input { padding-right: 40px; }
.pw-toggle {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: none;
}
.pw-toggle:hover { background: rgba(0,0,0,0.05); color: var(--ink); }
.pw-toggle:active, .pw-toggle.revealing { color: var(--primary); background: rgba(0,0,0,0.06); }
.pw-toggle svg { width: 18px; height: 18px; pointer-events: none; }

/* Searchable product picker (invoice / recurring-invoice line items) — a
   text input that filters the product list live instead of a giant
   <select>, which becomes unusable once the catalog grows past a
   screenful. See wireProductSearch() in page-invoices.js. */
.product-search-results {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 4px;
  max-height: 220px;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: 8px;
  box-shadow: var(--shadow-md);
  z-index: 20;
  display: none;
}
.product-search-results.open { display: block; }
.product-search-item {
  padding: 8px 11px;
  font-size: 13px;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
}
.product-search-item:hover, .product-search-item.highlighted { background: var(--hover-surface); }
.debrief-prod-item:hover { background: var(--hover-surface); }

/* Reusable wheel time picker (hour + minute 00–59 + AM/PM, plus typing).
   Built by initTimeSelect() in app.js: a typeable .tw-input with an
   iOS-style scroll-wheel .tw-pop popup underneath. TW_ITEM_H in app.js
   (38px) must match .tw-item's height, and .tw-pad's height must stay at
   exactly 2 rows (76px) so scrollTop = index * 38 centers that index under
   the .tw-center band — see the comment above initTimeSelect for the math.
   The native-iOS-picker look (numbers shrinking/fading as they move away
   from center, plus the `mask-image` fade at the column's top/bottom
   edges) is driven live by JS (updateArc in app.js) via per-item
   `scale`/`opacity` — flat 2D only, no 3D rotation. Gideon compared a flat
   version against a real rotateX+translateZ 3D drum in a live side-by-side
   demo and picked flat, so don't reintroduce 3D here without the same kind
   of live comparison — tuning 3D transforms from code alone (no visual
   feedback) produced a worse result last time.
   IMPORTANT: .tw-pop's visibility is controlled by the `hidden` attribute
   (JS toggles pop.hidden). Do NOT give .tw-pop an unconditional `display`
   value without also pairing it with a `.tw-pop[hidden]` override below —
   author CSS beats the browser's default `[hidden]{display:none}` rule
   regardless of source order, so an unconditional `display` here would
   make the popup impossible to close.
   IMPORTANT: .tw-center (the highlight band) is `position:absolute`, which
   always paints above plain in-flow content in the same stacking context
   regardless of DOM order — so .tw-col must carry its own `position` +
   `z-index` (below) to lift the item text above the band, or the selected
   number disappears behind the highlight. */
.time-wheel { position: relative; display: inline-block; }
.tw-input { width: 130px; }
.tw-input:disabled { opacity: .6; cursor: not-allowed; background: var(--hover-surface); }
.tw-pop {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: 10px 10px 8px;
}
.tw-pop[hidden] { display: none; }
.tw-cols { position: relative; display: flex; gap: 8px; height: 190px; }
.tw-col {
  position: relative;
  z-index: 1;
  width: 60px;
  height: 190px;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
  touch-action: pan-y;
  overscroll-behavior: contain;
  cursor: grab;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 22%, #000 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 22%, #000 78%, transparent 100%);
}
.tw-col::-webkit-scrollbar { display: none; width: 0; height: 0; }
.tw-col.dragging { cursor: grabbing; }
.tw-ap { width: 56px; }
.tw-pad { height: 76px; flex-shrink: 0; }
.tw-item {
  height: 38px;
  line-height: 38px;
  text-align: center;
  font-size: 16px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  cursor: pointer;
  scroll-snap-align: center;
  user-select: none;
  transition: color .1s;
}
.tw-item.on { color: var(--ink); font-weight: 700; font-size: 21px; }
/* Native iOS picker look: just two thin full-width divider lines around the
   selected row, no filled/rounded highlight pill. */
.tw-center {
  position: absolute;
  top: 76px;
  left: 0;
  right: 0;
  height: 38px;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  pointer-events: none;
}
.tw-actions { text-align: right; margin-top: 8px; }
.product-search-item .sub { font-size: 11px; color: var(--muted); white-space: nowrap; }
.product-search-empty { padding: 10px 11px; font-size: 12.5px; color: var(--muted); }

.modal-overlay {
  position: fixed; inset: 0; background: rgba(7,16,32,0.55); backdrop-filter: blur(2px);
  display: flex; align-items: center; justify-content: center; z-index: 100;
}
.modal {
  background: #fff; border-radius: var(--radius); padding: 24px;
  width: 480px; max-width: 90vw; max-height: 85vh; overflow-y: auto;
  box-shadow: var(--shadow-md);
}
/* Bulk multi-select toolbar (Invoices / Job cards / Customers / Inventory) */
.bulk-bar {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(20px + env(safe-area-inset-bottom));
  display: flex; align-items: center; gap: 14px;
  background: var(--toast-bg, #10233f); color: #fff;
  padding: 10px 14px; border-radius: 12px; box-shadow: var(--shadow-md);
  z-index: 150; max-width: min(94vw, 720px); flex-wrap: wrap;
}
.bulk-bar[hidden] { display: none; }
.bulk-bar .bulk-count { font-size: 13.5px; font-weight: 600; white-space: nowrap; }
.bulk-bar .bulk-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.bulk-cb, .bulk-all { width: 16px; height: 16px; cursor: pointer; }
td:has(> .bulk-cb), th:has(> .bulk-all) { width: 34px; text-align: center; }
/* Sit above the mobile bottom tab bar so it never hides the actions. */
@media (max-width: 820px) { .bulk-bar { bottom: calc(76px + env(safe-area-inset-bottom)); width: auto; } }

/* "or" divider (e.g. between password login and biometric login) */
.or-divider { display: flex; align-items: center; gap: 12px; margin: 16px 0; color: var(--muted); font-size: 12px; }
.or-divider::before, .or-divider::after { content: ''; flex: 1; height: 1px; background: var(--border); }

.modal h2 { font-size: 18px; margin-bottom: 18px; }

/* Floating "Done" bar shown above the on-screen keyboard on phones so you can
   drop the keyboard when finished typing. Positioned by --app-vvh (the live
   visible height kept in sync by app.js), so it hugs the top of the keyboard.
   Toggled via the [hidden] attribute from app.js; only wired up on mobile. */
#kbDoneBar {
  position: fixed;
  top: calc(var(--app-vvh, 100dvh) - 50px);
  right: 12px;
  z-index: 300;
  pointer-events: none;
}
#kbDoneBar[hidden] { display: none; }
.kb-done-btn {
  pointer-events: auto;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px; border: none; border-radius: 999px;
  background: var(--primary); color: #fff;
  font-family: inherit; font-size: 14px; font-weight: 600;
  box-shadow: var(--shadow-md); cursor: pointer;
}
.kb-done-btn svg { display: block; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }

.toast {
  position: fixed; bottom: 24px; right: 24px;
  background: var(--toast-bg); color: #fff; padding: 12px 18px; border-radius: 9px;
  font-size: 13.5px; z-index: 200; max-width: 320px; box-shadow: var(--shadow-md);
}
.toast.error { background: var(--red); }

.empty-state { text-align: center; padding: 50px 20px; color: var(--muted); }
.empty-state .signature { font-size: 32px; margin-bottom: 8px; font-family: 'Geist Mono', monospace; letter-spacing: 0.1em; }

.search-bar { display: flex; gap: 10px; margin-bottom: 18px; }
.search-bar input { flex: 1; }

/* ---- Login ---- */
.login-shell {
  min-height: 100vh; display: flex; align-items: stretch; justify-content: center;
  background: var(--primary-gradient);
  position: relative;
  overflow: hidden;
}
.login-shell::before, .login-shell::after {
  content: ''; position: absolute; border-radius: 50%; opacity: 0.18; background: #fff;
}
.login-shell::before { width: 480px; height: 480px; top: -180px; left: -140px; }
.login-shell::after { width: 360px; height: 360px; bottom: -160px; right: -100px; background: var(--accent); opacity: 0.28; }

.login-wrap { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; padding: 24px; z-index: 1; }
.login-card {
  background: rgba(255,255,255,0.98); width: 400px; max-width: 100%; padding: 40px 34px; border-radius: 18px;
  box-shadow: 0 20px 60px rgba(4, 16, 38, 0.35);
}
.login-card .brand { font-family: 'Geist', sans-serif; font-weight: 800; font-size: 21px; margin-bottom: 2px; color: var(--ink); }
.login-card .brand-sub { color: var(--muted); font-size: 12.5px; margin-bottom: 26px; }
.login-error { color: var(--red); font-size: 13px; margin-top: 10px; min-height: 18px; }
.login-links { display: flex; justify-content: space-between; margin-top: 14px; font-size: 12.5px; }
.login-links a { color: var(--primary); font-weight: 600; cursor: pointer; }
.login-links a:hover { text-decoration: underline; }

.role-tag { font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); font-weight: 600; }

@media (max-width: 820px) {
  /* .stat-grid now flows via auto-fit (see base rule) — no fixed column
     count needed here. */

  /* Stop accidental sideways scrolling at the root — the page still scrolls
     vertically the normal way, so no content gets clipped (unlike putting
     this on .main). */
  html { overflow-x: hidden; }

  /* REVERTED (19 Jul 2026, late): the "document never scrolls, .shell is
     the scroller" experiment fixed a cosmetic tab-bar ride but caused a
     family of real bugs — stuck pans with the keyboard, clipped content
     with a blank void, sideways wobble. Phones scroll the DOCUMENT again,
     the way browsers are engineered to handle URL bars, keyboards, and
     rubber-banding. Sideways overflow stays guarded at the root: */
  body { overflow-x: hidden; }

  /* Hide the sidebar, show a bottom tab bar instead */
  .sidebar { display: none; }
  /* Do NOT put overflow-x on .main. It's a stretched flex item under
     .shell{min-height:100vh}; any non-visible overflow-x makes iOS compute
     overflow-y:auto and clip the column to ~one screen, hiding everything
     lower down (Finance ledger, dashboard Job cards). Horizontal overflow is
     instead guarded at the root (html) below, which scrolls vertically the
     normal way and never clips page content. */
  .main { padding: 16px 16px 90px; max-width: 100%; min-width: 0; }

  /* Button clusters in a page header (Inventory's Stock log / Export /
     + Add product, etc.) must wrap instead of running off the right edge
     and getting clipped. */
  .page-header > div:last-child { flex-wrap: wrap; }
  .page-header > div:last-child > .btn { flex: 1 1 auto; }

  /* Keep wide tables from stretching the whole page (which made mobile
     browsers zoom the page out — everything tiny, prices spilling, dead
     whitespace below). Instead each table scrolls sideways *inside* its own
     card, so the page itself never grows wider than the screen. */
  .shell { min-width: 0; }
  /* Table stays inside its card and scrolls sideways only if it genuinely
     can't fit — no more stretching the whole page. */
  .main .card { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .main .card > table { width: 100%; }
  /* Matrix/grouped tables (KPI grid, attendance grids) keep their natural
     column widths and scroll sideways inside their own rt-scroll wrapper
     instead of stacking. The wrapper scrolls even though the card itself is
     forced overflow-x:visible on phones. */
  table.rt-matrix { width: auto; min-width: 100%; }
  table.rt-matrix td, table.rt-matrix th { white-space: nowrap; }

  /* Let long text columns wrap so the table shrinks to the screen, but keep
     amounts, dates and status badges on a single line so they read cleanly. */
  td { white-space: normal; word-break: break-word; }
  td.mono, .badge { white-space: nowrap; }

  /* Action-button columns (invoices/jobs/users/etc.) can hold up to ~7
     buttons. Let them wrap onto multiple lines and shrink a touch so a row
     stays compact instead of forcing a very wide, swipe-forever table.
     !important overrides the inline `white-space:nowrap` set in the JS. */
  table td:last-child { white-space: normal !important; }
  table td:last-child .btn { margin: 2px 3px 2px 0; }

  /* Touch targets: every tappable element is at least 44px tall with room to
     breathe between neighbours. */
  .btn { min-height: 44px; }
  .btn-sm { min-height: 44px; padding: 8px 14px; font-size: 13px; }
  .tab { min-height: 44px; }
  .chip { min-height: 40px; }

  /* 16px inputs stop iOS from auto-zooming the page when a field is focused
     (a big part of why the app felt "zoomed/tiny" before). */
  input, select, textarea { min-height: 44px; font-size: 16px; }
  label { font-size: 13px; }

  /* Respect notch / rounded-corner safe areas on the sides, AND reserve the
     top safe area so the page title clears the status-bar/notch when the app
     runs full-screen as an installed PWA (was colliding with the clock). */
  .main {
    padding-top: max(16px, calc(env(safe-area-inset-top) + 8px));
    padding-left: max(16px, env(safe-area-inset-left));
    padding-right: max(16px, env(safe-area-inset-right));
  }
  .tabbar { padding-left: max(4px, env(safe-area-inset-left)); padding-right: max(4px, env(safe-area-inset-right)); }
  .fab { right: max(24px, env(safe-area-inset-right)); }

  /* Long, unbreakable strings (emails, invoice numbers) must never force
     horizontal overflow. */
  td, .cust-sub, .cust-name, .mono { overflow-wrap: anywhere; }

  .tabbar {
    display: flex;
    position: fixed;
    bottom: 0; left: 0; right: 0;
    background: #fff;
    border-top: 1px solid var(--rule);
    padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
    z-index: 50;
    justify-content: space-around;
    box-shadow: 0 -4px 16px rgba(11,18,32,0.06);
  }
  .tabbar .tab {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    padding: 4px 2px;
    color: var(--muted);
    font-size: 10.5px;
    font-weight: 500;
    text-decoration: none;
  }
  .tabbar .tab svg { width: 22px; height: 22px; }
  .tabbar .tab.active { color: var(--primary); }
  /* The "More" tab is a <button>, so strip the default button chrome. */
  .tabbar button.tab { background: none; border: none; font-family: inherit; cursor: pointer; }

  /* Stack two-column layouts on mobile */
  .page-header { flex-direction: column; align-items: stretch; gap: 12px; }
  .dash-two-col { grid-template-columns: 1fr !important; }
  /* Fields go full-width, one per line, on phones (the row is now flex). */
  .field-row > .field { flex-basis: 100%; }

  /* Keyboard-safe modals: on phones the on-screen keyboard shrinks the visible
     viewport but NOT a position:fixed inset:0 overlay, so a vertically-centred
     modal hides its inputs / Save button behind the keyboard. Instead: size the
     overlay to the *visual* viewport height (--app-vvh, kept in sync by app.js),
     top-align it, and let it scroll — so the browser can bring the focused
     field above the keyboard and every button stays reachable by scrolling. */
  .modal-overlay {
    align-items: flex-start;
    height: var(--app-vvh, 100dvh);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 12px 0 max(12px, env(safe-area-inset-bottom));
  }
  .modal {
    width: 94vw;
    max-height: none;
    margin: auto 0;
  }
  table { font-size: 12.5px; }
  th, td { padding: 9px 8px; }
}

/* (Removed a max-width:460px override that forced .stat-grid to a single
   column — the base auto-fit rule already collapses to one column below
   ~400px, so the override was redundant.) */

/* ---- Tables become stacked cards on phones AND small tablets ----
   app.js tags each <td> with data-label="<column header>" and marks the
   header-less action column .rt-actions. Here we drop the header row and lay
   each row out as a labelled card, so nothing needs a horizontal swipe.
   Breakpoint aligned to 820px (2026-07-20) so it matches the point where the
   sidebar becomes the bottom tab bar — previously stacking happened only at
   640px, leaving a 641–819px "tablet gap" that showed the mobile chrome but
   desktop-style side-scrolling tables. Now the two switch together. */
@media (max-width: 820px) {
  /* !important overrides the inline overflow-x:auto on finance/inventory
     cards — once tables stack there's no sideways overflow to contain, and a
     leftover scroll container would needlessly clip content vertically too. */
  .main .card { overflow-x: visible !important; }

  table.responsive thead {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  table.responsive,
  table.responsive tbody { display: block; width: 100%; }
  /* Defeat any inline min-width (e.g. the Users table's min-width:860px set
     for desktop) so a stacked table never forces the card wider than the
     screen — which caused a half-empty card + iOS font inflation. */
  table.responsive { min-width: 0 !important; }
  table.responsive tr {
    display: block;
    padding: 12px 16px;
    border-bottom: 1px solid var(--rule);
  }
  table.responsive tr:last-child { border-bottom: none; }
  table.responsive tr:hover td { background: transparent; }

  table.responsive td {
    display: block;
    padding: 6px 0;
    border: none;
    text-align: left;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  table.responsive td.mono { white-space: normal; }
  table.responsive td[data-label]::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 3px;
    color: var(--muted);
    font-weight: 600;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }

  /* Action buttons: full-width-ish, two per row, comfortably tappable. */
  table.responsive td.rt-actions {
    display: flex;
    justify-content: flex-start;
    flex-wrap: wrap;
    gap: 8px;
    padding-top: 12px;
    text-align: left;
  }
  table.responsive td.rt-actions .btn {
    flex: 1 1 auto;
    min-width: calc(50% - 8px);
    margin: 0;
    justify-content: center;
  }

  /* Empty-state row keeps its centred look. */
  table.responsive td.rt-fullrow { display: block; text-align: center; }
  table.responsive td.rt-fullrow::before { content: none; }

  /* A leading header-less column (e.g. the attendance Male/Female/Online entry
     grid) is the row's label — show it as the stacked card's title, not a
     button row and not a blank labelled field. */
  table.responsive td.rt-rowhead {
    display: block; padding: 2px 0 6px; margin-bottom: 4px;
    font-size: 14px; font-weight: 700; border-bottom: 1px solid var(--rule);
  }
  table.responsive td.rt-rowhead::before { content: none; }

  /* KPI entry: hide the empty continuation code cell on phones so multi-KPI
     goals don't show a blank "CODE" line in the stacked view. */
  table.responsive td.kpi-code-cont { display: none; }
}

/* ---- Dashboard components ---- */
.dash-two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
.finance-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 20px; }
@media (max-width: 900px) { .finance-tiles { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .finance-tiles { grid-template-columns: 1fr; } }

.receivables-card {
  background: linear-gradient(135deg, #EAF2FE 0%, #E1F0FE 100%); border: 1px solid #D3E4FB; border-radius: var(--radius); padding: 20px 22px;
}
.receivables-card .label { font-size: 13px; color: var(--muted); margin-bottom: 6px; }
.receivables-card .value { font-family: 'Geist', sans-serif; font-size: 27px; font-weight: 800; color: var(--ink); }

.overdue-card {
  background: #FDEEEC; border: 1px solid #F5D2CE; border-radius: var(--radius); padding: 20px 22px;
  display: flex; flex-direction: column; justify-content: center; cursor: pointer;
  transition: transform 0.1s ease;
}
.overdue-card:hover { transform: translateY(-1px); }
.overdue-card .count { font-family: 'Geist', sans-serif; font-size: 27px; font-weight: 800; color: var(--red); }
.overdue-card .label { font-size: 13px; color: var(--muted); }
.overdue-card.zero { background: #EAF7F1; border-color: #CDEBDD; }
.overdue-card.zero .count { color: #1B8A57; }

.quick-create { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.quick-tile {
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 16px 8px; text-align: center; cursor: pointer; text-decoration: none; color: var(--ink);
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}
.quick-tile:hover { border-color: var(--primary); box-shadow: var(--shadow-sm); transform: translateY(-2px); }
.quick-tile svg { width: 26px; height: 26px; color: var(--primary); margin-bottom: 6px; }
.quick-tile .t-label { font-size: 12.5px; font-weight: 600; }

.section-title { display: flex; align-items: center; justify-content: space-between; margin: 8px 0 12px; }
.section-title h3 { font-size: 15.5px; }

.basis-toggle { display: inline-flex; background: #E9EEF7; border-radius: 100px; padding: 3px; }
.basis-toggle button {
  border: none; background: none; padding: 6px 16px; border-radius: 100px;
  font-size: 12.5px; font-weight: 600; cursor: pointer; color: var(--muted); font-family: inherit;
}
.basis-toggle button.active { background: #fff; color: var(--primary); box-shadow: 0 1px 3px rgba(11,18,32,0.12); }

.chart-wrap { width: 100%; height: 280px; }
/* The canvas's own box must be pinned by CSS (not left to its width/height
   attributes) — drawChart() in page-dashboard.js reads canvas.clientWidth/
   Height, multiplies by devicePixelRatio, and writes that back into the
   width/height attributes for sharpness. Without this rule those attributes
   ALSO define the canvas's on-screen size (nothing else does), so every
   redraw (every basis toggle click, every resize) re-reads its own already
   inflated size and grows it again — a runaway feedback loop, worst on any
   screen with devicePixelRatio > 1 (i.e. most phones and modern laptops).
   Pinning width/height to 100% here breaks that loop: clientWidth/Height
   always reflect .chart-wrap's fixed size, never the canvas's own buffer. */
.chart-wrap canvas { display: block; width: 100%; height: 100%; }

/* ---- Customers list (Zoho-style) ---- */
.filter-chips { display: flex; gap: 8px; align-items: center; margin-bottom: 16px; flex-wrap: wrap; }
.chip {
  border: 1px solid var(--rule); background: #fff; color: var(--ink);
  padding: 7px 16px; border-radius: 100px; font-size: 13px; font-weight: 600; cursor: pointer;
}
.chip.active { background: var(--primary-gradient); color: #fff; border-color: transparent; }
.chip-sort { margin-left: auto; }

.customer-list { background: var(--panel); border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden; }
.customer-row {
  display: flex; align-items: flex-start; gap: 14px; padding: 16px 18px;
  border-bottom: 1px solid var(--rule); cursor: pointer;
}
.customer-row:last-child { border-bottom: none; }
.customer-row:hover { background: var(--hover-surface); }

.avatar {
  width: 46px; height: 46px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-weight: 700; font-size: 15px; font-family: 'Geist', sans-serif;
}
.cust-body { flex: 1; min-width: 0; }
.cust-name { font-weight: 700; font-size: 15px; margin-bottom: 2px; }
.cust-sub { color: var(--muted); font-size: 13px; margin-bottom: 8px; }
.cust-figures { display: flex; gap: 40px; }
.cust-figures .fig-label { font-size: 12px; color: var(--muted); }
.cust-figures .fig-value { font-size: 14px; font-weight: 600; }
.cust-figures .fig-value.owed { color: var(--amber); }
.cust-figures .fig-value.overdue { color: var(--red); }

/* Sort sheet */
.sheet-overlay { position: fixed; inset: 0; background: rgba(7,16,32,0.55); z-index: 100; display: flex; align-items: flex-end; justify-content: center; }
.sheet {
  background: #fff; width: 100%; max-width: 520px; border-radius: 16px 16px 0 0; padding: 20px 20px 28px;
}
.sheet h3 { text-align: center; margin-bottom: 16px; }
.sort-opt {
  padding: 14px 12px; border-radius: 8px; cursor: pointer; font-size: 14px;
  display: flex; justify-content: space-between; align-items: center; border: 1px solid transparent;
}
.sort-opt:hover { background: var(--hover-surface); }
.sort-opt.active { border-color: var(--primary); color: var(--primary); font-weight: 600; }

/* ---- Mobile "More" sheet ---- */
.more-sheet {
  max-height: 82vh;
  overflow-y: auto;
  padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
}
.more-grabber { width: 40px; height: 4px; border-radius: 100px; background: var(--rule); margin: 0 auto 16px; }
.more-user { text-align: center; margin-bottom: 16px; }
.more-user-name { font-family: 'Geist', sans-serif; font-weight: 700; font-size: 16px; margin-bottom: 4px; }
.more-list { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; }
.more-item {
  display: flex; align-items: center; gap: 11px;
  padding: 14px; min-height: 52px;
  border: 1px solid var(--rule); border-radius: 10px;
  background: var(--panel); color: var(--ink);
  font-size: 14px; font-weight: 600; font-family: inherit;
  text-decoration: none; cursor: pointer; width: 100%; text-align: left;
}
.more-item + .more-item { margin-top: 8px; }
.more-list .more-item + .more-item { margin-top: 0; }
.more-item svg { width: 20px; height: 20px; flex-shrink: 0; color: var(--primary); }
.more-item:hover { background: var(--hover-surface); }
.more-item.active { border-color: var(--primary); color: var(--primary); background: var(--hover-surface); }
.more-item.danger { color: var(--red); }
.more-item.danger svg { color: var(--red); }

.fab {
  position: fixed; right: 24px; bottom: 24px; width: 56px; height: 56px; border-radius: 50%;
  background: var(--primary-gradient); color: #fff; border: none; font-size: 28px; cursor: pointer; z-index: 40;
  box-shadow: 0 6px 18px rgba(11,95,191,0.4); display: none;
}
@media (max-width: 820px) {
  .fab { display: block; bottom: 84px; }
  .cust-figures { gap: 28px; }
}

/* ---- Inventory thumbnails & profit ---- */
.item-thumb {
  width: 44px; height: 44px; border-radius: 8px; object-fit: cover;
  background: #EEF3FC; border: 1px solid var(--rule); flex-shrink: 0;
}
.item-thumb-placeholder {
  width: 44px; height: 44px; border-radius: 8px; background: #EEF3FC; border: 1px solid var(--rule);
  display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 18px; flex-shrink: 0;
}
.profit-pos { color: var(--primary); }
.profit-neg { color: var(--red); }
.img-drop {
  border: 1.5px dashed var(--rule); border-radius: 8px; padding: 14px; text-align: center;
  color: var(--muted); font-size: 13px; cursor: pointer;
}
.img-drop:hover { border-color: var(--primary); }
.img-drop.drag-over { border-color: var(--primary); background: #EAF2FE; color: var(--primary); }
.img-preview { max-width: 100%; max-height: 140px; border-radius: 8px; display: block; }
.img-preview-wrap { position: relative; display: inline-block; margin-top: 8px; }
.img-remove-btn {
  position: absolute; top: -8px; right: -8px; width: 24px; height: 24px; border-radius: 50%;
  background: var(--red); color: #fff; border: 2px solid #fff; cursor: pointer;
  font-size: 16px; line-height: 1; display: flex; align-items: center; justify-content: center;
  padding: 0; box-shadow: 0 1px 3px rgba(0,0,0,.25);
}
.img-remove-btn:hover { background: #C0392B; }

/* ---- Password strength meter ---- */
.pw-meter-bar { height: 6px; border-radius: 100px; background: var(--rule); overflow: hidden; }
.pw-meter-fill { height: 100%; border-radius: 100px; transition: width 0.15s ease, background 0.15s ease; }
.pw-meter-fill.weak { background: var(--red); }
.pw-meter-fill.okay { background: var(--amber); }
.pw-meter-fill.strong { background: var(--primary); }
.pw-meter-label { font-size: 12px; font-weight: 700; margin-top: 5px; }
.pw-meter-label.weak { color: var(--red); }
.pw-meter-label.okay { color: var(--amber); }
.pw-meter-label.strong { color: var(--primary); }
.pw-rules { list-style: none; padding: 0; margin: 8px 0 0; font-size: 12px; color: var(--muted); }
.pw-rules li { padding: 2px 0; }
.pw-rules li.ok { color: var(--primary); font-weight: 600; }

/* ---- Permission checklist ---- */
.perm-checklist { border: 1px solid var(--rule); border-radius: 8px; padding: 12px 14px; margin-top: 4px; max-height: 220px; overflow-y: auto; }

/* ---- App footer ---- */
.app-footer {
  margin-top: 40px; padding-top: 16px; border-top: 1px solid var(--rule);
  font-size: 12px; color: var(--muted); text-align: center;
}
.app-footer a { color: var(--muted); text-decoration: underline; }
.app-footer a:hover { color: var(--primary); }
@media (max-width: 820px) {
  .app-footer { margin-bottom: 70px; }
}

/* ============================ Row actions menu ============================
   Dropdown behind each table row's "Actions ▾" button (showActionsMenu in
   app.js). Fixed-position and body-appended so the table card's overflow
   scrolling never clips it. */
.actions-menu {
  position: fixed; z-index: 250; min-width: 176px;
  background: var(--panel); border: 1px solid var(--rule);
  border-radius: 10px; box-shadow: var(--shadow-md); padding: 5px;
  display: flex; flex-direction: column;
}
.actions-menu-item {
  display: block; width: 100%; text-align: left; background: none; border: none;
  padding: 8px 12px; border-radius: 7px; font: inherit; font-size: 13px;
  font-weight: 600; color: var(--ink); cursor: pointer; text-decoration: none;
}
.actions-menu-item:hover { background: var(--hover-surface); }
.actions-menu-item.primary { color: var(--primary); }
.actions-menu-item.danger { color: var(--red); }
.actions-menu-divider { height: 1px; background: var(--rule); margin: 4px 6px; }


/* ---- Wide-table safety net (all widths) ----
   A card whose direct child is a table (all the AOTC list cards: Users 8-col,
   Committees, the audit log with IP + Device, etc.) may be wider than the
   space it's given on smaller desktops and tablets. Let that card scroll the
   table sideways *inside itself* instead of stretching past the viewport and
   dragging the whole page into a horizontal shift. Scoped with :has(> table)
   so it never touches cards holding absolutely-positioned popovers (search
   results, the time wheel). The <=640px stacked-card rule overrides this back
   to visible, since a stacked table has no sideways overflow to contain. */
.main .card:has(> table) { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* Scroll-edge hint (desktop / large tablet only, >820px — below that tables
   stack into cards and there's nothing to scroll). Canonical "scrolling
   shadows" (Lea Verou / Roma Komarov): two opaque panel-coloured covers pinned
   to the left/right edges hide the shadow when you're at that end, and two soft
   shadows sit behind them via background-attachment:scroll — so a faint shadow
   only shows on a side that actually has more table off-screen, telling the
   user they can scroll. background-image (not the `background` shorthand) keeps
   the card's own background-color intact. */
@media (min-width: 821px) {
  .main .card:has(> table) {
    background-image:
      linear-gradient(to right, var(--panel), rgba(255,255,255,0)),
      linear-gradient(to left,  var(--panel), rgba(255,255,255,0)),
      radial-gradient(farthest-side at 0 50%,   rgba(11,18,32,.16), rgba(11,18,32,0)),
      radial-gradient(farthest-side at 100% 50%, rgba(11,18,32,.16), rgba(11,18,32,0));
    background-position: 0 0, 100% 0, 0 0, 100% 0;
    background-size: 32px 100%, 32px 100%, 14px 100%, 14px 100%;
    background-repeat: no-repeat;
    background-attachment: local, local, scroll, scroll;
  }
}

/* ---- In-app notification banner (push arriving while the app is open) ---- */
.push-banner {
  position: fixed; top: calc(12px + env(safe-area-inset-top, 0px)); left: 50%; transform: translateX(-50%);
  z-index: 400; display: flex; align-items: center; gap: 11px;
  max-width: min(94vw, 460px);
  background: var(--panel); color: var(--ink); border: 1px solid var(--rule);
  border-radius: 12px; box-shadow: var(--shadow-md); padding: 10px 14px;
  font: inherit; font-size: 13.5px; text-align: left; cursor: pointer;
  animation: toast-in .25s cubic-bezier(.2, 1.1, .4, 1);
}
.push-banner .pb-dot { font-size: 16px; flex-shrink: 0; }
.push-banner .pb-text { display: flex; flex-direction: column; line-height: 1.35; min-width: 0; }
.push-banner .pb-body { color: var(--muted); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.push-banner .pb-open { margin-left: auto; color: var(--primary); font-weight: 700; flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .push-banner { animation: none; } }

/* ---- Styled confirm / info dialog (replaces native confirm/alert) ---- */
.confirm-modal { width: 420px; }
.confirm-body {
  margin: 0; color: var(--ink); font-size: 14px; line-height: 1.55;
  white-space: pre-line; /* keep "\n" line breaks from the message */
}

/* ---- Column toggle control (wide tables) ----
   The little "Columns" button app.js inserts above wide tables. The dropdown
   itself is the shared .actions-menu (body-level, fixed) so nothing here needs
   to position it. Hidden on phones/small tablets (≤820px) where tables already
   stack into labelled cards and every field is visible anyway. */
.col-toggle { display: flex; justify-content: flex-end; padding: 10px 12px 0; }
.col-toggle-btn {
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--muted);
  background: var(--panel); border: 1px solid var(--rule); border-radius: 8px;
  padding: 5px 12px; cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.col-toggle-btn:hover { border-color: var(--primary-light); color: var(--ink); }
@media (max-width: 820px) { .col-toggle { display: none; } }

/* ---- Gina's Angel Hub additions ---- */
td.mono, .mono { white-space: nowrap; }           /* goal codes like T-AHRM-01 never wrap */
.entity-tag {
  margin-top: 4px; font-size: 11.5px; font-weight: 700; line-height: 1.3;
  color: #fff; background: rgba(255,255,255,.14);
  border-radius: 6px; padding: 3px 8px; display: inline-block; max-width: 100%;
}
.stat-card { border-top: 3px solid var(--primary); }
.stat-card:nth-child(2) { border-top-color: var(--red); }
.stat-card:nth-child(3) { border-top-color: var(--gas-green); }
.stat-card:nth-child(4) { border-top-color: var(--accent); }


/* ---- dashboard enrichment ---- */
.q-progress { height: 10px; background: var(--rule); border-radius: 6px; overflow: hidden; margin: 10px 0 6px; }
.q-progress > div { height: 100%; background: var(--primary-gradient); border-radius: 6px; }
.deadline-chips { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
.deadline-chip { font-size: 12.5px; font-weight: 600; padding: 5px 11px; border-radius: 999px; background: var(--panel); border: 1px solid var(--rule); }
.deadline-chip.warn { border-color: var(--red); color: var(--red); }
.source-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.quick-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* ================================================================
   2026 polish pass (ported from Smart DeonTech mainframe)
   ================================================================ */

/* ---- 2. Smooth page transitions (MPA View Transitions) ---- */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation-duration: 0.14s; }
::view-transition-new(root) { animation-duration: 0.18s; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}

/* ---- 3. Skeleton loaders ---- */
.skel {
  height: 12px; border-radius: 6px; background: var(--rule);
  position: relative; overflow: hidden;
}
.skel::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.55), transparent);
  transform: translateX(-100%); animation: skel-sweep 1.2s infinite;
}
html[data-theme="dark"] .skel::after {
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.09), transparent);
}
.skel.w40 { width: 40%; } .skel.w55 { width: 55%; } .skel.w85 { width: 85%; }
@keyframes skel-sweep { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .skel::after { animation: none; } }

/* ---- 4. Command palette ---- */
.cmdk-overlay {
  position: fixed; inset: 0; z-index: 300; display: flex; align-items: flex-start; justify-content: center;
  padding-top: 12vh; background: rgba(15, 23, 42, .45); backdrop-filter: blur(3px);
}
.cmdk {
  width: min(560px, 92vw); background: var(--paper); border-radius: 14px;
  box-shadow: 0 24px 60px rgba(0,0,0,.3); overflow: hidden; border: 1px solid var(--rule);
}
.cmdk input {
  width: 100%; border: none; outline: none; padding: 16px 18px; font-size: 15.5px;
  background: transparent; color: var(--ink); border-bottom: 1px solid var(--rule);
}
.cmdk-list { max-height: 46vh; overflow-y: auto; padding: 6px; }
.cmdk-group { font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); padding: 8px 12px 2px; }
.cmdk-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: 8px;
  cursor: pointer; font-size: 14px; color: var(--ink);
}
.cmdk-item.active, .cmdk-item:hover { background: var(--panel); }
.cmdk-item .sub { margin-left: auto; font-size: 11.5px; }
.cmdk-foot { display: flex; gap: 14px; padding: 8px 14px; border-top: 1px solid var(--rule); color: var(--muted); font-size: 11px; }
.cmdk-kbtn {
  display: flex; align-items: center; gap: 8px; width: calc(100% - 24px); margin: 0 12px 10px;
  padding: 8px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,.18);
  background: rgba(255,255,255,.08); color: #E2C7C7; font-size: 12.5px; cursor: pointer; font-family: inherit;
}
.cmdk-kbtn kbd {
  margin-left: auto; font-size: 10px; border: 1px solid rgba(255,255,255,.25); border-radius: 4px; padding: 1px 5px;
}

/* ---- 6. Micro-interactions ---- */
.btn { transition: transform .1s ease, box-shadow .15s ease, background .15s ease, opacity .15s ease; }
.btn:active { transform: scale(.97); }
:is(button, a, .nav-link, .tab, .badge, input, select, textarea):focus-visible {
  outline: 2px solid var(--primary-light); outline-offset: 2px; border-radius: 6px;
}
::selection { background: rgba(29, 63, 148, .22); }
html[data-theme="dark"] ::selection { background: rgba(94, 163, 245, .35); }
* { scrollbar-width: thin; scrollbar-color: var(--rule) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-thumb { background: var(--rule); border-radius: 8px; }
.modal-overlay { animation: overlay-fade .18s ease; }
.modal { animation: modal-pop .2s cubic-bezier(.2, 1.1, .4, 1); }
@keyframes overlay-fade { from { opacity: 0; } }
@keyframes modal-pop { from { opacity: 0; transform: scale(.96) translateY(6px); } }
.toast { animation: toast-in .25s cubic-bezier(.2, 1.1, .4, 1), toast-out .35s ease 2.85s forwards; }
@keyframes toast-in { from { opacity: 0; transform: translateY(12px); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(6px); } }
.login-card { animation: login-float .4s cubic-bezier(.2, 1, .4, 1); }
@keyframes login-float { from { opacity: 0; transform: translateY(14px); } }
@media (prefers-reduced-motion: reduce) {
  .modal-overlay, .modal, .toast, .login-card { animation: none; }
  .btn:active { transform: none; }
}

/* ---- 5. Typography extras ---- */
h1, h2, h3 { text-wrap: balance; }
.mono, .stat-card .value, td.mono { font-variant-numeric: tabular-nums; }

/* ---- grouped sidebar sections ---- */
.nav-section {
  font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .09em;
  color: rgba(185, 198, 222, .65); padding: 14px 14px 4px;
}

/* ---- Home hero (warm landing) ---- */
.dash-hero {
  background: var(--primary-gradient); color: #fff;
  border-radius: 14px; padding: 22px 24px; margin-bottom: 20px;
  display: flex; gap: 18px; align-items: flex-start;
  box-shadow: 0 10px 28px rgba(20, 44, 107, .28);
}
.hero-crest {
  width: 58px; height: 58px; object-fit: contain; flex-shrink: 0;
  background: #fff; border-radius: 12px; padding: 7px;
  box-shadow: 0 3px 10px rgba(0, 0, 0, .18);
}
.hero-greet { font-size: 22px; font-weight: 700; letter-spacing: -.01em; }
.hero-sub { opacity: .88; font-size: 13px; margin-top: 3px; }
.hero-verse {
  margin: 14px 0 0; font-style: italic; font-size: 13.5px; line-height: 1.55;
  opacity: .96; border-left: 3px solid rgba(255, 255, 255, .55);
  padding-left: 12px; max-width: 660px;
}
.hero-verse .verse-ref { opacity: .75; font-style: normal; font-size: 12px; display: block; margin-top: 3px; }
.dash-hero .q-progress { background: rgba(255, 255, 255, .22); }
.dash-hero .q-progress > div { background: #fff; }
.dash-hero .deadline-chip { background: rgba(255, 255, 255, .16); border-color: transparent; color: #fff; }
.dash-hero .deadline-chip.warn { background: rgba(255, 255, 255, .92); color: var(--red); }
@media (max-width: 620px) {
  .dash-hero { padding: 18px 16px; }
  .hero-crest { width: 44px; height: 44px; }
  .hero-greet { font-size: 18px; }
}

/* ---- "Needs your attention" cards ---- */
.attn-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 14px; margin-bottom: 20px;
}
.attn-grid:empty { display: none; }
.attn-card {
  background: var(--panel); border: 1px solid var(--rule);
  border-left: 4px solid var(--primary); border-radius: 12px; padding: 14px 16px;
  cursor: pointer; transition: transform .12s ease, box-shadow .12s ease;
}
.attn-card:hover { transform: translateY(-2px); box-shadow: 0 6px 18px rgba(20, 44, 107, .12); }
.attn-card.warn { border-left-color: #C9A227; }
.attn-card.ok { border-left-color: var(--gas-green, #00734A); }
.attn-card h4 { margin: 0 0 5px; font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.attn-card .attn-main { font-size: 14.5px; font-weight: 600; }
.attn-card .attn-more { font-size: 12.5px; color: var(--muted); margin-top: 3px; }

/* ---- Modal tables (2026-07-19) ----
   Buttons inside pop-up tables must never wrap letter-by-letter, whatever
   the screen width; app.js also stacks .modal tables on phones now. */
.modal .btn { white-space: nowrap; }

/* ---- Mobile safety net (2026-07-19) ----
   Holistic guards so individual pages can't break the phone layout.
   Together with: auto-stacking tables (app.js), overflow-x:hidden on .shell,
   --app-vvh keyboard sizing, and focus scroll-into-view.
   Breakpoint bumped 640→820 (2026-07-20) to stay in step with the stacking
   and sidebar/tab-bar breakpoints — these guards are harmless on tablets. */
@media (max-width: 820px) {
  /* Nothing may exceed the screen width, whatever inline width it asked for. */
  .main img, .main canvas, .main svg, .main video { max-width: 100%; height: auto; }
  .main input, .main select, .main textarea { max-width: 100%; }
  .main .field-row { flex-wrap: wrap; }
  .main .field { min-width: 0; }
  /* Long unbroken strings (links, codes, emails) wrap instead of pushing wide. */
  .main .card { overflow-wrap: anywhere; }
  /* Button rows wrap rather than run off the edge. */
  .main .page-header, .main .section-title { flex-wrap: wrap; gap: 8px; }
}

/* ================================================================
   UX polish pass (2026-07-20): mobile ergonomics + micro-interactions
   ================================================================ */

/* ---- Mobile ergonomics ---- */
@media (max-width: 820px) {
  /* Modal footers (including the new confirm/info dialogs): buttons stretch to
     fill the row and grow to a comfortable thumb size instead of sitting small
     and right-aligned. Cancel stays on the left, primary/confirm on the right. */
  .modal-actions { flex-wrap: wrap; gap: 10px; }
  .modal-actions .btn { flex: 1 1 auto; justify-content: center; min-height: 46px; }

  /* Segmented / pill controls get a proper tap height. */
  .basis-toggle button, .col-toggle-btn, .chip { min-height: 40px; }

  /* A touch more breathing room between stacked cards on data-dense pages. */
  table.responsive tr { padding-top: 14px; padding-bottom: 14px; }
}

/* ---- Micro-interactions & consistency ---- */
/* Consistent press feedback on the tappable "tiles" that previously only had a
   hover lift (no active-state cue on touch). Matches the .btn:active language. */
.quick-tile:active, .attn-card:active, .overdue-card:active, .chip:active,
.more-item:active, .sort-opt:active, .cmdk-item:active {
  transform: scale(.985);
}
/* Confirm/info dialog: give the body a little separation from the buttons and
   keep the primary action visually dominant (btn-primary/danger already do). */
.confirm-modal .modal-actions { margin-top: 22px; }

@media (prefers-reduced-motion: reduce) {
  .quick-tile:active, .attn-card:active, .overdue-card:active, .chip:active,
  .more-item:active, .sort-opt:active, .cmdk-item:active { transform: none; }
}

/* ================================================================
   In-app messaging (chat)
   ================================================================ */
.chat { display: flex; height: min(72vh, 620px); overflow: hidden; }
.chat-list { width: 300px; flex-shrink: 0; border-right: 1px solid var(--rule); overflow-y: auto; }
.conv-item {
  display: block; width: 100%; text-align: left; background: none; border: none;
  border-bottom: 1px solid var(--rule); padding: 12px 14px; cursor: pointer; font: inherit; color: var(--ink);
}
.conv-item:hover { background: var(--hover-surface); }
.conv-item.active { background: var(--hover-surface); box-shadow: inset 3px 0 0 var(--primary); }
.conv-top { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.conv-name { font-weight: 700; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.conv-time { font-size: 11px; color: var(--muted); flex-shrink: 0; }
.conv-last { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--muted); margin-top: 3px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.conv-unread { margin-left: auto; flex-shrink: 0; background: var(--primary); color: #fff; font-size: 11px; font-weight: 700; border-radius: 100px; padding: 1px 7px; }
.chat-thread { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.chat-empty { margin: auto; color: var(--muted); font-size: 14px; padding: 24px; text-align: center; }
.chat-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--rule); flex-shrink: 0; }
.chat-title { font-weight: 700; font-size: 15px; }
.chat-back { display: none; background: none; border: none; font-size: 26px; line-height: 1; color: var(--primary); cursor: pointer; padding: 0 4px; }
.chat-msgs { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 8px; }
.msg-row { display: flex; }
.msg-row.mine { justify-content: flex-end; }
.msg-bubble { max-width: 76%; padding: 8px 12px; border-radius: 14px; font-size: 14px; line-height: 1.4; }
.msg-row.theirs .msg-bubble { background: var(--hover-surface); border: 1px solid var(--rule); border-bottom-left-radius: 4px; }
.msg-row.mine .msg-bubble { background: var(--primary-gradient); color: #fff; border-bottom-right-radius: 4px; }
.msg-from { font-size: 11.5px; font-weight: 700; color: var(--primary); margin-bottom: 2px; }
.msg-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.msg-time { font-size: 10.5px; opacity: .7; margin-top: 3px; text-align: right; }
.chat-composer { display: flex; gap: 8px; padding: 12px 14px; border-top: 1px solid var(--rule); flex-shrink: 0; }
.chat-composer input { flex: 1; }
.grp-person { display: flex; align-items: center; gap: 8px; padding: 6px 2px; font-weight: 500; }
.grp-person input { width: auto; min-height: 0; }
@media (max-width: 820px) {
  .chat { height: calc(var(--app-vvh, 100dvh) - 220px); min-height: 360px; }
  .chat-list { width: 100%; }
  .chat-thread { display: none; }
  .chat.thread-open .chat-list { display: none; }
  .chat.thread-open .chat-thread { display: flex; }
  .chat-back { display: block; }
}

/* ---- GAS Track additions ---- */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
@media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } }
.stat-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--panel); border: 1px solid var(--rule); border-radius: 999px;
  padding: 8px 16px; font-size: 13.5px; color: var(--muted); box-shadow: var(--shadow-sm);
}
.stat-chip b { color: var(--ink); font-size: 15px; }
.mark-btn {
  width: 34px; height: 34px; border-radius: 8px; border: 1px solid var(--rule);
  background: var(--panel); cursor: pointer; font-weight: 700; color: var(--muted);
}
.mark-btn.on-P { background: #00734A; border-color: #00734A; color: #fff; }
.mark-btn.on-L { background: var(--amber); border-color: var(--amber); color: #fff; }
.mark-btn.on-A { background: var(--red); border-color: var(--red); color: #fff; }

/* Sidebar motto */
.side-motto {
  font-size: 11.5px; font-style: italic; color: #D6A9A9;
  margin: 10px 4px 14px; line-height: 1.4;
  padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.12);
}
/* Mission / vision cards on the dashboard */
.mv-card { border-left: 4px solid var(--primary); padding-left: 14px; margin-bottom: 14px; }
.mv-card h4 { margin: 0 0 4px; font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--primary); }
.mv-card p { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--ink); }

/* ---- Month calendar -----------------------------------------------------
   A wall-calendar grid: terms shaded so the school year is visible at a
   glance, exam weeks banded, events sitting on their own days. Built on CSS
   grid rather than a table so a day cell can hold several events without the
   row heights fighting each other. */
.cal-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.cal-dow { margin-bottom: 4px; }
.cal-dow span {
  text-align: center; font-size: 11px; font-weight: 700; letter-spacing: .04em;
  color: var(--muted); text-transform: uppercase; padding: 4px 0;
}
.cal-day {
  min-height: 78px; border: 1px solid var(--rule); border-radius: 8px;
  padding: 5px 6px; background: var(--panel); position: relative;
  display: flex; flex-direction: column; gap: 3px;
}
.cal-day.is-outside { opacity: .38; }
.cal-day.in-term { background: color-mix(in srgb, var(--primary) 5%, var(--panel)); }
.cal-day.in-exams { background: color-mix(in srgb, var(--gas-gold) 15%, var(--panel)); }
.cal-day.is-today { border-color: var(--primary); border-width: 2px; padding: 4px 5px; }
.cal-day.is-clickable { cursor: pointer; }
.cal-day.is-clickable:hover { border-color: var(--primary); }
.cal-daynum { font-size: 12px; font-weight: 700; color: var(--ink); }
.cal-day.is-today .cal-daynum { color: var(--primary); }
.cal-chip {
  font-size: 10.5px; line-height: 1.25; border-radius: 4px; padding: 2px 4px;
  background: color-mix(in srgb, var(--primary) 14%, var(--panel));
  color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-chip.t-term { background: color-mix(in srgb, var(--primary) 22%, var(--panel)); font-weight: 700; }
.cal-chip.t-exam { background: color-mix(in srgb, var(--gas-gold) 34%, var(--panel)); }
.cal-chip.t-holiday { background: color-mix(in srgb, var(--gas-green) 20%, var(--panel)); }
.cal-legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 14px; font-size: 12px; color: var(--muted); }
.cal-legend i { display: inline-block; width: 11px; height: 11px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }

@media (max-width: 700px) {
  .cal-day { min-height: 56px; padding: 3px; }
  .cal-daynum { font-size: 11px; }
  /* Chips become dots — a name can't fit legibly in a phone-width cell, and
     truncated titles read as noise. Tap the day to see them listed below. */
  .cal-chip { font-size: 0; height: 5px; padding: 0; border-radius: 3px; }
}
