:root {
  --theme-text-muted: #5f5f5f;
  --theme-border: #e3e3e3;
  --theme-banner-background: #f750ca;
  --theme-link-hover: #a3027a;
  --theme-accent-3: #4fa8d8;
  --theme-accent-1: #6fae6e;
  --theme-accent-2: #ffd400;
  --theme-footer-background: #181818;
  --theme-footer-text: #f5f5f5;
  --theme-row-hover: #fdf1fa;
  --theme-page-background: #f6eef4;
  --theme-menu-bar: #f750ca;
  --theme-menu-text: #ffffff;
  --theme-body-background: #ffffff;
  --theme-body-text: #000000;
  --theme-body-link: #c2038f;
  --theme-button-body: #ffe5f9;
  --theme-button-border: #f750ca;
  --theme-accent-tint: #ffe5f9;
  --theme-mms-accent-1: #6fae6e;
  --theme-mms-accent-2: #ffd400;
  --theme-mms-accent-3: #4fa8d8;
  --theme-mms-button-body: #ffe5f9;
  --theme-mms-button-border: #f750ca;

  /* Bootstrap variable remap - see docblock above. Sourced from
     button_border, not button_body (2026-09-06, found live) -
     button_body is now deliberately the LIGHT button fill (see the
     unified .btn-primary rule below), but --bs-primary/--bs-primary-rgb
     back dozens of OTHER "vivid accent" uses across the admin (focus
     rings, subtle hover tints, icon chips, badges) that were all built
     assuming button_body was still the vivid one. button_border is now
     that vivid token, so this remap follows it instead. */
  --bs-primary: var(--theme-button-border);
  --bs-primary-rgb: 247, 80, 202;
  --bs-link-color: var(--theme-body-link);
  --bs-link-hover-color: var(--theme-link-hover);
  --bs-body-color: var(--theme-body-text);
  --bs-body-bg: var(--theme-body-background);
  --bs-border-color: var(--theme-border);
  --bs-secondary-color: var(--theme-text-muted);
  /* Sitewide type system (2026-08-07, Allie: "lets sort fonts out... i
     agree we need a uniform sitewide look") - Space Grotesk for
     headings/numbers (reads as operations/production software, fits a
     business-management back end rather than a retail storefront),
     Inter for body copy (the standard for dense, legible admin UI).
     Both loaded via Google Fonts <link> in header.php/sidebar-
     header.php, same CDN-heavy convention as Bootstrap/jQuery/
     DataTables/TinyMCE elsewhere in this app. Real fallback chain kept
     on both in case the CDN is ever slow/blocked - never just the web
     font name alone. */
  --font-display: 'Space Grotesk', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --bs-body-font-family: var(--font-body);

  /* Page-wide gradient (owner reference: legacy/accounting's own
     --wl-gradient token) - composed from real tokens already here
     rather than its own stored value, so it stays in sync with
     accent-tint/body-background automatically instead of needing to be
     kept in step by hand. */
  --theme-gradient: linear-gradient(180deg, var(--theme-accent-tint) 0%, var(--theme-body-background) 55%);
}

body {
  background: var(--theme-gradient);
  background-attachment: fixed;
  color: var(--theme-body-text);
}

/* Display font on headings/numbers, sitewide (2026-08-07) - Space
   Grotesk's real weight range is 300-700 (no 800), so anything that
   used to be fw-bold 800 needs to drop to 700 here or the browser
   silently synthesizes/falls back instead of actually using the
   loaded font at an unsupported weight. .fw-bold itself is Bootstrap's
   own utility (font-weight:700 already) so it doesn't need touching -
   only the handful of custom 800 rules built across the dashboard/
   shell CSS this session do. */
h1, h2, h3, h4, h5, h6,
.app-stat-num, .stat-mini-num, .pipeline-tile .num,
.topbar-title, .page-header-card h1 {
  font-family: var(--font-display);
  font-weight: 700;
}
/* Tabular figures on every $/count display (2026-08-07) - a real
   accounting-software detail now that this system includes a real
   accounting module, not just decoration: columns of numbers align
   vertically instead of each digit having proportional width. */
