/*
 * Copyright (c)  2009, Tracmor, LLC
 *
 * This file is part of Tracmor.
 *
 * Tracmor is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Tracmor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tracmor; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/* Self-hosted Inter (the UI's intended typeface). Previously 'Inter' was named
   in the font stack but never loaded, so browsers fell back to a system font —
   and Firefox/Zen would paint a generic fallback first, then re-resolve the
   stack, causing a brief text "resize" on every page load. font-display:optional
   fixes that: the browser uses Inter only if it's ready in time and NEVER swaps
   mid-render (no flash, no layout shift). The font is preloaded in the page
   <head> so on a warm cache it's ready before first paint. Variable weight axis
   covers every weight the UI uses (400–800). */
@font-face {
	font-family: 'Inter';
	font-style: normal;
	font-weight: 100 900;
	font-display: optional;
	src: url('fonts/inter-latin-variable.woff2') format('woff2');
}

/* =========================================================================
   Design tokens
   ========================================================================= */
:root {
	/* Type */
	--font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI',
		Roboto, 'Helvetica Neue', Arial, sans-serif;
	--font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
		'Liberation Mono', monospace;

	--text-xs:   11px;
	--text-sm:   12px;
	--text-base: 14px;
	--text-md:   15px;
	--text-lg:   16px;
	--text-xl:   18px;
	--text-2xl:  20px;
	--text-3xl:  24px;

	--leading-tight:  1.25;
	--leading-normal: 1.5;

	--weight-regular:  400;
	--weight-medium:   500;
	--weight-semibold: 600;
	--weight-bold:     700;

	/* Spacing scale */
	--space-1:  4px;
	--space-2:  8px;
	--space-3:  12px;
	--space-4:  16px;
	--space-5:  20px;
	--space-6:  24px;
	--space-8:  32px;
	--space-10: 40px;

	/* Radii */
	--radius-sm: 4px;
	--radius-md: 6px;
	--radius-lg: 8px;
	--radius-xl: 12px;

	/* Shadows */
	--shadow-sm: 0 1px 2px 0 rgba(15, 23, 42, 0.05);
	--shadow-md: 0 4px 6px -1px rgba(15, 23, 42, 0.08),
				 0 2px 4px -2px rgba(15, 23, 42, 0.06);
	--shadow-lg: 0 10px 15px -3px rgba(15, 23, 42, 0.10),
				 0 4px 6px -4px rgba(15, 23, 42, 0.05);

	/* Neutral palette. Renamed-in-spirit from the original blue-tinted "slate" to a
	   TRUE neutral gray (R=G=B) so the default Light/Dark themes read as grayscale,
	   not blue. The token is still called --slate-* (used app-wide); only the values
	   changed. Colorful themes override the structural role tokens with their own
	   hue, so this only neutralizes the default theme (their incidental neutrals —
	   muted text, borders — just become true-gray, which is imperceptible). */
	--slate-50:  #fafafa;
	--slate-100: #f5f5f5;
	--slate-200: #e5e5e5;
	--slate-300: #d4d4d4;
	--slate-400: #a3a3a3;
	--slate-500: #737373;
	--slate-600: #525252;
	--slate-700: #404040;
	--slate-800: #262626;
	--slate-900: #171717;

	/* Brand accent. The default Light/Dark themes are intentionally GRAYSCALE
	   (neutral slate), so the accent ramp mirrors the slate ramp — buttons, links,
	   active states and focus rings are all neutral gray. Colorful themes (incl.
	   Cobalt) override the role tokens (--accent/--link/--sidebar-active/…) with
	   their own hue, so they are unaffected by this. */
	--accent-50:  var(--slate-50);
	--accent-100: var(--slate-100);
	--accent-200: var(--slate-200);
	--accent-300: var(--slate-300);
	--accent-400: var(--slate-400);
	--accent-500: var(--slate-500);
	--accent-600: var(--slate-600);
	--accent-700: var(--slate-700);
	--accent-800: var(--slate-800);
	--accent-900: var(--slate-900);

	/* Semantic colors */
	--success-50:  #f0fdf4;
	--success-500: #16a34a;
	--success-600: #15803d;
	--success-700: #166534;

	--warning-50:  #fffbeb;
	--warning-500: #f59e0b;
	--warning-600: #d97706;
	--warning-700: #b45309;

	--danger-50:  #fef2f2;
	--danger-500: #dc2626;
	--danger-600: #b91c1c;
	--danger-700: #991b1b;

	/* Semantic role tokens (light theme) */
	--bg:            var(--slate-50);
	--surface:       #ffffff;
	--surface-alt:   var(--slate-100);
	--surface-muted: var(--slate-100);
	--border:        var(--slate-200);
	--border-strong: var(--slate-300);
	--text:          var(--slate-800);
	--text-muted:    var(--slate-600);
	--text-subtle:   var(--slate-400);
	--link:          var(--accent-600);
	--link-hover:    var(--accent-700);
	--accent:        var(--accent-600);
	--accent-hover:  var(--accent-700);
	--focus-ring:    0 0 0 3px rgba(82, 82, 82, 0.30);

	/* Component tokens */
	--input-bg:      var(--surface);
	--input-border:  var(--slate-300);
	--input-focus:   var(--accent-500);
	--input-radius:  var(--radius-md);
	--input-padding: 6px 10px;

	--button-radius: var(--radius-md);

	--row-hover:     var(--slate-200);
	--row-alt:       var(--slate-100);
	--row-border:    var(--slate-200);
	--header-bg:     var(--slate-100);

	/* Sidebar (always a dark panel). Tokenised so colorful themes can tint it;
	   defaults reproduce the original slate sidebar exactly. */
	--sidebar-bg:     var(--slate-900);
	--sidebar-edge:   var(--slate-800);
	--sidebar-hover:  var(--slate-800);
	--sidebar-active: var(--accent-700);
	--sidebar-link:   var(--slate-300);
	--sidebar-muted:  var(--slate-400);
}

/* =========================================================================
   Dark theme — remaps the role tokens only; every component already consumes
   these, so no component rule needs to change. The fixed slate/accent/semantic
   palettes above are intentionally NOT remapped.
   ========================================================================= */
/* Auto dark: applies only when the user has NOT explicitly chosen a theme on
   their settings page (no data-theme attribute). When they pick Light or Dark,
   html[data-theme] is set and this @media block no longer matches. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme]) {
		/* slate-950, so the always-dark sidebar (slate-900) still reads above it */
		--bg:            #020617;
		--surface:       var(--slate-800);
		--surface-alt:   var(--slate-700);
		--surface-muted: var(--slate-700);
		--border:        var(--slate-700);
		--border-strong: var(--slate-600);
		--text:          var(--slate-100);
		--text-muted:    var(--slate-400);
		--text-subtle:   var(--slate-500);
		--link:          var(--accent-300);
		--link-hover:    var(--accent-200);
		--accent:        var(--accent-500);
		--accent-hover:  var(--accent-400);
		--focus-ring:    0 0 0 3px rgba(163, 163, 163, 0.42);

		--input-bg:      var(--slate-800);
		--input-border:  var(--slate-600);

		--row-hover:     var(--slate-600);
		--row-alt:       var(--slate-700);
		--row-border:    var(--slate-700);
		--header-bg:     var(--slate-700);

		--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.40);
		--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.50),
					 0 2px 4px -2px rgba(0, 0, 0, 0.40);
		--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.55),
					 0 4px 6px -4px rgba(0, 0, 0, 0.40);
	}
}

/* Explicit dark variants. This shared dark base (neutral slate surfaces, light
   text, deeper shadows — matching the auto-dark @media block above) applies to
   plain "Dark" AND every colorful "*-dark"; each family below then only recolors
   the accent + sidebar on top. "Light" needs no block (the default :root). */
:root[data-theme="dark"],
:root[data-theme="ocean-dark"],
:root[data-theme="forest-dark"],
:root[data-theme="grape-dark"],
:root[data-theme="sunset-dark"],
:root[data-theme="rose-dark"],
:root[data-theme="cobalt-dark"],
:root[data-theme="lime-dark"],
:root[data-theme="bubblegum-dark"],
:root[data-theme="rainbow-dark"] {
	--bg:            #020617;
	--surface:       var(--slate-800);
	--surface-alt:   var(--slate-700);
	--surface-muted: var(--slate-700);
	--border:        var(--slate-700);
	--border-strong: var(--slate-600);
	--text:          var(--slate-100);
	--text-muted:    var(--slate-400);
	--text-subtle:   var(--slate-500);
	--link:          var(--accent-300);
	--link-hover:    var(--accent-200);
	--accent:        var(--accent-500);
	--accent-hover:  var(--accent-400);
	--focus-ring:    0 0 0 3px rgba(163, 163, 163, 0.42);

	--input-bg:      var(--slate-800);
	--input-border:  var(--slate-600);

	--row-hover:     var(--slate-600);
	--row-alt:       var(--slate-700);
	--row-border:    var(--slate-700);
	--header-bg:     var(--slate-700);

	--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.40);
	--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.50),
				 0 2px 4px -2px rgba(0, 0, 0, 0.40);
	--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.55),
				 0 4px 6px -4px rgba(0, 0, 0, 0.40);
}

/* =========================================================================
   Colorful themes — five families, each with a Light and a Dark variant.
   LIGHT variants override the accent + a subtle background tint + the sidebar
   on top of the default light :root. DARK variants inherit the shared dark base
   above and only recolor the accent + sidebar (brighter accents read better on
   dark). Picker values come from $GLOBALS['TRACMOR_THEMES'] (prepend.inc.php).
   ========================================================================= */

/* ---- Ocean (teal) ---- */
:root[data-theme="ocean-light"] {
	--accent: #0891b2; --accent-hover: #0e7490;
	--link: #0e7490; --link-hover: #155e75;
	--input-focus: #0891b2; --focus-ring: 0 0 0 3px rgba(8, 145, 178, 0.32);
	--bg: #eef9fc;
	--surface-alt: #ddf0f7; --surface-muted: #ddf0f7; --header-bg: #ddf0f7;
	--row-hover: #eef9fc; --row-alt: #f4fbfd;
	--border: #c2e5ef; --border-strong: #9bd2e2; --input-border: #bbdfea;
	--sidebar-bg: #0b2a36; --sidebar-edge: #11414f; --sidebar-hover: #11414f; --sidebar-active: #0e7490;
}
:root[data-theme="ocean-dark"] {
	--bg: #061e26;
	--surface: #0c2c37; --surface-alt: #133a48; --surface-muted: #133a48;
	--border: #1f4857; --border-strong: #2d5f71;
	--input-bg: #0c2c37; --input-border: #2d5f71;
	--row-hover: #133a48; --row-alt: #0f3340; --row-border: #1f4857; --header-bg: #133a48;
	--accent: #22b8d6; --accent-hover: #46c7e0;
	--link: #7dd3e8; --link-hover: #a9e4f2;
	--input-focus: #22b8d6; --focus-ring: 0 0 0 3px rgba(34, 184, 214, 0.45);
	--sidebar-bg: #08222c; --sidebar-edge: #123c49; --sidebar-hover: #123c49; --sidebar-active: #0e7490;
}