.app-stat-num, .stat-mini-num, .pipeline-tile .num,
.mystores-mini-table td {
  font-variant-numeric: tabular-nums;
}

.card, .navbar, .dropdown-menu, .modal-content {
  background-color: var(--theme-body-background);
  color: var(--theme-body-text);
  border-color: var(--theme-border);
}

.text-muted, .text-secondary {
  color: var(--theme-text-muted) !important;
}

/* Unified 2026-09-06 (Allie: "unification of button site wide.. i want
   all buttons to have the outline [look]") - .btn-primary reuses the
   EXACT SAME role mapping .btn-store-theme already uses below (fill =
   button_body, border/text = button_border, full swap on hover/active),
   not a separate "outline mode" formula - .btn-outline-primary gets the
   identical rule too, so there is only ever ONE real button style, on
   the storefront and in the admin alike. The reason every button now
   READS as outline isn't new CSS - it's that Generate Theme (see
   mystores/stores/layout.php) generates button_body as a light, muted
   tone close to body_background and keeps button_border vivid, so the
   same fill/border-swap formula that always existed here naturally
   produces an outline look, instead of the old vivid+vivid pairing that
   made every button read as a solid block regardless of which Bootstrap
   class was used. This also fixes the dark-mode contrast bug found
   live (fill and text were sometimes too close in lightness to read) -
   fill and text are now deliberately far apart by construction. */
.btn-primary, .btn-outline-primary {
  /* border-color = button_border (matches TEXT, not the fill) - a copy
     error found live 2026-09-06: it was set to button_body (same as
     the fill), making the border invisible and leaving text floating
     with no visible outline box at all. .btn-store-theme below already
     had this right; this rule just needed to actually match it. */
  --bs-btn-bg: var(--theme-button-body);
  --bs-btn-border-color: var(--theme-button-border);
  --bs-btn-border-width: 2px;
  --bs-btn-color: var(--theme-button-border);
  --bs-btn-hover-bg: var(--theme-button-border);
  --bs-btn-hover-border-color: var(--theme-button-border);
  --bs-btn-hover-color: var(--theme-button-body);
  --bs-btn-active-bg: var(--theme-button-border);
  --bs-btn-active-border-color: var(--theme-button-border);
  --bs-btn-active-color: var(--theme-button-body);
  font-weight: 600;
}
/* "Armed"/toggled-on state (2026-09-06, Allie: "if you need an armed
   button, just reverse the rest and hover settings") - permanently
   applies the same swapped colours :hover/:active already use, as a
   real persistent state rather than a different Bootstrap variant.
   admin/stock/item-zones.php's ruler-mode toggle uses this instead of
   switching between btn-primary/btn-outline-secondary. */
.btn-primary.is-armed, .btn-outline-primary.is-armed {
  --bs-btn-bg: var(--theme-button-border);
  --bs-btn-border-color: var(--theme-button-border);
  --bs-btn-color: var(--theme-button-body);
}
.btn { border-radius: 10px; }

/* ---------- storefront button theme (2026-07-31) ---------- */
/* Deliberate resting/hover COLOUR SWAP, not two separate stored
   states - body becomes border and vice versa on hover/active. Same
   pattern the Layout tab's own live-preview button demonstrates
   interactively (mystores/stores/layout.php). Text colour always
   matches whichever colour is currently the border (Allie's own
   request, 2026-07-31 - "can the text be the button border colour" -
   the theme_tokens description for button_border was relabelled
   "Button Text/Border" for the same reason), so it swaps alongside
   background/border rather than staying fixed and losing contrast
   once the background catches up to it. */