/* ---- Forest (green) ---- */
:root[data-theme="forest-light"] {
	--accent: #16a34a; --accent-hover: #15803d;
	--link: #15803d; --link-hover: #166534;
	--input-focus: #16a34a; --focus-ring: 0 0 0 3px rgba(22, 163, 74, 0.28);
	--bg: #f1faf3;
	--surface-alt: #e2f4e7; --surface-muted: #e2f4e7; --header-bg: #e2f4e7;
	--row-hover: #f1faf3; --row-alt: #f5fbf6;
	--border: #c7e8d1; --border-strong: #a4d7b4; --input-border: #c0e6cb;
	--sidebar-bg: #0e2a18; --sidebar-edge: #1b4028; --sidebar-hover: #1b4028; --sidebar-active: #15803d;
}
:root[data-theme="forest-dark"] {
	--bg: #07190e;
	--surface: #0d2618; --surface-alt: #143422; --surface-muted: #143422;
	--border: #1e4830; --border-strong: #2c5e3f;
	--input-bg: #0d2618; --input-border: #2c5e3f;
	--row-hover: #143422; --row-alt: #102e1c; --row-border: #1e4830; --header-bg: #143422;
	--accent: #34d27f; --accent-hover: #54dd93;
	--link: #6ee7a8; --link-hover: #9bf0c3;
	--input-focus: #34d27f; --focus-ring: 0 0 0 3px rgba(52, 210, 127, 0.40);
	--sidebar-bg: #0a2114; --sidebar-edge: #173823; --sidebar-hover: #173823; --sidebar-active: #15803d;
}

/* ---- Grape (purple) ---- */
:root[data-theme="grape-light"] {
	--accent: #7c3aed; --accent-hover: #6d28d9;
	--link: #6d28d9; --link-hover: #5b21b6;
	--input-focus: #7c3aed; --focus-ring: 0 0 0 3px rgba(124, 58, 237, 0.28);
	--bg: #f7f4fe;
	--surface-alt: #eee7fd; --surface-muted: #eee7fd; --header-bg: #eee7fd;
	--row-hover: #f7f4fe; --row-alt: #faf8fe;
	--border: #ddd2fb; --border-strong: #c6b2f6; --input-border: #d9ccf9;
	--sidebar-bg: #201a3a; --sidebar-edge: #312a54; --sidebar-hover: #312a54; --sidebar-active: #6d28d9;
}
:root[data-theme="grape-dark"] {
	--bg: #100c20;
	--surface: #1a1530; --surface-alt: #251e44; --surface-muted: #251e44;
	--border: #332a5a; --border-strong: #443878;
	--input-bg: #1a1530; --input-border: #443878;
	--row-hover: #251e44; --row-alt: #1f193a; --row-border: #332a5a; --header-bg: #251e44;
	--accent: #a78bfa; --accent-hover: #b9a4fb;
	--link: #c4b5fd; --link-hover: #ddd6fe;
	--input-focus: #a78bfa; --focus-ring: 0 0 0 3px rgba(167, 139, 250, 0.45);
	--sidebar-bg: #17122a; --sidebar-edge: #2c2550; --sidebar-hover: #2c2550; --sidebar-active: #6d28d9;
}

/* ---- Sunset (orange) ---- */
:root[data-theme="sunset-light"] {
	--accent: #ea580c; --accent-hover: #c2410c;
	--link: #c2410c; --link-hover: #9a3412;
	--input-focus: #ea580c; --focus-ring: 0 0 0 3px rgba(234, 88, 12, 0.26);
	--bg: #fff7f1;
	--surface-alt: #ffecdc; --surface-muted: #ffecdc; --header-bg: #ffecdc;
	--row-hover: #fff7f1; --row-alt: #fffaf6;
	--border: #fbd6b9; --border-strong: #f7bc91; --input-border: #fad0ac;
	--sidebar-bg: #2a1709; --sidebar-edge: #422410; --sidebar-hover: #422410; --sidebar-active: #c2410c;
}
:root[data-theme="sunset-dark"] {
	--bg: #1a0d04;
	--surface: #271509; --surface-alt: #382011; --surface-muted: #382011;
	--border: #492c18; --border-strong: #5f3b22;
	--input-bg: #271509; --input-border: #5f3b22;
	--row-hover: #382011; --row-alt: #2f1b0d; --row-border: #492c18; --header-bg: #382011;
	--accent: #fb923c; --accent-hover: #fdba74;
	--link: #fdba74; --link-hover: #fed7aa;
	--input-focus: #fb923c; --focus-ring: 0 0 0 3px rgba(251, 146, 60, 0.40);
	--sidebar-bg: #221206; --sidebar-edge: #40220d; --sidebar-hover: #40220d; --sidebar-active: #c2410c;
}

/* ---- Rose (pink) ---- */
:root[data-theme="rose-light"] {
	--accent: #e11d48; --accent-hover: #be123c;
	--link: #be123c; --link-hover: #9f1239;
	--input-focus: #e11d48; --focus-ring: 0 0 0 3px rgba(225, 29, 72, 0.26);
	--bg: #fff5f7;
	--surface-alt: #ffe5eb; --surface-muted: #ffe5eb; --header-bg: #ffe5eb;
	--row-hover: #fff5f7; --row-alt: #fff8fa;
	--border: #fbcfda; --border-strong: #f6a6bc; --input-border: #f9c5d2;
	--sidebar-bg: #2b0f1a; --sidebar-edge: #44182a; --sidebar-hover: #44182a; --sidebar-active: #be123c;
}
:root[data-theme="rose-dark"] {
	--bg: #1a0810;
	--surface: #290f19; --surface-alt: #3a1626; --surface-muted: #3a1626;
	--border: #4c2032; --border-strong: #642b43;
	--input-bg: #290f19; --input-border: #642b43;
	--row-hover: #3a1626; --row-alt: #31111d; --row-border: #4c2032; --header-bg: #3a1626;
	--accent: #fb7185; --accent-hover: #fda4af;
	--link: #fda4af; --link-hover: #fecdd3;
	--input-focus: #fb7185; --focus-ring: 0 0 0 3px rgba(251, 113, 133, 0.40);
	--sidebar-bg: #240b13; --sidebar-edge: #431826; --sidebar-hover: #431826; --sidebar-active: #be123c;
}

/* ---- Cobalt (electric blue) ---- */
:root[data-theme="cobalt-light"] {
	--accent: #2563eb; --accent-hover: #1d4ed8;
	--link: #1d4ed8; --link-hover: #1e40af;
	--input-focus: #2563eb; --focus-ring: 0 0 0 3px rgba(37, 99, 235, 0.28);
	--bg: #eff5ff;
	--surface-alt: #dfeaff; --surface-muted: #dfeaff; --header-bg: #dfeaff;
	--row-hover: #eff5ff; --row-alt: #f5f8ff;
	--border: #cdddfb; --border-strong: #a9c5f7; --input-border: #c7d9fa;
	--sidebar-bg: #0d1a33; --sidebar-edge: #16294d; --sidebar-hover: #16294d; --sidebar-active: #1d4ed8;
}
:root[data-theme="cobalt-dark"] {
	--bg: #0a1326;
	--surface: #0f1d35; --surface-alt: #16294a; --surface-muted: #16294a;
	--border: #21385e; --border-strong: #2e4a78;
	--input-bg: #0f1d35; --input-border: #2e4a78;
	--row-hover: #16294a; --row-alt: #12233f; --row-border: #21385e; --header-bg: #16294a;
	--accent: #3b82f6; --accent-hover: #60a5fa;
	--link: #93c5fd; --link-hover: #bfdbfe;
	--input-focus: #3b82f6; --focus-ring: 0 0 0 3px rgba(59, 130, 246, 0.45);
	--sidebar-bg: #0a1426; --sidebar-edge: #15233f; --sidebar-hover: #15233f; --sidebar-active: #1d4ed8;
}

/* ---- Lime (zesty yellow-green) ---- */
:root[data-theme="lime-light"] {
	--accent: #65a30d; --accent-hover: #4d7c0f;
	--link: #4d7c0f; --link-hover: #3f6212;
	--input-focus: #65a30d; --focus-ring: 0 0 0 3px rgba(101, 163, 13, 0.28);
	--bg: #f7fee7;
	--surface-alt: #ecf9cb; --surface-muted: #ecf9cb; --header-bg: #ecf9cb;
	--row-hover: #f7fee7; --row-alt: #fbffe9;
	--border: #d9ed9f; --border-strong: #c0e070; --input-border: #d4ea93;
	--sidebar-bg: #1a2008; --sidebar-edge: #2c3611; --sidebar-hover: #2c3611; --sidebar-active: #4d7c0f;
}
:root[data-theme="lime-dark"] {
	--bg: #0d1304;
	--surface: #141d07; --surface-alt: #1d2a0c; --surface-muted: #1d2a0c;
	--border: #2c4011; --border-strong: #3c5618;
	--input-bg: #141d07; --input-border: #3c5618;
	--row-hover: #1d2a0c; --row-alt: #172307; --row-border: #2c4011; --header-bg: #1d2a0c;
	--accent: #a3e635; --accent-hover: #bef264;
	--link: #bef264; --link-hover: #d9f99d;
	--input-focus: #a3e635; --focus-ring: 0 0 0 3px rgba(163, 230, 53, 0.40);
	--sidebar-bg: #141a06; --sidebar-edge: #29330e; --sidebar-hover: #29330e; --sidebar-active: #4d7c0f;
}
/* Lime's accent is bright, so its primary buttons use dark text (incl. hover) for
   legibility instead of the default white. The active nav item keeps white text —
   it sits on the dark --sidebar-active, not the bright accent. */
:root[data-theme="lime-light"] .btn--primary,
:root[data-theme="lime-dark"] .btn--primary,
:root[data-theme="lime-light"] .btn--primary:hover,
:root[data-theme="lime-dark"] .btn--primary:hover {
	color: #1a2e05;
	text-shadow: none;
}

/* ---- Bubblegum (fuchsia/magenta + purple) ----
   A candy pink-purple identity: a magenta-fuchsia accent over deep-purple
   chrome, deliberately distinct from Rose (red-pink) and Grape (blue-violet). */
:root[data-theme="bubblegum-light"] {
	--accent: #d946ef; --accent-hover: #c026d3;
	--link: #a21caf; --link-hover: #86198f;
	--input-focus: #d946ef; --focus-ring: 0 0 0 3px rgba(217, 70, 239, 0.28);
	--bg: #fdf4ff;
	--surface-alt: #fae8ff; --surface-muted: #fae8ff; --header-bg: #fae8ff;
	--row-hover: #fdf4ff; --row-alt: #fef7ff;
	--border: #f3d4fb; --border-strong: #eab8f7; --input-border: #f0cdf9;
	--sidebar-bg: #2c0f3d; --sidebar-edge: #43185a; --sidebar-hover: #43185a; --sidebar-active: #a21caf;
}
:root[data-theme="bubblegum-dark"] {
	--bg: #190a23;
	--surface: #251033; --surface-alt: #321542; --surface-muted: #321542;
	--border: #45205c; --border-strong: #5a2c75;
	--input-bg: #251033; --input-border: #5a2c75;
	--row-hover: #321542; --row-alt: #2b1239; --row-border: #45205c; --header-bg: #321542;
	--accent: #e879f9; --accent-hover: #f0abfc;
	--link: #f0abfc; --link-hover: #f5d0fe;
	--input-focus: #e879f9; --focus-ring: 0 0 0 3px rgba(232, 121, 249, 0.42);
	--sidebar-bg: #1e0a2b; --sidebar-edge: #3a1550; --sidebar-hover: #3a1550; --sidebar-active: #a21caf;
}

/* ---- Rainbow (multi-hue) ----
   The token blocks use a single readable violet anchor for text/links/borders/
   focus; the actual "rainbow" is layered onto pure-background elements below
   (primary buttons, the active nav item, the sidebar edge, the wordmark), because
   a CSS custom property holds one solid color, not a gradient. Both variants set
   their own surfaces (deep violet in dark) so they don't fall back to the neutral
   dark base. */
:root[data-theme="rainbow-light"] {
	--accent: #7c3aed; --accent-hover: #6d28d9;
	--link: #7c3aed; --link-hover: #6d28d9;
	--input-focus: #7c3aed; --focus-ring: 0 0 0 3px rgba(124, 58, 237, 0.28);
	--bg: #faf7ff;
	--surface-alt: #f3edff; --surface-muted: #f3edff; --header-bg: #f3edff;
	--row-hover: #faf7ff; --row-alt: #fdfbff;
	--border: #e7ddfb; --border-strong: #d4c4f7; --input-border: #e3d7fa;
	--sidebar-bg: #1b1430; --sidebar-edge: #2a2046; --sidebar-hover: #2a2046; --sidebar-active: #7c3aed;
}
:root[data-theme="rainbow-dark"] {
	--bg: #120a1f;
	--surface: #1a1030; --surface-alt: #241640; --surface-muted: #241640;
	--border: #34255a; --border-strong: #453576;
	--input-bg: #1a1030; --input-border: #453576;
	--row-hover: #241640; --row-alt: #1d1336; --row-border: #34255a; --header-bg: #241640;
	--accent: #a78bfa; --accent-hover: #c4b5fd;
	--link: #c4b5fd; --link-hover: #ddd6fe;
	--input-focus: #a78bfa; --focus-ring: 0 0 0 3px rgba(167, 139, 250, 0.45);
	--sidebar-bg: #140e24; --sidebar-edge: #261c44; --sidebar-hover: #261c44; --sidebar-active: #7c3aed;
}

/* Rainbow gradients on pure-background elements (shared by both rainbow variants).
   White text gets a soft shadow so it stays legible across every gradient stop. */