.btn-store-theme {
  background-color: var(--theme-button-body);
  border: 2px solid var(--theme-button-border);
  color: var(--theme-button-border);
  font-weight: 600;
  border-radius: 10px;
  padding: 0.5rem 1.25rem;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.btn-store-theme:hover,
.btn-store-theme:active,
.btn-store-theme:focus {
  background-color: var(--theme-button-border);
  border-color: var(--theme-button-body);
  color: var(--theme-button-body);
}

/* Store nav/body link hover (2026-07-31, Allie's own question - "what
   happens to the menu and Body links on hover? do we have a process
   for that?"). Both classes shift to --theme-banner-background (Colour
   A's own vivid rendering) on hover - the same "shift to the other
   brand colour" interactive language the button swap already uses,
   rather than inventing a third behaviour. */
.store-menu-link {
  color: var(--theme-menu-text, #ffffff);
  transition: color 0.15s ease;
}
.store-menu-link:hover,
.store-menu-link:focus {
  color: var(--theme-banner-background);
}
.store-body-link {
  color: var(--theme-body-link);
  transition: color 0.15s ease;
}
.store-body-link:hover,
.store-body-link:focus {
  color: var(--theme-banner-background);
}

/* Storefront surfaces - menu_bar/page_background/body_background/
   body_text (2026-08-11) - these 4 store tokens are editable on the
   Layout tab and resolve into --theme-* vars for the real storefront
   markup. */
.store-menu-bar { background-color: var(--theme-menu-bar, #c0c0c0); }
.store-page-bg { background-color: var(--theme-page-background, #000000); }
.store-body-bg {
  background-color: var(--theme-body-background, #ffffff);
  color: var(--theme-body-text, #000000);
}
.store-body-text { color: var(--theme-body-text, #000000); }

/* ---------- header / navbar ---------- */
.navbar-brand img { height: 60px; }
.navbar-mms {
  /* Light gradient (body-background -> a light brand-tint wash), not a
     vivid brand fill - text below flips to dark to match, same as the
     modal header (superseded 2026-07-28, first tried a bold-magenta
     version, wrong direction entirely). */
  background: linear-gradient(90deg, var(--theme-body-background) 0%, var(--theme-accent-tint) 100%);
}

/* Gradient modal headers, applied globally so every modal (this one,
   confirmDialog in footer.php, and any future one) gets it automatically
   instead of each page inline-styling its own - owner reference:
   legacy/accounting's Edit Card modal. Light gradient, so header
   text/close button need to be DARK here, not white - this is the
   opposite of a vivid brand-fill bar, which would need light text
   instead. */
.modal-header {
  background: linear-gradient(90deg, var(--theme-body-background) 0%, var(--theme-accent-tint) 100%);
  color: var(--theme-body-text);
  border-bottom: none;
}
.modal-header .modal-title, .modal-header h1, .modal-header h2, .modal-header h3 { color: var(--theme-body-text); }
/* Close button needs to flip with light/dark mode, not be one fixed
   colour - dark X on the light-mode gradient, white X on the dark-mode
   gradient. Tied to the same data-bs-theme attribute Theme::currentMode()
   already sets on <html>, rather than a separate token, since it's a
   direct function of which mode is active, not an independent style
   choice. */
.modal-header .btn-close { filter: none; }
[data-bs-theme="dark"] .modal-header .btn-close { filter: invert(1) grayscale(100%) brightness(200%); }

/* Every modal scrolls internally instead of overflowing the viewport -
   applied unconditionally (not just a mobile media query), since the
   owner hit this exact bug on desktop too, 2026-07-28. Went through
   several attempts on this same bug the same day - fullscreen class,
   then modal-dialog-scrollable, then a hand-rolled flex/max-height
   version using `max-height: inherit` on .modal-content - the last one
   was still reportedly broken even after the actual missing overflow:
   hidden was added, which turned out to most likely be
   assets/css/theme.css.php never having a cache-busting query string
   (fixed separately in header.php, 2026-07-28) - i.e. the fix was
   probably fine and just never reached the browser. Rewritten once
   more anyway to remove any dependency on `inherit` resolving
   correctly, since that's one more thing that can't silently fail this
   way: both .modal-dialog and .modal-content get the SAME literal
   calc() twice each (plain vh first, dvh second - later declaration
   wins where supported, older browsers just keep the vh version,
   avoiding any @supports scoping too). min-height: 0 on modal-body
   specifically matters - flex children default to min-height:auto,
   which silently blocks internal scrolling from ever kicking in
   regardless of overflow-y. */
.modal-dialog {
  max-height: calc(100vh - 3.5rem);
  max-height: calc(100dvh - 3.5rem);
}
.modal-content {
  max-height: calc(100vh - 3.5rem);
  max-height: calc(100dvh - 3.5rem);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* THE actual bug, found via dev tools computed-styles inspection,
   2026-07-28 (screenshot showed max-height/overflow correctly applied
   to .modal-content, so the constraint itself was always working) -
   every modal here wraps modal-header/modal-body/modal-footer in a
   <form>, and .modal-content's flex rules only reach its DIRECT
   children. The form itself was a plain block element with no flex
   behaviour of its own, so header/body/footer were never actually
   arranged as flex items at all - overflow:hidden on modal-content
   just silently clipped whatever didn't fit, with no scroll happening
   anywhere. The form needs the same flex treatment, passed through. */
.modal-content > form {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1 1 auto;
  overflow: hidden;
}
.modal-header, .modal-footer { flex-shrink: 0; }
.modal-body {
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}
/* Dark grey text (owner request, 2026-07-28) - now that the navbar is
   the light gradient, white text/icons would be as invisible as the
   dark X was on the modal header before that got fixed. var(--theme-
   body-text) rather than a fixed "dark grey", so it still flips
   correctly in dark mode instead of needing its own separate rule
   there. */
.navbar-mms .nav-link { color: var(--theme-body-text); }
.navbar-mms .nav-link:hover, .navbar-mms .nav-link:focus { color: var(--theme-button-border); text-decoration: underline; }
.navbar-mms .btn-header-cta {
  background: var(--theme-button-border);
  color: #ffffff;
  font-weight: 600;
}
.navbar-mms .btn-header-cta:hover { background: var(--theme-button-body); color: var(--theme-button-border); }
.navbar-mms .navbar-toggler { border-color: var(--theme-border); }
.navbar-mms .navbar-toggler-icon {
  /* Stroke colour interpolated server-side from the resolved
     body_text token (this is theme.css.php, a PHP file, not static
     CSS) - a data-URI SVG can't reference a CSS custom property
     directly, so the actual hex has to be baked in at render time
     instead. */
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23000000' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}

/* ---------- data tables (owner reference: legacy/accounting's
   register/list tables - not just the colours, the whole density/type
   treatment: condensed monospace, small text, tight rows) ---------- */
.table {
  color: var(--theme-body-text);
  background: var(--theme-body-background);
  border-color: var(--theme-border);
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
  font-size: 0.72rem;
}
.table thead th {
  background: var(--theme-body-background);
  color: var(--theme-body-text);
  border-color: var(--theme-border);
  font-size: 0.68rem;
  font-weight: 700;
  padding: 0.3rem 0.4rem;
  position: sticky;
  top: 0;
  z-index: 10;
  box-shadow: inset 0 -1px 0 var(--theme-border);
}
.table > :not(caption) > * > * {
  background: var(--theme-body-background);
  color: var(--theme-body-text);
  border-bottom-color: var(--theme-border);
  padding: 0.2rem 0.4rem;
}

/* Even tighter on phones (owner: "make the table smaller so it fits
   the mobile viewport") - stacks with the icon-only headers above and
   DataTables Responsive column-collapsing to fit a real screen width. */
@media (max-width: 767.98px) {
  .table { font-size: 0.62rem; }
  .table thead th { font-size: 0.6rem; padding: 0.25rem 0.3rem; }
  .table > :not(caption) > * > * { padding: 0.15rem 0.3rem; }
  .table-row-icon-action { font-size: 0.95rem; }
}
.table-hover > tbody > tr:hover > * {
  background: var(--theme-row-hover) !important;
}
.table-striped > tbody > tr:nth-of-type(odd) > * {
  background: var(--theme-body-background);
}

/* Bare, chrome-free row-action icons (owner reference: legacy's
   fa-circle-arrow-right per row, no button box around it) - use on any
   table row action instead of a bordered btn-outline-* button. */
.table-row-icon-action {
  font-size: 1.05rem;
  cursor: pointer;
  color: var(--theme-button-body);
}
.table-row-icon-action:hover { color: var(--theme-button-border); }
.table-row-icon-action.danger { color: #b02a37; font-size: 0.85rem; }
.table-row-icon-action.danger:hover { color: #7a1d27; }
/* These are <i role="button" tabindex="0"> glyphs, not real <button>/<a>
   elements - a plain browser default outline on something this small is
   easy to miss, so make keyboard focus unmistakable. :focus-visible
   (not :focus) so it only shows for keyboard nav, not every mouse click. */
.table-row-icon-action:focus-visible,
.table-row-icon-action-btn:focus-visible {
  outline: 2px solid var(--theme-button-body);
  outline-offset: 2px;
  border-radius: 3px;
}

/* Same bare chrome-free look as .table-row-icon-action, for cases that
   need a real <button type="submit"> instead of a bare <i> (e.g. a
   row action that navigates via its own form post, not a JS-handled
   click) - strips Bootstrap's default button border/background/padding
   so it reads identically to the icon-only actions beside it. */
.table-row-icon-action-btn {
  background: none;
  border: none;
  padding: 0 0.15rem;
  font-size: 1.05rem;
  line-height: 1;
}
.table-row-icon-action-btn:hover { opacity: 0.75; }

/* ---------- form controls (owner reference: legacy/accounting's Edit
   Card modal - flat, softly-filled fields with no visible border at
   rest, rather than a boxed/outlined look; also dark-mode aware -
   inputs stayed white before this, unreadable against a dark page) */
.form-control, .form-select {
  background-color: rgba(127, 127, 127, 0.08);
  color: var(--theme-body-text);
  border: 1px solid transparent;
  border-radius: 8px;
}
.form-control:focus, .form-select:focus {
  background-color: var(--theme-body-background);
  color: var(--theme-body-text);
  border-color: var(--theme-button-body);
  box-shadow: 0 0 0 0.15rem rgba(var(--bs-primary-rgb), 0.2);
}
.form-control::placeholder { color: var(--theme-text-muted); opacity: 0.7; }
.form-label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--theme-text-muted);
}

/* Checkboxes/radios were left on Bootstrap's default border (a faint
   rgba(0,0,0,.25) outline), which nearly disappears against a bg-light
   box - found 2026-07-31 testing the T&C checkboxes on register.php,
   which were genuinely hard to spot. Bigger, clearly-visible border at
   rest, filled solid in the theme's button colour once checked. */
.form-check-input {
  width: 1.2em;
  height: 1.2em;
  border: 2px solid var(--theme-text-muted, rgba(0, 0, 0, 0.5));
}
.form-check-input:checked {
  background-color: var(--theme-button-border);
  border-color: var(--theme-button-border);
}
.form-check-input:focus {
  border-color: var(--theme-button-border);
  box-shadow: 0 0 0 0.15rem rgba(var(--bs-primary-rgb), 0.25);
}

/* Drag-and-drop image upload box (owner reference: legacy/mms's
   Dropzone.js-based upload screens) - built native/no external library,
   since this app's forms are single full-submit POSTs, not Dropzone's
   own async per-file upload queue. Click-to-browse always works too;
   drag-and-drop is a bonus on top, not the only way in. See
   wireDropzone() in footer.php for the JS half. */
.dropzone-upload {
  position: relative;
  border: 2px dashed var(--theme-border, rgba(127, 127, 127, 0.35));
  border-radius: 8px;
  min-height: 120px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background-color: rgba(127, 127, 127, 0.04);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.dropzone-upload:hover, .dropzone-upload.dropzone-dragover {
  border-color: var(--theme-button-body);
  background-color: rgba(var(--bs-primary-rgb), 0.06);
}
.dropzone-upload input[type="file"] {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
  /* 2026-09-16, real bug found live-testing: being on top and full-size,
     this input (not the dropzone div) was the actual drag target - and
     a bare <input type="file" accept="..."> applies its own native
     drag-validation cursor before any JS dragover handler runs, which
     for a plain OS file drag often can't confirm the type yet and shows
     "not allowed" regardless of wireDropzone()'s own preventDefault().
     pointer-events: none hands EVERY pointer/drag interaction to the
     div beneath instead - click-to-browse still works via its existing
     dropzoneEl click listener (inputEl.click()), and drag/drop now runs
     through wireDropzone()'s own handlers with nothing native to
     override them. */
  pointer-events: none;
}
.dropzone-upload .dropzone-preview {
  width: 100%;
  height: 100%;
  min-height: 120px;
  object-fit: cover;
  display: none;
}
.dropzone-upload.has-preview .dropzone-preview { display: block; }
.dropzone-upload.has-preview .dropzone-placeholder { display: none; }
.dropzone-upload .dropzone-placeholder {
  text-align: center;
  padding: 1rem;
  pointer-events: none;
}

/* Zone/override markers on item-zones.php's reference photo (the ones
   NOT currently open in the Edit panel - that one gets #zoneDragBox's
   own drag/resize handles instead) - each is a real <button> inside a
   tiny position:absolute <form>, so clicking anywhere on a zone opens
   it for editing. Without this reset, a bare <button> would show its
   native browser chrome (padding/border/font) instead of just being
   the invisible/tinted hit-area the inline width/height/background
   already define. */
.zone-marker-btn {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}

/* Free-text autocomplete (owner reference: legacy/mms's Add Colour
   screen) - not a native <datalist>, since a browser's own datalist
   popup can't be styled to match this big scrollable list look. See
   wireAutocomplete() in footer.php for the JS half. */
.autocomplete-wrap {
  position: relative;
}
/* Site-wide drag-and-drop upload zones - see
   assets/js/drag-drop-uploads.js (2026-07-29), which wraps every
   input[type=file] in a .drag-drop-zone automatically. */
.drag-drop-zone {
  border: 2px dashed var(--theme-button-body);
  border-radius: 10px;
  padding: 12px;
  text-align: center;
  transition: background-color 0.15s, border-color 0.15s;
}
.drag-drop-zone.drag-drop-active {
  background-color: color-mix(in srgb, var(--theme-button-body) 10%, transparent);
  border-color: var(--theme-button-body);
}
.drag-drop-zone input[type="file"] {
  cursor: pointer;
}
.drag-drop-hint {
  margin-top: 6px;
  pointer-events: none;
}
.drag-drop-filename {
  margin-top: 6px;
  color: var(--theme-button-body);
}

.autocomplete-list {
  display: none;
  position: absolute;
  z-index: 1080;
  top: 100%;
  left: 0;
  right: 0;
  max-height: 260px;
  overflow-y: auto;
  margin-top: 2px;
  background: var(--theme-body-background, #fff);
  border: 1px solid rgba(127, 127, 127, 0.35);
  border-radius: 6px;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
}
.autocomplete-list.show {
  display: block;
}
.autocomplete-item {
  padding: 0.45rem 0.9rem;
  cursor: pointer;
}
.autocomplete-item:hover,
.autocomplete-item.active {
  background: rgba(var(--bs-primary-rgb), 0.12);
}