:root[data-theme="rainbow-light"] .btn--primary,
:root[data-theme="rainbow-dark"] .btn--primary,
:root[data-theme="rainbow-light"] .btn--primary:hover,
:root[data-theme="rainbow-dark"] .btn--primary:hover {
	background-image: linear-gradient(135deg, #e11d48, #ea580c, #16a34a, #2563eb, #7c3aed);
	background-color: transparent;
	border-color: transparent;
	color: #fff;
	text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
:root[data-theme="rainbow-light"] .btn--primary:hover,
:root[data-theme="rainbow-dark"] .btn--primary:hover { filter: brightness(1.08); }

:root[data-theme="rainbow-light"] .app-nav__link.is-active,
:root[data-theme="rainbow-dark"] .app-nav__link.is-active {
	background-image: linear-gradient(135deg, #e11d48, #ea580c, #16a34a, #2563eb, #7c3aed);
	color: #fff;
}

/* Rainbow accent strip down the sidebar's right edge. */
:root[data-theme="rainbow-light"] .app-sidebar,
:root[data-theme="rainbow-dark"] .app-sidebar {
	border-right: 3px solid transparent;
	border-image: linear-gradient(180deg, #e11d48, #ea580c, #16a34a, #2563eb, #7c3aed) 1;
}

/* Rainbow wordmark, when the text brand shows instead of a company-logo image. */
:root[data-theme="rainbow-light"] .app-sidebar__wordmark,
:root[data-theme="rainbow-dark"] .app-sidebar__wordmark {
	background: linear-gradient(135deg, #f43f5e, #f59e0b, #22c55e, #3b82f6, #a855f7);
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
}

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

* {
	font-family: var(--font-sans);
	box-sizing: border-box;
}

html, body {
	background: var(--bg);
	color: var(--text);
}

body {
	font-size: var(--text-base);
	line-height: var(--leading-normal);
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

th, td, body {
	font-size: var(--text-base);
	text-align: left;
}

a {
	color: var(--link);
	text-decoration: none;
}
a:hover { color: var(--link-hover); text-decoration: underline; }

/* =========================================================================
   Typographic utilities (existing classes)
   ========================================================================= */
.title {
	font-size: var(--text-2xl);
	color: var(--text);
	font-weight: var(--weight-semibold);
	margin-left: -2px;
	letter-spacing: -0.01em;
}
.title_action {
	font-size: var(--text-lg);
	font-weight: var(--weight-semibold);
	margin-bottom: -4px;
	margin-left: -2px;
	color: var(--text);
}
.warning { color: var(--danger-600); }
.instructions {
	font-size: var(--text-sm);
	font-style: italic;
	color: var(--text-muted);
}
.item_divider { line-height: 14px; }
.item_label {
	font-size: var(--text-base);
	padding-bottom: var(--space-1);
	vertical-align: top;
}
.item_label_disabled {
	font-size: var(--text-lg);
	padding-bottom: var(--space-1);
	padding-top: var(--space-1);
	color: var(--text-subtle);
	vertical-align: top;
}

.scrollBox {
	height: 48px;
	overflow: auto;
	border: 1px solid var(--border);
	background-color: var(--surface-muted);
	white-space: normal;
	border-radius: var(--radius-sm);
	padding: var(--space-1) var(--space-2);
}

.bluelink {
	color: var(--accent);
	font-weight: var(--weight-semibold);
}
.graylink {
	color: var(--text-muted);
	text-decoration: none;
}
.graylink:hover { color: var(--text); }

.greentext { color: var(--success-600); }
.redtext   { color: var(--danger-600); }

.spinner {
	vertical-align: middle;
	position: absolute;
}

.disclaimer {
	font-size: var(--text-xs);
	color: var(--text-muted);
}

/* =========================================================================
   Shortcuts panel
   ========================================================================= */
.shortcuts {
	margin-left: var(--space-1);
	background-color: var(--surface);
	border-collapse: collapse;
	border-spacing: 0;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	width: 160px;
	line-height: 1.8;
	overflow: hidden;
}
.shortcuts-title {
	width: 150px;
	background-color: var(--surface-alt);
	color: var(--text);
	font-size: var(--text-sm);
	font-weight: var(--weight-semibold);
	text-align: center;
	padding: var(--space-2) 0;
	letter-spacing: 0.02em;
	text-transform: uppercase;
}
.shortcuts th,
.shortcuts td {
	border: 0;
	border-collapse: collapse;
}
.shortcuts a {
	min-height: 100%;
	min-width: 100%;
	width: 100%;
	height: 100%;
	display: inline-block;
	color: var(--text);
	text-decoration: none;
	margin: 0;
}
.shortcuts-button {
	cursor: pointer;
	font-size: var(--text-sm);
	border: 0;
	transition: background-color 0.12s ease;
}
.shortcuts-button:hover {
	background-color: var(--accent-50);
	cursor: pointer;
}
.shortcuts-button-icon {
	padding: var(--space-1);
}
.shortcuts-button-label {}

/* =========================================================================
   Form controls
   ========================================================================= */
input[type="text"].textbox,
input[type="password"].textbox,
input[type="email"].textbox,
input[type="number"].textbox,
input.textbox,
textarea.textbox,
.textbox {
	font-size: var(--text-base);
	font-family: var(--font-sans);
	width: 250px;
	max-width: 100%;          /* never overflow its container on small/narrow screens */
	border: 1px solid var(--input-border);
	background: var(--input-bg);
	color: var(--text);
	padding: var(--input-padding);
	border-radius: var(--input-radius);
	transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
textarea.textbox { height: 72px; line-height: var(--leading-normal); }

input.textbox:focus,
textarea.textbox:focus,
.textbox:focus {
	outline: none;
	border-color: var(--input-focus);
	box-shadow: var(--focus-ring);
}

select.listbox,
.listbox {
	font-size: var(--text-base);
	font-family: var(--font-sans);
	width: 255px;
	max-width: 100%;
	border: 1px solid var(--input-border);
	background: var(--input-bg);
	color: var(--text);
	padding: 5px 8px;
	border-radius: var(--input-radius);
	transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
select.listbox:focus,
.listbox:focus {
	outline: none;
	border-color: var(--input-focus);
	box-shadow: var(--focus-ring);
}

/* =========================================================================
   Paginator (QPaginator)
   ========================================================================= */
span.paginator { margin: 0; padding: 0; }
span.paginator span {
	list-style-type: none;
	display: inline;
	padding: 0;
	margin: 0;
}
span.paginator span.page a {
	text-decoration: none;
	color: var(--text);
	padding: 2px 8px;
	margin: 0 2px;
	border-radius: var(--radius-sm);
	display: inline-block;
}
span.paginator span.page a:hover {
	background-color: var(--accent-50);
	color: var(--accent-700);
}
span.paginator span.arrow {
	font-weight: var(--weight-semibold);
	color: var(--text-subtle);
	margin: 0;
	padding: 2px 8px;
}
span.paginator span.arrow a {
	font-weight: var(--weight-semibold);
	color: var(--text);
	text-decoration: none;
}
span.paginator span.selected {
	font-weight: var(--weight-semibold);
	background-color: var(--accent);
	color: #fff;
	padding: 2px 8px;
	margin: 0 2px;
	border-radius: var(--radius-sm);
	border: 1px solid var(--accent);
}
span.paginator span.break { color: var(--text-muted); margin: 0 6px; }
span.paginator span.ellipsis { color: var(--text-muted); }

/* =========================================================================
   Datagrid
   ========================================================================= */
.datagrid {
	width: 99%;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	border-collapse: separate;
	border-spacing: 0;
	background: var(--surface);
	overflow: hidden;
}

.dtg_column {
	border-bottom: 1px solid var(--row-border);
	/* Tight padding + a capped width so more columns fit across the page. nowrap +
	   ellipsis keep every row a single uniform line (long values truncate and get a
	   title tooltip via JS in footer_modern.inc.php) instead of wrapping. The
	   responsive block below compacts these further on narrower screens. */
	padding: 6px 7px;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 240px;
}
/* Numeric / currency columns read better right-aligned (opt-in via CssClass). */
.dtg_column--num, .dtg_header--num { text-align: right; }
/* Muted dash dropped into visually-empty cells by the footer JS. */
.dtg-empty { color: var(--text-muted); }

.dtg_header {
	font-weight: var(--weight-semibold);
	/* Header bars match the bulk-actions bar (var(--surface)); the uppercase
	   bold text + strong bottom border keep the header distinct without a fill. */
	background-color: var(--surface);
	background-image: none;
	color: var(--text);
	border-top: 1px solid var(--border);
	border-bottom: 1px solid var(--border-strong);
	padding: 7px 7px;
	font-size: var(--text-sm);
	letter-spacing: 0.02em;
	text-transform: uppercase;
	white-space: nowrap;
}
.dtg_header a {
	color: var(--text);
	text-decoration: none;
}
.dtg_header a:hover { color: var(--accent); }

/* Progressive horizontal compaction so more columns fit as the viewport narrows
   (above the 960px mobile breakpoint, where the sidebar still occupies width).
   Shrinks cell padding, font size, header letter-spacing, and the per-column
   truncation cap — long values still ellipsis-truncate with a hover tooltip. */
@media (max-width: 1366px) {
	.dtg_column, .dtg_header { padding-left: 5px; padding-right: 5px; }
	.grid-card__scroll .dtg_column { max-width: 200px; }
}
@media (max-width: 1100px) {
	.dtg_column, .dtg_header { padding-left: 4px; padding-right: 4px; font-size: var(--text-xs); }
	.dtg_header { letter-spacing: 0; }
	.grid-card__scroll .dtg_column { max-width: 150px; }
}

/* Manual Compact density (body.density-compact, toggled in the grid bar and
   persisted in the tracmor_density cookie). Forces the densest styling at ANY
   width — tighter rows + columns + smaller text — for users who want to see as
   many columns/rows as possible even on a large monitor. */
body.density-compact .dtg_column {
	padding: 3px 4px;
	font-size: var(--text-xs);
}
body.density-compact .dtg_header {
	padding: 4px 4px;
	font-size: var(--text-xs);
	letter-spacing: 0;
}
body.density-compact .grid-card__scroll .dtg_column { max-width: 150px; vertical-align: middle; }
/* Text-area custom-field columns hold paragraph-length values. The clamped inner
   box clips its overflow, so the column won't grow on its own — give it a real
   minimum width (and a much wider cap) so it always has room for two lines of text. */
body.density-compact .grid-card__scroll .dtg_column--textarea { min-width: 160px; max-width: 340px; }

/* Comfortable density: show the FULL value of every field. Cells wrap (instead of
   nowrap+ellipsis), via an inner .dtg-cellinner that the footer JS font-fits —
   short values keep the larger preferred size, long values shrink (to a floor) and,
   only if they still don't fit one line, the cell wraps and the row grows taller. */
body:not(.density-compact) .grid-card__scroll .dtg_column {
	white-space: normal;
	max-width: 320px;
	vertical-align: middle;
}
.dtg-cellinner {
	/* Comfortable: no clamp — the cell grows to show ALL content. The footer JS
	   shrinks the font first (to keep the field on one line); only when the font hits
	   its floor does the cell wrap to more lines and the row grow taller.
	   white-space:normal is explicit so the inner wraps even inside a nowrap td. */
	white-space: normal;
	overflow-wrap: anywhere;
	line-height: 1.3;
	font-size: var(--text-base); /* preferred size; JS reduces per-cell as needed */
}
/* Compact density: the footer JS shrinks the font to keep the field on one line, but
   if it still won't fit at the floor the cell may use a SECOND line (at that floor
   size); anything needing more than two lines is ellipsis-truncated. */
.dtg-cellinner--clamp {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	overflow: hidden;
}

/* =========================================================================
   Record header / form sections
   ========================================================================= */
/* Action toolbar at the top of a record form — a clean row, not a grey slab. */
.record_header {
	font-weight: var(--weight-semibold);
	background-color: transparent;
	background-image: none;
	color: var(--text);
	padding: var(--space-2) var(--space-3) var(--space-3);
	border-bottom: 1px solid var(--border);
}
/* Section heading within a form. */
.record_subheader {
	color: var(--text);
	font-weight: var(--weight-semibold);
	font-size: var(--text-md);
	border-top: 1px solid var(--border);
	padding-bottom: var(--space-1);
	padding-top: var(--space-2);
	margin-top: var(--space-2);
}
/* -------------------------------------------------------------------------
   Edit-form fields: modern top-aligned layout. The label sits ABOVE its
   field and inputs fill the column up to a readable width. Achieved by making
   the record_* cells block-level; scoped via :has() to the field rows so
   datagrids and report tables (which don't use these classes) are untouched.
   Two field patterns exist and are both handled: record_field_name +
   record_field_edit (direct render) and + record_field_value (field-array /
   read-only display).
   ------------------------------------------------------------------------- */
tr:has(> .record_field_edit),
tr:has(> .record_field_value) {
	display: block;
	margin-bottom: var(--space-3);
}
/* Field label — block, left-aligned, sits above the input. */
.record_field_name {
	display: block;
	color: var(--text-muted);
	font-size: var(--text-sm);
	font-weight: var(--weight-medium);
	text-align: left;
	width: auto;
	white-space: normal;
	padding: 0 0 2px 0;
	vertical-align: top;
}
.record_field_edit,
.record_field_value {
	display: block;
	font-size: var(--text-base);
	width: auto;
	padding: 0;
	vertical-align: top;
	white-space: normal;
	color: var(--text);
}
/* Inputs fill the field column up to a comfortable max width. Checkboxes and
   radios keep their natural size (not matched below). */
.record_field_edit input[type="text"],
.record_field_edit input[type="password"],
.record_field_edit input[type="email"],
.record_field_edit input:not([type]),
.record_field_edit select,
.record_field_edit textarea,
.record_field_value input[type="text"],
.record_field_value input[type="password"],
.record_field_value input[type="email"],
.record_field_value input:not([type]),
.record_field_value select,
.record_field_value textarea {
	width: 100%;
	max-width: 420px;
	box-sizing: border-box;
}
/* Date-picker groups (Qcodo QDateTimePicker renders month/day/year as three
   *_lstMonth/_lstDay/_lstYear selects) stay inline at their natural width
   instead of each stretching to full column width and stacking. */
.record_field_edit select[id$="_lstMonth"],
.record_field_edit select[id$="_lstDay"],
.record_field_edit select[id$="_lstYear"],
.record_field_value select[id$="_lstMonth"],
.record_field_value select[id$="_lstDay"],
.record_field_value select[id$="_lstYear"] {
	display: inline-block;
	width: auto;
	max-width: none;
	margin-right: var(--space-1);
}
.record_field_spacer { width: 40px; }

/* -------------------------------------------------------------------------
   Two-column form grid (used by the multi-field edit forms). Fields flow into
   two columns row-major, so every grid row is as tall as its tallest field and
   the next row stays aligned across both columns. A field's input and any
   adornment (e.g. the "+" add-new icon or a search button) share one flex row,
   vertically centered with a small gap. Collapses to one column when narrow.
   ------------------------------------------------------------------------- */
.form-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	column-gap: var(--space-8);
	row-gap: var(--space-3);
	align-items: start;
}
.form-grid--single { grid-template-columns: minmax(0, 460px); }
/* Subheading that separates field sections inside a card body (e.g. the asset
   edit screen groups custom fields by type under these). */
.form-section__heading {
	margin: var(--space-5) 0 var(--space-3);
	padding-top: var(--space-4);
	border-top: 1px solid var(--border);
	font-size: var(--text-sm);
	font-weight: var(--weight-semibold);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--text-muted);
}
/* The first section (built-in fields) needs no divider above it. */
.card__body > .form-section__heading:first-child { margin-top: 0; padding-top: 0; border-top: none; }
@media (max-width: 760px) {
	.form-grid { grid-template-columns: minmax(0, 1fr); }
}
.form-grid__field { min-width: 0; }
.form-grid__label {
	color: var(--text-muted);
	font-size: var(--text-sm);
	font-weight: var(--weight-medium);
	padding-bottom: 2px;
}
/* Input + adornment(s) on one vertically-centered row. The input fills the
   full column width — matching the read-only field box it stands in for — and
   simply shrinks (flex) to make room when a "+"/search button sits beside it. */
.form-grid__value {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	min-height: 30px;
}
/* Qcodo wraps each control in <span id="cN">; the span holding a real input/
   select/textarea grows to fill the row, and the control fills that span, so
   inputs expand to the column width (minus the reserved gutter). Adornment
   spans (the "+"/search) and checkboxes keep their natural size. */
/* Empty Qcodo wrapper spans (e.g. a hidden/blank label beside a field) would
   otherwise still consume the flex gap and shrink the input — drop them so a
   field with no visible button renders the full column width. */
.form-grid__value > span:empty { display: none; }
.form-grid__value > span:has(> input[type="text"]),
.form-grid__value > span:has(> input[type="password"]),
.form-grid__value > span:has(> input[type="email"]),
.form-grid__value > span:has(> input:not([type])),
.form-grid__value > span:has(> select),
.form-grid__value > span:has(> textarea) {
	flex: 1 1 auto;
	min-width: 0;
}
.form-grid__value input[type="text"],
.form-grid__value input[type="password"],
.form-grid__value input[type="email"],
.form-grid__value input:not([type]),
.form-grid__value select,
.form-grid__value textarea {
	width: 100%;
	box-sizing: border-box;
	min-width: 0;
	/* Match the read-only field box's size so editable and read-only fields are
	   the same height (the editable ones keep their white background + border). */
	min-height: 36px;
	padding: 7px 10px;
}
/* Date-picker groups stay inline + natural width inside the value row. */
.form-grid__value select[id$="_lstMonth"],
.form-grid__value select[id$="_lstDay"],
.form-grid__value select[id$="_lstYear"] {
	flex: 0 0 auto;
	width: auto;
	min-width: 0;
}

/* Read-only value cells (view mode, and derived display-only fields like
   Category/Manufacturer) render as a filled field box that spans the column —
   so the data area reads as a consistent grid of field boxes whether or not a
   value is present.

   Detection is PER-CELL and based on whether the cell currently shows a
   *visible* editable control: Qcodo wraps each control in a <span id="cN_ctl">
   and toggles that wrapper's `display:none` to switch a field between its label
   (view) and its input (edit). A cell is boxed unless it holds a visible
   input/select/textarea. This is keyed off the per-control wrappers — which
   Qcodo DOES re-render on an AJAX postback — rather than a wrapper class on the
   grid, which is static template markup and would stay stale when the view<->edit
   toggle happens over AJAX (leaving editable inputs wrongly styled as read-only). */
/* "Visible control wrapper" = a direct-child <span> that is NOT hidden. Qcodo
   emits the hidden style two ways depending on render path — "display:none;" on
   the initial PHP render and "display: none; ..." on an AJAX postback update —
   so both spellings must be excluded. */
.form-grid__value:not(:has(> span:not([style*="display:none"]):not([style*="display: none"]) input)):not(:has(> span:not([style*="display:none"]):not([style*="display: none"]) select)):not(:has(> span:not([style*="display:none"]):not([style*="display: none"]) textarea)) {
	background: var(--surface-muted);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	padding: 7px 10px;
	min-height: 36px;
	box-sizing: border-box;
}

/* Large (multiline) fields show 4 lines of height in BOTH edit and read-only view,
   so a large text field is the same size whichever mode it renders in. 7.2em ≈ 4
   lines once the 7px/10px padding + 1px border are accounted for (border-box). Their
   value cell aligns content to the top. */
.form-grid__value textarea {
	min-height: 7.2em;
	resize: vertical;
}
.form-grid__value:has(textarea) { align-items: flex-start; }
/* Read-only (hidden-textarea) long-text box matches the editable textarea's
   ~4-line height. !important is required because the generic read-only-box rule
   above carries far higher specificity (its chained :not(:has()) conditions)
   and would otherwise cap this box at the standard single-line 36px. */
/* View-mode read-only text-area: the rendered .scrollBox IS the field box. The value
   box must not also paint its read-only field-box (background/border/padding) or the
   result is a box-in-a-box with a gap all around the scroll box. Make the value box a
   transparent container and let the scroll box fill it edge-to-edge. */
.form-grid__value:has(> span[style*="display:none"] textarea),
.form-grid__value:has(> span[style*="display: none"] textarea) {
	min-height: 7.2em !important;   /* 4 lines, matching the editable textarea */
	background: none !important;
	border: none !important;
	padding: 0 !important;
}
.form-grid__value:has(> span[style*="display:none"] textarea) > div:first-child,
.form-grid__value:has(> span[style*="display: none"] textarea) > div:first-child {
	display: block !important;   /* override Qcodo's inline display:inline */
	flex: 1 1 auto;
	align-self: stretch;
	min-width: 0;
}
.form-grid__value:has(> span[style*="display:none"] textarea) .scrollBox,
.form-grid__value:has(> span[style*="display: none"] textarea) .scrollBox {
	width: 100%;
	height: 100%;
	box-sizing: border-box;
	/* Match the editable textarea's padding so the read-only box is the same size. */
	padding: 7px 10px;
}

/* Secondary action button row (e.g. the asset transaction actions below the
   record) — spaced like the page-header toolbar instead of touching. */
.asset-actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2);
	margin: var(--space-4) 0;
}
/* "Print Tag" toolbar sits tight under the edit form, above the cards below. */
.asset-actions--tag { margin: 0 0 var(--space-3); }

/* A card that fills the available width and a comfortable minimum height, so
   the data area reads as a defined box even when sparsely populated. */
.card--fill { min-height: 60vh; }

/* -------------------------------------------------------------------------
   Import field-mapping table (CSV import, step 2). Themed to match the app's
   data tables instead of the legacy hardcoded grey/11px inline styles.
   ------------------------------------------------------------------------- */
.import-map {
	width: 100%;
	border-collapse: collapse;
}
.import-map th {
	text-align: left;
	font-size: var(--text-xs);
	font-weight: var(--weight-semibold);
	text-transform: uppercase;
	letter-spacing: 0.03em;
	color: var(--text-muted);
	padding: var(--space-2) var(--space-3);
	border-bottom: 1px solid var(--border);
	white-space: nowrap;
}
.import-map td {
	padding: var(--space-2) var(--space-3);
	border-bottom: 1px solid var(--border);
	font-size: var(--text-sm);
	color: var(--text);
	vertical-align: top;
}
.import-map tr:last-child td { border-bottom: none; }

/* Report filter forms (reports/*_report) carry irregular field groups (date
   ranges, checkbox rows) that don't fit the rigid .filter-bar grid, so they
   stay table-laid-out — but themed (no legacy grey/10px inline styles) inside a
   card. Labels use the muted token. */
.report-filter { width: 100%; border-collapse: collapse; }
.report-filter > tbody > tr > td,
.report-filter > tr > td { padding: var(--space-2) var(--space-3); vertical-align: top; }
.report-filter .item_label,
.report-filter .item_label span { color: var(--text-muted); font-size: var(--text-sm); font-weight: var(--weight-medium); }

/* Generated report data tables (reports/dtr_*) — themed like .datagrid instead
   of the legacy hardcoded #eeeeee header / #f0f0f0 zebra (dark-mode safe). */
.report-data { width: 100%; border-collapse: collapse; margin-top: var(--space-4); }
.report-data th {
	text-align: left;
	padding: var(--space-2) var(--space-3);
	border-bottom: 1px solid var(--border);
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.03em;
	color: var(--text-muted);
	font-weight: var(--weight-semibold);
}
.report-data td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--border); color: var(--text); }
.report-data tbody tr:nth-child(even) { background: var(--surface-muted); }

/* =========================================================================
   Role / permissions table
   ========================================================================= */
.role_table_header {
	background-color: var(--surface-alt);
	font-weight: var(--weight-semibold);
	font-size: var(--text-xs);
	width: 80px;
	text-align: center;
	empty-cells: show;
	color: var(--text);
	letter-spacing: 0.04em;
	text-transform: uppercase;
	padding: var(--space-1) var(--space-2);
}
.role_table_left {
	background-color: var(--surface-alt);
	font-weight: var(--weight-semibold);
	font-size: var(--text-xs);
	width: 80px;
	text-align: right;
	color: var(--text);
	padding: var(--space-1) var(--space-2);
}
.role_table_cell {
	background-color: var(--surface);
	width: 80px;
	border: 1px solid var(--border);
}

/* =========================================================================
   Packing slip
   ========================================================================= */
.packing_slip_field {
	font-size: var(--text-sm);
	font-weight: var(--weight-semibold);
	width: 150px;
	vertical-align: top;
	color: var(--text-muted);
}
.packing_slip_value {
	font-size: var(--text-sm);
	width: 150px;
	white-space: nowrap;
	color: var(--text);
}

/* =========================================================================
   Save notification (will become toast in theme 6)
   ========================================================================= */
.save_notification {
	color: var(--success-700);
	background-color: var(--success-50);
	font-size: var(--text-sm);
	border: 1px solid var(--success-500);
	border-radius: var(--radius-md);
	padding: var(--space-2) var(--space-3);
}

.add_icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	vertical-align: middle;
	cursor: pointer;
	padding: var(--space-1);
	border-radius: var(--radius-sm);
	transition: background-color 0.12s ease;
}
.add_icon img { display: block; }
.add_icon:hover { background-color: var(--accent-50); }

/* =========================================================================
   Dialogs (QDialogBox / QFileAsset)
   ========================================================================= */
.modal_dialog {
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-lg);
	/* !important overrides the legacy inline BackColor='#FFFFFF' some dialogs
	   still set, so modals follow the theme (and dark mode). */
	background: var(--surface) !important;
	color: var(--text);
	/* Tall dialogs (e.g. New Company with address fields) scroll inside the
	   viewport instead of overflowing off the top of the screen; the width cap
	   keeps wide dialogs (search tools at 900px) on-screen on phones/tablets. */
	max-height: 88vh;
	max-width: 94vw;
	overflow: auto;
}
/* A form rendered inside a dialog shouldn't draw the datagrid box border —
   the dialog itself is the container. */
.modal_dialog .datagrid,
div.dialogbox .datagrid,
div.fileassetDbox .datagrid {
	border: none;
	background: transparent;
}

div.fileassetDbox,
div.dialogbox {
	border: 1px solid var(--border);
	/* !important overrides any legacy inline BackColor='#ffffff' so all dialog
	   types follow the theme (and dark mode). */
	background-color: var(--surface) !important;
	color: var(--text);
	padding: var(--space-5);
	width: 400px;
	max-width: 92vw;
	max-height: 88vh;
	overflow: auto;
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-lg);
}
div.fileassetDbox h1 {
	margin: 0 0 var(--space-3);
	font-size: var(--text-xl);
	font-weight: var(--weight-semibold);
	color: var(--text);
}
div.fileassetDbox input { padding: 4px 8px; margin-right: var(--space-1); }

/* -------------------------------------------------------------------------
   Dialog panels (New Company / Contact / Address / Model, mass-edit, etc.)
   A consistent inner layout that mirrors the page edit screens: a header with
   the title on the left and the Save/Cancel actions on the right, then a
   form-grid of fields. Lives inside an already-themed .modal_dialog surface.
   ------------------------------------------------------------------------- */
.dialog-panel__header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
	flex-wrap: wrap;
	margin-bottom: var(--space-4);
	padding-bottom: var(--space-3);
	border-bottom: 1px solid var(--border);
}
.dialog-panel__title {
	margin: 0;
	font-size: var(--text-xl);
	font-weight: var(--weight-semibold);
	color: var(--text);
}
.dialog-panel__actions {
	display: flex;
	gap: var(--space-2);
	flex-wrap: wrap;
}
.dialog-panel__hint {
	margin: 0 0 var(--space-4);
	color: var(--text-muted);
	font-size: var(--text-sm);
}
/* Mass-edit fields prefix their label with a checkbox that enables the field. */
.form-grid__label input[type="checkbox"] {
	margin-right: var(--space-2);
	vertical-align: middle;
}

.file_asset a { color: var(--text); text-decoration: none; }
.file_asset_icon {
	border-width: 1px;
	border-style: solid;
	border-color: var(--border);
	border-radius: var(--radius-sm);
}

/* =========================================================================
   Hover tips (QHoverTip — popups over datagrid status/icon cells)
   Visual styling lives here so the popup follows the theme + dark mode;
   QHoverTip only sets position (absolute) and the .hovertip class.
   ========================================================================= */
/* The QHoverTip renders position:absolute with no coordinates, so it falls back
   to its nearest positioned ancestor. With none, that's <body>: every tip stacks
   at the page's top:0 and appears further from its row the further down the list
   it is (and worse as the page grows). Anchor each tip to its own datagrid cell
   so it pops up next to the icon it belongs to, on every row. (The tip sits a
   couple of wrapper spans deep inside the cell, so this is a descendant :has.) */
td:has(.hovertip) { position: relative; }
.hovertip {
	z-index: 1000;
	/* Anchor just below the icon within the (now relatively-positioned) cell. */
	top: 100%;
	left: 0;
	max-width: 480px;
	padding: var(--space-3) var(--space-4);
	background: var(--surface);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-lg);
	font-size: var(--text-sm);
	line-height: 1.45;
}
.hovertip table {
	border-collapse: collapse;
	margin: 0;
}
.hovertip th {
	text-align: left;
	font-size: var(--text-xs);
	font-weight: var(--weight-semibold);
	text-transform: uppercase;
	letter-spacing: 0.03em;
	color: var(--text-muted);
	padding: var(--space-1) var(--space-3) var(--space-2) 0;
	border-bottom: 1px solid var(--border);
}
.hovertip td {
	padding: var(--space-1) var(--space-3) var(--space-1) 0;
	color: var(--text);
	border-bottom: 1px solid var(--border);
}
.hovertip tr:last-child td { border-bottom: none; }

/* =========================================================================
   Reports
   ========================================================================= */
.report_section_heading {
	padding-top: var(--space-5);
	font-size: var(--text-lg);
	font-weight: var(--weight-semibold);
	color: var(--text);
	text-decoration: none;
}
.report_column_header {
	font-size: var(--text-sm);
	background-color: var(--accent-700);
	color: #fff;
	font-weight: var(--weight-semibold);
	padding: var(--space-2) var(--space-3);
	letter-spacing: 0.02em;
}
.report_cell {
	background-color: var(--surface);
	border-bottom: 1px solid var(--border);
	width: 200px;
	padding: var(--space-2) var(--space-3);
}
.report_total {
	text-align: right;
	font-weight: var(--weight-semibold);
	color: var(--text);
}
.report_title {
	font-size: var(--text-xl);
	font-weight: var(--weight-semibold);
	color: var(--text);
}

/* =========================================================================
   History / data-repeater
   ========================================================================= */
.gravatar {
	margin-right: var(--space-2);
	float: left;
	border-radius: 50%;
}
/* Circular user icon in history lists, matching the sidebar account avatar. */
.gravatar img {
	border-radius: 50%;
	display: block;
	object-fit: cover;
}
.history_note {
	font-size: var(--text-sm);
	color: var(--text-muted);
	margin-left: 54px;
	margin-top: var(--space-1);
}
.summary {
	margin-top: var(--space-2);
	font-weight: var(--weight-semibold);
	white-space: nowrap;
	font-size: var(--text-sm);
	color: var(--text);
}
.dtr_column_history {
	border-top: 1px solid var(--border);
}
.dtr_results {
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	padding: var(--space-1) var(--space-2);
	margin-right: var(--space-3);
	background: var(--surface);
}
.transaction_type {
	float: left;
	width: 25px;
}
.transaction_data {
	float: left;
	padding: var(--space-3);
}
.datarepeater {
	margin-right: var(--space-3);
	border: 1px solid var(--border);
	border-top: none;
	background: var(--surface);
}
/* Wrapper for a wide embedded datagrid (e.g. inventory Shipping/Receiving
   history): scrolls horizontally on its own instead of overflowing the page. */
.table-scroll {
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: thin;
	scrollbar-color: var(--border-strong) var(--surface-alt);
}

/* =========================================================================
   Auth shell (login / access-denied — minimal, no sidebar)
   ========================================================================= */
body.auth {
	margin: 0;
	min-height: 100vh;
	background: var(--bg);
	display: flex;
	align-items: center;
	justify-content: center;
	padding: var(--space-6);
}
.auth-shell { width: 100%; max-width: 380px; }
.auth-card {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius-xl);
	box-shadow: var(--shadow-lg);
	padding: var(--space-8) var(--space-6);
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
	text-align: center;
}
.auth-card__brand img { max-width: 200px; height: auto; margin: 0 auto; display: block; }
.auth-card__title { margin: 0; font-size: var(--text-2xl); font-weight: var(--weight-semibold); color: var(--text); }
.auth-card__message { margin: 0; color: var(--text-muted); font-size: var(--text-sm); }
.auth-card__field { text-align: left; }
.auth-card__field input { width: 100%; }
.auth-card__actions { margin-top: var(--space-2); }

/* =========================================================================
   Modern app shell (theme 1 — used by pages migrated to the new layout)
   ========================================================================= */
body.app {
	margin: 0;
	height: 100vh;
	height: 100dvh;            /* avoids mobile URL-bar clipping; falls back to vh */
	overflow: hidden;
	background: var(--bg);
	display: flex;
}

/* Mobile top bar + hamburger + drawer backdrop. Hidden on desktop; the
   responsive blocks below switch them on and turn the sidebar into a drawer. */
.app-topbar { display: none; }
.app-backdrop { display: none; }
.app-hamburger {
	display: inline-flex;
	flex-direction: column;
	justify-content: center;
	gap: 4px;
	width: 40px;
	height: 40px;
	padding: 9px 8px;
	border: none;
	background: transparent;
	border-radius: var(--radius-md);
	cursor: pointer;
}
.app-hamburger__bar {
	display: block;
	height: 2px;
	width: 100%;
	background: currentColor;
	border-radius: 2px;
}

.skip-link {
	position: absolute;
	left: var(--space-2);
	top: -200px;
	z-index: 100;
	background: var(--surface);
	color: var(--text);
	padding: var(--space-2) var(--space-3);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-md);
	font-weight: var(--weight-medium);
	transition: top 0.12s ease;
}
.skip-link:focus {
	top: var(--space-2);
	text-decoration: none;
	outline: none;
	box-shadow: var(--focus-ring);
}

.app-sidebar {
	width: 184px;
	flex: 0 0 184px;
	background: var(--sidebar-bg);
	color: var(--slate-100);
	display: flex;
	flex-direction: column;
	height: 100vh;
	border-right: 1px solid var(--sidebar-edge);
}

.app-sidebar__brand {
	padding: var(--space-5) var(--space-5) var(--space-4);
	border-bottom: 1px solid var(--slate-800);
}
.app-sidebar__logo {
	max-width: 140px;
	max-height: 36px;
	filter: brightness(0) invert(1);
	display: block;
}
.app-sidebar__wordmark {
	font-size: var(--text-xl);
	font-weight: var(--weight-bold);
	color: #fff;
	letter-spacing: -0.02em;
}

.app-sidebar__nav {
	flex: 1;
	padding: var(--space-3) var(--space-2);
	overflow-y: auto;
}
.app-nav {
	list-style: none;
	margin: 0;
	padding: 0;
}
.app-nav__item { margin: 2px 0; }
.app-nav__item--secondary { margin-top: var(--space-4); }

.app-nav__link {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-2) var(--space-3);
	border-radius: var(--radius-md);
	color: var(--sidebar-link);
	text-decoration: none;
	font-size: var(--text-base);
	font-weight: var(--weight-medium);
	transition: background-color 0.12s ease, color 0.12s ease;
}
.app-nav__link:hover {
	background: var(--sidebar-hover);
	color: #fff;
	text-decoration: none;
}
.app-nav__link.is-active {
	background: var(--sidebar-active);
	color: #fff;
}
.app-nav__icon {
	width: 18px;
	height: 18px;
	flex-shrink: 0;
	filter: brightness(0) invert(1);
	opacity: 0.85;
}
.app-nav__link.is-active .app-nav__icon { opacity: 1; }
.app-nav__label { flex: 1; }

/* Sub-navigation revealed under the active module (the old shortcut links) */
.app-subnav {
	list-style: none;
	margin: var(--space-1) 0 var(--space-2);
	padding: 0;
}
.app-subnav__link {
	display: block;
	/* Reduced left indent (was 42px, aligned under the main label) so sub-items
	   sit further left and fit the narrower sidebar. */
	padding: 5px var(--space-3) 5px 24px;
	color: var(--sidebar-muted);
	text-decoration: none;
	font-size: var(--text-sm);
	border-radius: var(--radius-md);
}
.app-subnav__link:hover {
	background: var(--sidebar-hover);
	color: #fff;
	text-decoration: none;
}
.app-subnav__link.is-active {
	background: var(--sidebar-hover);
	color: #fff;
	font-weight: var(--weight-medium);
}
.app-subnav__divider {
	height: 1px;
	background: var(--sidebar-edge);
	margin: var(--space-2) var(--space-3) var(--space-2) 42px;
}

.app-sidebar__footer {
	padding: var(--space-4) var(--space-4);
	border-top: 1px solid var(--sidebar-edge);
}
/* The avatar + name is a link to the account settings page. */
.app-user {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	text-decoration: none;
	padding: var(--space-2);
	border-radius: var(--radius-md);
	transition: background-color 0.12s ease;
}
.app-user:hover { background: var(--slate-800); }
.app-user:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.app-user__hint {
	font-size: var(--text-xs);
	color: var(--slate-400);
}
.app-user__avatar {
	width: 36px;
	height: 36px;
	border-radius: 50%;
	background: var(--accent-600);
	color: #fff;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-weight: var(--weight-semibold);
	font-size: var(--text-sm);
	letter-spacing: 0.02em;
	flex-shrink: 0;
}
/* When the user has uploaded a profile photo, the avatar holds an <img> that
   fills the circle instead of initials on the accent fill. */
.app-user__avatar--img { background: none; padding: 0; overflow: hidden; }
.app-user__avatar--img img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	border-radius: 50%;
	display: block;
}
/* Account-settings profile-photo widget: a clean circular preview beside the
   upload link. The QFileAsset's own thumbnail (.file_asset_icon) routes through
   QImageControl's broken cache, so it's hidden here in favour of this preview. */
.avatar-upload { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.avatar-preview {
	width: 72px;
	height: 72px;
	border-radius: 50%;
	background: var(--surface-alt);
	border: 1px solid var(--border);
	overflow: hidden;
	flex-shrink: 0;
}
.avatar-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avatar-upload .file_asset_icon { display: none; }
.avatar-upload__controls { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }

.app-user__meta { display: flex; flex-direction: column; min-width: 0; }
.app-user__name {
	font-size: var(--text-sm);
	color: #fff;
	font-weight: var(--weight-medium);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 150px;
}
.app-user__signout {
	display: inline-block;
	margin: var(--space-1) 0 0 var(--space-2);
	font-size: var(--text-xs);
	color: var(--slate-400);
	text-decoration: none;
}
.app-user__signout:hover { color: #fff; text-decoration: underline; }

.app-main {
	flex: 1;
	display: flex;
	flex-direction: column;
	min-width: 0;
	height: 100vh;
	height: 100dvh;
	overflow: hidden;
	background: var(--bg);
}

.app-content {
	flex: 1;
	min-height: 0;
	min-width: 0;
	/* The content area itself does NOT scroll — it's a flex column: fixed
	   page-header + filter on top, fixed bulk-actions bar at the bottom, and
	   the results grid fills the middle and scrolls internally. */
	display: flex;
	flex-direction: column;
	overflow: hidden;
	padding: var(--space-4) var(--space-5);
	max-width: 100%;
}
/* Qcodo's RenderBegin wraps the page content in <form>, so the flex column
   that fixes header+filter on top and the bulk bar at the bottom lives here. */
.app-content > form {
	flex: 1 1 auto;
	min-height: 0;
	min-width: 0;
	display: flex;
	flex-direction: column;
}
.app-content > form > .page-header { flex: 0 0 auto; }

/* Page header (inside .app-content) */
.page-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
	margin-bottom: var(--space-3);
}
/* row-reverse so the action buttons sit on the LEFT and the title on the RIGHT.
   Markup order is .page-header__titles then .page-header__actions; reversing the
   flow swaps which side each lands on across every edit/view screen without
   touching the 50+ templates. Scoped with :has() so headers that have no action
   buttons (e.g. report views) keep their lone title left-aligned. */
.page-header:has(.page-header__actions) {
	flex-direction: row-reverse;
}
.page-header__titles { display: flex; flex-direction: column; gap: 1px; }
.page-header__title {
	margin: 0;
	font-size: var(--text-2xl);
	font-weight: var(--weight-semibold);
	color: var(--text);
	letter-spacing: -0.01em;
}
.page-header__subtitle {
	margin: 0;
	font-size: var(--text-sm);
	color: var(--text-muted);
}
.page-header__actions {
	display: flex;
	gap: var(--space-2);
	flex-wrap: wrap;
}

/* The single button system: .btn + a .btn--* modifier. Used on <a> links and,
   via QButton's default CssClass, on every form <input type="button|submit">. */
.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-2);
	font-family: var(--font-sans);
	font-size: var(--text-sm);
	font-weight: var(--weight-medium);
	line-height: 1;
	border-radius: var(--button-radius);
	padding: 8px 14px;
	border: 1px solid transparent;
	cursor: pointer;
	transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
	text-decoration: none;
	white-space: nowrap;
}
.btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }

.btn--primary {
	background: var(--accent);
	border-color: var(--accent);
	color: #fff;
}
.btn--primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: #fff; text-decoration: none; }

.btn--secondary {
	background: var(--surface);
	border-color: var(--border-strong);
	color: var(--text);
}
.btn--secondary:hover { background: var(--surface-alt); border-color: var(--slate-400); text-decoration: none; color: var(--text); }

.btn--danger {
	background: var(--danger-600);
	border-color: var(--danger-600);
	color: #fff;
}
.btn--danger:hover { background: var(--danger-700); border-color: var(--danger-700); color: #fff; text-decoration: none; }

.btn--ghost {
	background: transparent;
	border-color: transparent;
	color: var(--text-muted);
}
.btn--ghost:hover { background: var(--surface-alt); color: var(--text); text-decoration: none; }

/* Cards — used for filter, toolbar, grid container */
.card {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-sm);
}
.card + .card { margin-top: var(--space-4); }
.card__body { padding: var(--space-4); }
.card__header {
	padding: var(--space-2) var(--space-4);
	border-bottom: 1px solid var(--border);
	display: flex;
	align-items: center;
	gap: var(--space-3);
}
.card__title {
	margin: 0;
	font-size: var(--text-sm);
	font-weight: var(--weight-semibold);
	color: var(--text-muted);
	text-transform: uppercase;
	letter-spacing: 0.06em;
}
.card__hint {
	margin: 0 0 var(--space-3);
	font-size: var(--text-sm);
	color: var(--text-muted);
}

/* Inline save/validation feedback (e.g. account settings page). */
.form-status {
	font-size: var(--text-sm);
	font-weight: var(--weight-medium);
}
.form-status--ok  { color: #15803d; }
.form-status--err { color: var(--danger-600); }

/* Vertical list of links inside a card (e.g. the reports landing page) */
.card-links { display: flex; flex-direction: column; }
.card-links__item {
	padding: var(--space-3) var(--space-4);
	border-bottom: 1px solid var(--border);
	color: var(--link);
	font-weight: var(--weight-medium);
}
.card-links__item:last-child { border-bottom: none; }
.card-links__item:hover { background: var(--surface-alt); text-decoration: none; }

/* The "Filter …" card title is redundant with the labelled fields — hide it
   to save vertical space (the filter bar becomes the top of the card). */
.filter-card .card__header { display: none; }

/* Filter bar (modern asset_search_composite) */
.filter-bar {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
	gap: var(--space-2) var(--space-3);
	padding: var(--space-3);
}
.filter-bar__field {
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
	min-width: 0;
}
.filter-bar__field label,
.filter-bar__field .renderWithName div.left {
	font-size: var(--text-xs);
	font-weight: var(--weight-semibold);
	color: var(--text-muted);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}
.filter-bar__field input.textbox,
.filter-bar__field select.listbox,
.filter-bar__field .textbox,
.filter-bar__field .listbox {
	width: 100%;
}
.filter-bar__actions {
	grid-column: 1 / -1;
	display: flex;
	align-items: center;
	gap: var(--space-2);
	padding-top: var(--space-2);
	border-top: 1px solid var(--border);
}
.filter-bar__advanced { margin-right: auto; }

/* Advanced filter grid (QAdvancedSearchComposite extra fields) — laid out in the
   same responsive grid as .filter-bar instead of the legacy one-field-per-row
   table. Sits directly below the basic filter bar, separated by a hairline.
   Multiple controls in one cell (e.g. the Date radio + comparator + pickers)
   stack with a little breathing room. */
.filter-bar--advanced { border-top: 1px solid var(--border); }
.filter-bar--advanced .filter-bar__field { gap: var(--space-2); }

/* Primary "search everything" row at the top of the filter card: a single wide
   keyword box + the Search button. The per-field .filter-bar below it narrows
   the same result set. */
.filter-search {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	padding: var(--space-3) var(--space-3) 0;
}
.filter-search__box { flex: 1 1 auto; min-width: 0; }
.filter-search__box input.textbox,
.filter-search__box .textbox {
	width: 100%;
	font-size: var(--text-base);
}
.filter-search > .btn,
.filter-search > input[type="button"],
.filter-search > input[type="submit"] { flex: 0 0 auto; }

/* Search button as a magnifying-glass icon: the "Search" value is kept (accessible
   name) but hidden, and a white magnifier is painted as a centered background image
   over the primary button colour. */
.filter-search input[type="button"].btn--primary,
.filter-search input[type="submit"].btn--primary {
	color: transparent;
	width: 42px;
	min-width: 42px;
	padding-left: 0;
	padding-right: 0;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: center;
	background-size: 18px 18px;
}

/* "?" search-help button (left of the search box) + its help dialog. Injected on
   every list page by footer_modern.inc.php. */
.search-help-btn {
	flex: 0 0 auto;
	width: 30px;
	height: 30px;
	border-radius: 50%;
	border: 1px solid var(--border);
	background: var(--surface);
	color: var(--text-muted);
	font: var(--weight-bold) var(--text-base)/1 var(--font-sans);
	cursor: pointer;
}
.search-help-btn:hover { background: var(--surface-alt); color: var(--text); border-color: var(--border-strong); }

.search-help[hidden] { display: none; }
.search-help {
	position: fixed;
	inset: 0;
	z-index: 200;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: var(--space-4);
}
.search-help__backdrop {
	position: absolute;
	inset: 0;
	background: rgba(0, 0, 0, 0.45);
}
.search-help__card {
	position: relative;
	background: var(--surface);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-lg);
	width: 100%;
	max-width: 540px;
	max-height: 85vh;
	overflow-y: auto;
	padding: var(--space-5);
}
.search-help__close {
	position: absolute;
	top: var(--space-2);
	right: var(--space-3);
	border: none;
	background: none;
	color: var(--text-muted);
	font-size: var(--text-2xl);
	line-height: 1;
	cursor: pointer;
}
.search-help__close:hover { color: var(--text); }
.search-help__card h2 {
	margin: 0 0 var(--space-2);
	font-size: var(--text-lg);
	font-weight: var(--weight-semibold);
}
.search-help__card p { margin: 0 0 var(--space-3); color: var(--text-muted); font-size: var(--text-sm); }
.search-help__card h3 {
	margin: var(--space-4) 0 var(--space-2);
	padding-top: var(--space-3);
	border-top: 1px solid var(--border);
	font-size: var(--text-sm);
	font-weight: var(--weight-semibold);
	text-transform: uppercase;
	letter-spacing: .04em;
	color: var(--text-muted);
}
.search-help__card dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: var(--space-2) var(--space-3); align-items: baseline; }
.search-help__card dt { white-space: nowrap; }
.search-help__card dd { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }
.search-help__card code {
	font-family: var(--font-mono, monospace);
	background: var(--surface-alt);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	padding: 1px 5px;
	font-size: 0.9em;
	color: var(--text);
}

/* Toolbar under the search box: the "Advanced filters" disclosure toggle (left)
   + Clear (right). Everything else is hidden until the disclosure is opened, so
   the resting state is just the one search field. */
.filter-toolbar {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--space-1) var(--space-2);
	padding: var(--space-2) var(--space-3) var(--space-3);
}
/* "Include" toggles (Archived / TBR / Shipped), pushed to the right by the
   Advanced toggle's margin-right:auto. Compact + abbreviated so they fit on
   mobile without widening the filter card. */
.filter-includes {
	margin-left: auto;          /* push the toggles (and Clear after them) to the right */
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	justify-content: flex-end;
	gap: var(--space-1) var(--space-3);
}
.filter-include {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	font-size: var(--text-xs);
	color: var(--text-muted);
	white-space: nowrap;
	cursor: pointer;
	user-select: none;
}
.filter-include input { margin: 0; }
.filter-toggle {
	margin-right: auto;
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	font-size: var(--text-sm);
	font-weight: var(--weight-medium);
	color: var(--link);
	cursor: pointer;
	user-select: none;
}
.filter-toggle:hover { color: var(--link-hover); text-decoration: underline; }
/* Disclosure caret, rendered before the label; rotates when the card is open. */
.filter-toggle::before {
	content: "";
	width: 0; height: 0;
	border-top: 4px solid transparent;
	border-bottom: 4px solid transparent;
	border-left: 5px solid currentColor;
	transition: transform 0.15s ease;
}
.filter-card.is-open .filter-toggle::before { transform: rotate(90deg); }

/* Collapsible region holding the per-field filters + the advanced composite.
   Hidden by default; the toggle adds .is-open to the .filter-card to reveal it. */
.filter-advanced { display: none; }
.filter-card.is-open .filter-advanced {
	display: block;
	border-top: 1px solid var(--border);
	/* Cap the height so a tall, single-column set of filters (esp. on mobile)
	   doesn't run off-screen — scroll within the panel instead. */
	max-height: min(60vh, 540px);
	overflow-y: auto;
	overscroll-behavior: contain;
}

/* Visually-hidden label, still read by screen readers. */
.sr-only {
	position: absolute;
	width: 1px; height: 1px;
	padding: 0; margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* Toolbar — standalone action bar that can pair with the grid card */
.toolbar {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--space-3);
	padding: var(--space-3) var(--space-4);
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-sm);
	margin-top: var(--space-4);
}
.toolbar__spacer { flex: 1; }
.toolbar__actions { display: flex; gap: var(--space-2); }
.toolbar__label {
	font-size: var(--text-xs);
	font-weight: var(--weight-semibold);
	color: var(--text-muted);
	text-transform: uppercase;
	letter-spacing: 0.06em;
}

/* When a list page uses a Qcodo search composite, Qcodo wraps it in a generated
   <span id="cN_ctl"> between the form and our .list-shell. That wrapper has
   flex-grow:0 and sizes to content, which would break the height chain (grid
   sizes to content → can't scroll, bulk bar pushed off-screen). Collapse the
   wrapper with display:contents so .list-shell becomes a direct flex child of
   the form. (Inline-search list pages put .list-shell directly in the template,
   so there's no wrapper to collapse — this rule simply doesn't match.) */
.app-content form > *:has(> .list-shell) {
	display: contents;
}

/* List shell: the per-page results container (a search composite with
   CssClass="list-shell", or a literal <div class="list-shell"> for inline-search
   pages). A flex column inside the non-scrolling .app-content — the filter card
   stays fixed at the top, the grid card grows to fill the rest. */
.list-shell {
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
}
.list-shell > .filter-card { flex: 0 0 auto; }

/* Grid card — wraps the QDataGrid for modern presentation. The paginator row is
   lifted out of the scrolling table into .grid-card__bar by the relocation
   script in footer_modern.inc.php, so the title bar is sized to the CARD (the
   visible content area) and stays put when the results table scrolls. Inside
   .list-shell the card grows to fill the height; only .grid-card__scroll scrolls. */
.grid-card {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-sm);
	overflow: hidden;          /* clip rounded corners; scrolling lives on __scroll */
}
.list-shell > .grid-card {
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	flex-direction: column;
}

/* Title section: holds the relocated paginator row's inner table, which lays
   out the result-count (left) and the paginator (right) across its full width. */
.grid-card__bar {
	flex: 0 0 auto;
	padding: var(--space-3) var(--space-4);
	/* Match the bulk-actions bar / column header (var(--surface)). */
	background: var(--surface);
	border-bottom: 1px solid var(--border);
	font-size: var(--text-xs);
	color: var(--text-muted);
	/* Lay the relocated paginator table and the injected rows-per-page selector
	   on one line. */
	display: flex;
	align-items: center;
	gap: var(--space-4);
}
.grid-card__bar:empty { display: none; }
/* The relocated paginator table fills the bar: the count cell sits left, the
   paginator right; the Density/Rows selectors follow on the far right. Drop the
   inner table's hardcoded 50/50 cell widths so the paginator gets the room it
   needs (otherwise "Next" wraps) while the short count cell stays left. */
.grid-card__bar > table { flex: 1 1 auto; }
.grid-card__bar table td { width: auto !important; }
/* Density selector (injected by footer_modern.inc.php). */
.density {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	white-space: nowrap;
	text-transform: uppercase;
	letter-spacing: 0.02em;
}
.density select {
	font: inherit;
	padding: 2px 6px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--surface);
	color: var(--text);
}

/* Hamburger mode: keep the count, paginator and density control on one line (the
   density dropdown is label-less and rows-per-page is gone, so they fit). Keep the
   count on one line, and allow a graceful wrap as a fallback on very tight widths. */
@media (max-width: 960px) {
	.grid-card__bar {
		flex-wrap: wrap;
		row-gap: var(--space-2);
		column-gap: var(--space-3);
	}
	/* Override the inner table's inline width:100% so its flex-basis is its content
	   width (not the whole bar) — that lets the density control share the line instead
	   of being pushed onto a second row. */
	.grid-card__bar > table { width: auto !important; }
	.grid-card__bar table td { white-space: nowrap; }
}
/* Phones: also compact the paginator to Prev / current page / Next so the numbered
   links don't wrap in the narrow row. */
@media (max-width: 520px) {
	.grid-card__bar .paginator .page,
	.grid-card__bar .paginator .ellipsis { display: none; }
}

/* Results table scrolls (both axes) inside the card, filling the leftover
   height. Styled scrollbars forced visible (no overlay auto-hide). */
.grid-card__scroll {
	flex: 1 1 auto;
	min-height: 0;
	overflow: auto;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: thin;
	scrollbar-color: var(--border-strong) var(--surface-alt);
}
.grid-card__scroll::-webkit-scrollbar { width: 12px; height: 12px; }
.grid-card__scroll::-webkit-scrollbar-track { background: var(--surface-alt); }
.grid-card__scroll::-webkit-scrollbar-thumb {
	background: var(--border-strong);
	border: 3px solid var(--surface-alt);
	border-radius: var(--radius-lg);
}
.grid-card__scroll::-webkit-scrollbar-thumb:hover { background: var(--slate-400); }

.grid-card .datagrid {
	/* width:auto so the table sizes to its content instead of stretching to fill
	   the card — columns (and the header row) only take the width they need, packed
	   to the left with whitespace on the right. Wider-than-card tables still scroll
	   horizontally via .grid-card__scroll. */
	width: auto;
	border: none;
	border-radius: 0;
	/* Override the base .datagrid's overflow:hidden: an ancestor with
	   overflow!=visible between the sticky <th> and .grid-card__scroll would
	   capture the sticky context and let the header scroll away. */
	overflow: visible;
}
/* Keep the column headers pinned while the results scroll vertically. Sticky is
   applied to the header <th> CELLS, not the <tr>: position:sticky on a table row
   is buggy across browsers — the row shifts by a pixel at the scroll extremes
   and flickers during hover repaints — whereas sticky cells are rock-solid. Only
   the header row uses <th> (data cells are <td>), so this targets exactly the
   header. A solid background + bottom divider stop rows showing through as they
   scroll underneath. */
.grid-card__scroll .datagrid th {
	position: sticky;
	top: 0;
	z-index: 2;
	background: var(--header-bg);
	box-shadow: inset 0 -1px 0 var(--border);
}

/* The datagrids set legacy inline row styles in PHP — hardcoded black/white
   text, an #EFEFEF zebra, and a fixed header colour — which override the
   theme and ignore dark mode. Neutralize them so every grid (result lists AND
   the child grids embedded in edit pages) follows the tokens: clean surface
   rows, subtle .dtg_column dividers, and a hover state. The header/column rules
   only match .dtg_* cells, so legacy record-form tables are unaffected. */
.datagrid tr {
	background-color: transparent !important;
	color: var(--text) !important;
}
.datagrid .dtg_header {
	background-color: var(--surface) !important;
	color: var(--text) !important;
}
.datagrid .dtg_column {
	font-size: var(--text-sm);
	color: var(--text);
}
/* Zebra striping: tint every other result row so the eye can track a row
   across the page. Applied to .dtg_column cells (not the <tr>, which is forced
   transparent !important above), mirroring the hover rule. Kept before the
   hover rule so hover wins the equal-specificity tie and still highlights
   striped rows. The header row is untouched — its cells are .dtg_header. */
.datagrid tr:nth-child(even) .dtg_column {
	background-color: var(--row-alt);
}
.datagrid tr:hover .dtg_column {
	background-color: var(--row-hover);
}

/* Column-selection tool: the "…" button in the grid header + its dropdown.
   (Replaces the old hardcoded blue/grey inline styles.) */
.column-toggle-btn {
	display: inline-block;
	cursor: pointer;
	padding: 0 var(--space-2);
	border-radius: var(--radius-sm);
	color: var(--text-muted);
	font-weight: var(--weight-bold);
	letter-spacing: 1px;
	line-height: 1.4;
}
.column-toggle-btn:hover {
	background: var(--surface-alt);
	color: var(--text);
}
.column-toggle-menu {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-lg);
	padding: var(--space-2);
	font-size: var(--text-sm);
	color: var(--text);
	z-index: 60;
	/* The menu is position:fixed (set in datagrid_column_toggle.js) so a long
	   column list can't scroll with the page — cap its height and scroll inside. */
	max-height: 75vh;
	overflow-y: auto;
}
.column-toggle-head {
	font-size: var(--text-xs);
	font-weight: var(--weight-semibold);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--text-muted);
	padding: var(--space-1) var(--space-2) var(--space-2);
}
.column-toggle-item {
	display: block;
	padding: var(--space-1) var(--space-2);
	border-radius: var(--radius-sm);
	cursor: pointer;
	color: var(--text);
}
.column-toggle-item:hover { background: var(--surface-alt); }
.column-toggle-export {
	margin-top: var(--space-1);
	padding-top: var(--space-2);
	border-top: 1px solid var(--border);
	color: var(--link);
}

/* Bulk-actions bar: fixed at the bottom of the content area, always visible. */
.toolbar--bulk {
	flex: 0 0 auto;
	margin-top: var(--space-3);
}
/* A toolbar inside the list shell (e.g. admin quick-add) is spaced by the
   shell's gap, so drop its own top margin. */
.list-shell > .toolbar { margin-top: 0; }

/* Generic vertical scroller for non-list modern pages (settings, reports) whose
   content may exceed the viewport — wraps the body so it scrolls inside the
   fixed .app-content instead of being clipped. */
.page-scroll {
	flex: 1 1 auto;
	min-height: 0;
	overflow-y: auto;
}

/* Below the full-desktop width the sidebar becomes an off-canvas drawer opened by
   the hamburger in a sticky top bar; the drawer keeps the FULL navigation (labels
   + sub-nav). There is NO intermediate icon-only/compact state — the menu is
   either the full sidebar (above this width) or the hamburger drawer (below it). */
@media (max-width: 960px) {
	body.app { flex-direction: column; }

	.app-topbar {
		display: flex;
		align-items: center;
		gap: var(--space-2);
		flex: 0 0 auto;
		padding: var(--space-2) var(--space-3);
		background: var(--sidebar-bg);
		color: #fff;
		border-bottom: 1px solid var(--sidebar-edge);
	}
	.app-topbar__brand {
		flex: 1;
		min-width: 0;
		font-size: var(--text-lg);
		font-weight: var(--weight-bold);
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}
	.app-hamburger { color: #fff; }

	/* The screen title lives in the top bar now, so drop the in-page page-header
	   title + subtitle to save vertical space. */
	.page-header__titles { display: none; }
	.page-header:empty,
	.page-header:not(:has(.page-header__actions)) { display: none; margin: 0; }

	/* On LIST pages, also drop the whole header — its only actions are navigation
	   links (Import / + New …) that are already in the hamburger nav drawer — so the
	   results grid gets that vertical room back. Detail/edit headers (Save, Edit,
	   Delete …) are kept: their form has no .list-shell. */
	form:has(.list-shell) > .page-header { display: none; }

	/* Bulk-action bar (Mass Edit / Mass Delete) isn't useful on touch/mobile —
	   hide it in the hamburger layout; it stays on the full-sidebar layout. */
	.toolbar--bulk { display: none; }

	/* On phones the filters stack to one tall column; cap tighter so the results
	   stay reachable and the panel scrolls internally. */
	.filter-card.is-open .filter-advanced { max-height: 50vh; }

	/* Drawer: fixed, off-canvas, slides in. Restores the full vertical sidebar.
	   Width matches the full-sidebar layout (184px) so the menu is the same size
	   in both layouts; max-width is a safety cap for ultra-narrow screens only. */
	.app-sidebar {
		position: fixed;
		top: 0;
		left: 0;
		width: 184px;
		max-width: 84vw;
		height: 100vh;
		height: 100dvh;
		flex-basis: auto;
		z-index: 60;
		overflow-y: auto;
		transform: translateX(-100%);
		transition: transform 0.25s ease;
		will-change: transform;
	}
	body.nav-open .app-sidebar { transform: translateX(0); box-shadow: var(--shadow-lg); }

	/* Full navigation inside the drawer (labels + sub-nav + user details). */
	.app-nav__label,
	.app-sidebar__wordmark,
	.app-subnav,
	.app-user__signout,
	.app-user__meta { display: revert; }
	.app-sidebar__brand { padding: var(--space-4) var(--space-5); justify-content: flex-start; }
	.app-sidebar__logo { max-width: none; }
	.app-nav__link { justify-content: flex-start; padding: var(--space-2) var(--space-3); }
	.app-sidebar__footer { padding: var(--space-4) var(--space-5); justify-content: flex-start; }
	.app-user { justify-content: flex-start; }

	.app-backdrop {
		display: block;
		position: fixed;
		inset: 0;
		z-index: 55;
		background: rgba(2, 6, 23, 0.55);
		opacity: 0;
		visibility: hidden;
		transition: opacity 0.25s ease, visibility 0.25s ease;
	}
	body.nav-open .app-backdrop { opacity: 1; visibility: visible; }

	/* Tighter edge padding + a smaller gap between the search panel and the results
	   box so they use more of the screen on phones. */
	.app-content { padding: var(--space-2); }
	.list-shell { gap: var(--space-2); }
	.page-header { flex-direction: column; align-items: stretch; }

	/* Asset-history rows: let the summary text wrap within the card instead of
	   overflowing. On desktop `.summary` is a forced one-liner (white-space:
	   nowrap) and `.transaction_data` is a shrink-to-fit float, both of which
	   overflow a narrow screen. */
	.transaction_data { float: none; width: auto; }
	.summary { white-space: normal; }
}

