/* CSS Variables for theme colors — a Material Design 3-inspired tonal system,
   seeded from this app's existing brand blue rather than switching to
   Google's stock palette (same idea as Firebase Console: Material's system —
   elevation, shape, motion, tonal color roles — applied to your own brand). */
:root {
  --primary-color: #4361ee;
  --primary-container: #dde3ff;
  --on-primary: #ffffff;
  --on-primary-container: #001551;
  --secondary-color: #3f37c9;
  --accent-color: #4895ef;
  --background-color: #f7f8fc;
  --surface-color: #ffffff;
  --surface-container: #eef0f9;
  --surface-container-high: #e4e7f4;
  --text-primary: #1a1c22;
  --text-secondary: #45464f;
  --border-color: #dfe1ec;
  --outline-variant: #c6c8d4;
  --success-color: #1e8e3e;
  --warning-color: #f29900;
  --error-color: #ba1a1a;
  --sidebar-width: 250px;
  --header-height: 60px;
  --top-bar-height: 56px;
  --top-bar-menu-height: 56px;
  --top-bars-height: calc(var(--top-bar-height) + var(--top-bar-menu-height));
  --border-radius: 12px;
  --radius-pill: 100px;
  /* Buttons: a small, distinctly "square" corner (not the full --radius-pill
     shape .btn used before) — Material's own filled/outlined button shape
     when a product wants squared-off rather than pill controls. Icon-only
     buttons (.btn-icon) keep --radius-pill — a round tap target is still the
     correct Material shape for an icon button specifically. */
  --radius-button: 8px;
  /* Material's dp elevation scale (approximated): resting surfaces sit at
     level 1, hover/dragged states rise to 2-3, overlays (menus/dropdowns) to
     3, modals/dialogs to 4. --box-shadow stays as an alias to elevation-1 so
     every rule already written against it upgrades automatically. */
  --elevation-1: 0 1px 2px rgba(0, 0, 0, 0.12), 0 1px 3px 1px rgba(0, 0, 0, 0.08);
  --elevation-2: 0 1px 2px rgba(0, 0, 0, 0.14), 0 2px 6px 2px rgba(0, 0, 0, 0.10);
  --elevation-3: 0 1px 3px rgba(0, 0, 0, 0.16), 0 4px 8px 3px rgba(0, 0, 0, 0.10);
  --elevation-4: 0 2px 3px rgba(0, 0, 0, 0.18), 0 6px 10px 4px rgba(0, 0, 0, 0.10);
  --box-shadow: var(--elevation-1);
  --transition: all 0.2s cubic-bezier(0.2, 0, 0, 1);
}

/* Dark theme variables */
[data-theme="dark"] {
  --primary-color: #aec2ff;
  --primary-container: #002a77;
  --on-primary: #002a77;
  --on-primary-container: #dde3ff;
  --secondary-color: #b8c3ff;
  --accent-color: #4cc9f0;
  --background-color: #121317;
  --surface-color: #1c1d24;
  --surface-container: #22232b;
  --surface-container-high: #2b2d37;
  --text-primary: #e3e2e9;
  --text-secondary: #c4c6d0;
  --border-color: #333542;
  --outline-variant: #45464f;
  --success-color: #7dd995;
  --warning-color: #ffb74d;
  --error-color: #ffb4ab;
  --elevation-1: 0 1px 2px rgba(0, 0, 0, 0.30), 0 1px 3px 1px rgba(0, 0, 0, 0.20);
  --elevation-2: 0 1px 2px rgba(0, 0, 0, 0.34), 0 2px 6px 2px rgba(0, 0, 0, 0.22);
  --elevation-3: 0 1px 3px rgba(0, 0, 0, 0.36), 0 4px 8px 3px rgba(0, 0, 0, 0.22);
  --elevation-4: 0 2px 3px rgba(0, 0, 0, 0.38), 0 6px 10px 4px rgba(0, 0, 0, 0.22);
  --box-shadow: var(--elevation-1);
}

/* Reset and base styles */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Custom scrollbar, applied globally — thin, rounded, theme-aware, instead
   of the browser's traditional wide flat one. The detailed
   ::-webkit-scrollbar rules below (width/radius/hover) are what Chrome/Edge/
   Safari actually render. scrollbar-width/-color is scoped to ONLY apply
   where ::-webkit-scrollbar isn't supported (Firefox, which has no
   pseudo-element scrollbar styling at all) — recent Chromium versions also
   understand scrollbar-width and would otherwise let its blunt "thin"
   keyword (a fixed ~2px, no radius/hover control) silently override the
   richer webkit customization below instead of layering with it. */
@supports not selector(::-webkit-scrollbar) {
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--outline-variant) transparent;
  }
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background-color: var(--outline-variant);
  border-radius: var(--radius-pill);
  /* Transparent border + padding-box clip insets the visible thumb within
     the scrollbar's own track width, giving it a slim, floating look
     instead of a flat bar running edge-to-edge. */
  border: 2px solid transparent;
  background-clip: padding-box;
}

::-webkit-scrollbar-thumb:hover {
  background-color: var(--text-secondary);
}

::-webkit-scrollbar-corner {
  background: transparent;
}

body {
  font-family: var(--font-family, 'Roboto', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif);
  background-color: var(--background-color);
  color: var(--text-primary);
  transition: var(--transition);
  overflow-x: hidden;
}

/* Text size preference (see setFontSize() in app.js, [data-font-size] set
   pre-paint in index.html's bootstrap script) — same data-attribute +
   localStorage-cache convention as [data-sidebar-position]/[data-top-bar-fixed]
   below. `zoom` (not font-size or transform: scale) is deliberate: this
   stylesheet is almost entirely px-based, not rem, so scaling the root
   font-size alone would leave nearly everything else unchanged; `transform:
   scale` would also make this element a new containing block for its
   position:fixed descendants (the top bar, modals, dropdowns), breaking
   their positioning. zoom scales layout and text together, uniformly,
   without that containing-block side effect. No rule needed for the
   default/"normal" case — the attribute is simply absent then. */
html[data-font-size="small"] {
  zoom: 0.9;
}

html[data-font-size="large"] {
  zoom: 1.15;
}

html[data-font-size="x-large"] {
  zoom: 1.3;
}

/* App layout */
#app {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* Top Bar */
/* topbar-color/topbar-gradient/topbar-text (core/theme-validator.ts) were
   added because the top bar previously had no theme hook at all — always
   var(--surface-color), so an otherwise-fully-themed page still showed a
   plain white bar. topbar-color is the solid, contrast-checked fallback;
   topbar-gradient is the optional decorative overlay on top of it. Every
   consumer below falls back to today's plain-white/dark-text look when a
   theme doesn't set these, so this is fully backward-compatible. */
.top-bar {
  height: var(--top-bar-height);
  background: var(--topbar-gradient, var(--topbar-color, var(--surface-color)));
  border-bottom: 1px solid var(--border-color);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 16px;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  box-shadow: var(--box-shadow);
}

.top-bar-left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.menu-toggle {
  background: none;
  border: none;
  color: var(--topbar-text, var(--text-primary));
  font-size: 1.25rem;
  cursor: pointer;
  padding: 8px;
  border-radius: var(--border-radius);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: var(--transition);
}

/* currentColor-based mix instead of a fixed rgba(0,0,0,.05)/rgba(255,255,255,.1)
   pair — adapts automatically to whichever color .menu-toggle actually ends
   up with (dark text on an unthemed white bar, or topbar-text on a themed
   one) instead of needing a separate [data-theme="dark"] override. */
.menu-toggle:hover {
  background-color: rgba(0, 0, 0, 0.05);
}

[data-theme="dark"] .menu-toggle:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

.logo {
  display: flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  /* Brand-colored on an unthemed (white) bar; switches to topbar-text when a
     theme actually colors the bar, so the wordmark doesn't sit in the same
     hue as its own background. */
  color: var(--topbar-text, var(--primary-color));
  font-weight: 700;
  font-size: 1.25rem;
  padding: 4px 8px;
  border-radius: var(--border-radius);
  transition: var(--transition);
}

.logo:hover {
  background-color: rgba(67, 97, 238, 0.1);
}

.logo i {
  font-size: 1.5rem;
}

.logo .brand-logo-img {
  height: 40px;
  width: auto;
  max-width: 180px;
  object-fit: contain;
}

.top-bar-center {
  flex: 1;
  max-width: 700px;
  margin: 0 24px;
}

/* A normal flex item between .top-bar-center and .top-bar-right (source
   order in index.html), not absolutely centered on the whole bar — .top-bar-
   center's search box is flex:1 and can genuinely span the bar's true
   midpoint, so an absolute dead-center overlay collided with it. Sitting in
   normal flex flow instead means it always lands in whatever space remains
   next to the search box, with no overlap regardless of search bar width or
   (with searchDrawer themes) whether the inline search box is even there. */
/* topbarClock theme layout option (core/theme-validator.ts) — off by
   default (display:none) for the platform's own default look and any theme
   that doesn't explicitly opt in; [data-topbar-clock="true"] is what
   actually shows it, same gating pattern as #search-toggle-btn/
   [data-search-drawer="true"]. */
.topbar-clock {
  display: none;
  align-items: center;
  gap: 8px;
  padding: 6px 10px 6px 14px;
  margin: 0 8px;
  flex-shrink: 0;
  border-radius: var(--radius-pill, 999px);
  background: color-mix(in srgb, currentColor 10%, transparent);
  color: var(--topbar-text, var(--text-primary));
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  letter-spacing: 0.5px;
  white-space: nowrap;
}

[data-topbar-clock="true"] .topbar-clock {
  display: flex;
}

.topbar-clock-hide {
  background: none;
  border: none;
  color: inherit;
  opacity: 0.6;
  cursor: pointer;
  padding: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  transition: var(--transition);
}

.topbar-clock-hide:hover {
  opacity: 1;
  background-color: color-mix(in srgb, currentColor 15%, transparent);
}

/* Collapsed state — clicking the hide button shrinks the widget down to a
   small icon-only pill instead of removing it outright, so there's still a
   one-click way back (clicking the pill itself) rather than a dead end that
   only a localStorage edit could undo. */
.topbar-clock.collapsed {
  padding: 6px;
  cursor: pointer;
}

.topbar-clock.collapsed #topbar-clock-time,
.topbar-clock.collapsed .topbar-clock-hide {
  display: none;
}

/* Active company badge — same normal-flex-item slot convention as
   .topbar-clock (between the search bar and the notification/user icons),
   but always on (no theme gate) since there's always an active company
   once logged in. [hidden] on the root element is the only "off" state,
   used only if somehow no company data is available yet. */
.company-badge {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  margin: 0 8px;
  flex-shrink: 0;
  border-radius: var(--radius-pill, 999px);
  background: color-mix(in srgb, currentColor 10%, transparent);
  color: var(--topbar-text, var(--text-primary));
  max-width: 220px;
  white-space: nowrap;
}

.company-badge[hidden] {
  display: none;
}

/* 28px = 70% of the site's own brand logo (.logo .brand-logo-img is 40px
   tall) — the size the user asked for relative to it. width:auto (not a
   fixed square) since a real uploaded company logo is rarely square,
   same object-fit:contain treatment as .brand-logo-img. */
.company-badge-logo {
  height: 28px;
  width: auto;
  max-width: 100px;
  object-fit: contain;
  border-radius: 4px;
  flex-shrink: 0;
}

.company-badge-icon {
  font-size: 1rem;
  opacity: 0.85;
  flex-shrink: 0;
}

/* Font Awesome's own .fas rule (font-awesome/all.min.css, loaded before
   this stylesheet — see index.html's <head>) sets display:inline-block
   unconditionally, which as author CSS beats the browser's default
   [hidden]->display:none UA rule regardless of source order or
   specificity ties. Without this override, the fallback business icon
   stayed visible even when JS set hidden=true after finding a real logo
   to show instead. */
.company-badge-icon[hidden] {
  display: none;
}

.company-badge-logo[hidden] {
  display: none;
}

.company-badge-name {
  font-weight: 600;
  font-size: 0.85rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

.search-container {
  position: relative;
  width: 100%;
}

.search-icon {
  position: absolute;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-secondary);
  pointer-events: none;
}

.global-search {
  width: 100%;
  padding: 10px 16px 10px 44px;
  border: 1px solid var(--border-color);
  border-radius: 24px;
  background-color: var(--background-color);
  color: var(--text-primary);
  font-size: 0.9rem;
  transition: var(--transition);
}

.global-search:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 20%, transparent);
  background-color: var(--surface-color);
}

.search-results {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background-color: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  /* Overlay elevation (matches .user-dropdown/the search-drawer variant
     below, both already at elevation-3) — was elevation-1 (a resting
     surface's own level), too flat for something that floats above the
     page. */
  box-shadow: var(--elevation-3);
  max-height: 400px;
  overflow-y: auto;
  z-index: 1000;
  display: none;
  padding: 4px;
}

.search-results.show {
  display: block;
}

/* searchDrawer theme layout option — see toggleSearchDrawer() in app.js.
   #search-toggle-btn (hidden by default, shown only here) sits in
   .top-bar-right next to the notification icons; clicking it toggles
   .search-container.drawer-open. Reuses the existing .search-container/
   .global-search/.search-results elements completely unchanged — only
   their position/default-visibility changes in this mode, none of the
   underlying search JS does. */
#search-toggle-btn {
  display: none;
}

[data-search-drawer="true"] #search-toggle-btn {
  display: flex;
}

[data-search-drawer="true"] .top-bar-center {
  /* Kept in the layout (not display:none) so .search-container, its child,
     can still render when opened — an ancestor's display:none would hide
     it regardless of its own position/display. flex/margin/max-width all
     need zeroing too, not just width, or the base .top-bar-center rule
     (flex:1, 24px side margins) would still claim space in the bar even
     though its content is invisible until opened. */
  flex: 0;
  max-width: 0;
  margin: 0;
  width: 0;
  overflow: visible;
}

[data-search-drawer="true"] .search-container {
  position: absolute;
  top: calc(100% + 8px);
  right: 16px;
  width: 320px;
  max-width: calc(100vw - 32px);
  background-color: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  box-shadow: var(--elevation-3);
  padding: 8px;
  display: none;
}

[data-search-drawer="true"] .search-container.drawer-open {
  display: block;
  animation: dropdownFade 0.2s ease;
}

[data-search-drawer="true"] .search-icon,
[data-search-drawer="true"] .global-search {
  display: none;
}

[data-search-drawer="true"] .search-container.drawer-open .search-icon {
  display: block;
}

[data-search-drawer="true"] .search-container.drawer-open .global-search {
  display: block;
}

[data-search-drawer="true"] .search-container .search-results {
  position: static;
  border: none;
  box-shadow: none;
  margin-top: 4px;
}

.search-result-item {
  padding: 12px 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 12px;
  transition: var(--transition);
  /* Same inset "state layer" shape as .dropdown-item — the container's own
     4px padding (added above) provides the horizontal inset, so this only
     needs a small vertical gap between items plus the rounded corners. */
  margin: 2px 0;
  border-radius: var(--radius-button);
}

.search-result-item:hover {
  background-color: var(--surface-container);
}

.search-result-item i {
  color: var(--text-secondary);
  width: 20px;
  text-align: center;
}

.search-result-item .result-info {
  flex: 1;
}

.search-result-item .result-title {
  font-weight: 500;
  color: var(--text-primary);
}

.search-result-item .result-type {
  font-size: 0.75rem;
  color: var(--text-secondary);
  text-transform: uppercase;
}

.top-bar-right {
  display: flex;
  align-items: center;
  gap: 4px;
}

.top-bar-btn {
  background: none;
  border: none;
  color: var(--topbar-text, var(--text-primary));
  font-size: 1.1rem;
  cursor: pointer;
  padding: 8px;
  border-radius: var(--border-radius);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: var(--transition);
}

.top-bar-btn:hover {
  background-color: rgba(0, 0, 0, 0.05);
}

[data-theme="dark"] .top-bar-btn:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

.notification-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  background-color: var(--error-color);
  color: white;
  font-size: 0.65rem;
  font-weight: 600;
  min-width: 16px;
  height: 16px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
}

.user-menu-container {
  position: relative;
  margin-left: 8px;
}

.user-menu-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  color: var(--topbar-text, var(--text-primary));
  cursor: pointer;
  padding: 4px 8px 4px 4px;
  border-radius: 24px;
  transition: var(--transition);
}

.user-menu-toggle:hover {
  background-color: rgba(0, 0, 0, 0.05);
}

[data-theme="dark"] .user-menu-toggle:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

.user-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background-color: var(--primary-color);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 0.85rem;
}

.user-name {
  font-weight: 500;
  font-size: 0.9rem;
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-menu-toggle i:last-child {
  font-size: 0.7rem;
  color: var(--text-secondary);
}

.user-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 240px;
  background-color: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  /* Overlay elevation, matching this file's own dp-scale comment at the top
     (menus/dropdowns sit at elevation 3) — was a hardcoded shadow that
     couldn't adapt to dark mode the way every other elevation on the
     platform already does. */
  box-shadow: var(--elevation-3);
  z-index: 1000;
  display: none;
  overflow: hidden;
}

.user-dropdown.show {
  display: block;
  animation: dropdownFade 0.2s ease;
}

@keyframes dropdownFade {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.dropdown-header {
  padding: 16px;
  border-bottom: 1px solid var(--border-color);
}

.dropdown-user-info {
  display: flex;
  align-items: center;
  gap: 12px;
}

.dropdown-avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background-color: var(--primary-color);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 1.25rem;
}

.dropdown-user-details {
  display: flex;
  flex-direction: column;
}

.dropdown-user-name {
  font-weight: 600;
  color: var(--text-primary);
}

.dropdown-user-email {
  font-size: 0.8rem;
  color: var(--text-secondary);
}

.dropdown-divider {
  height: 1px;
  background-color: var(--border-color);
  margin: 8px 0;
}

.dropdown-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  color: var(--text-primary);
  text-decoration: none;
  transition: var(--transition);
  border: none;
  background: none;
  /* Inset rounded item, same state-layer idea as the sidebar's .menu-link —
     a Material menu's items float inside the menu's own padding rather than
     running edge-to-edge, so their hover/press shape is a rounded rect, not
     a flat full-bleed row. calc() (not a horizontal margin) keeps this
     self-contained to just this rule, without needing to touch
     .user-dropdown's own padding (which .dropdown-header/.dropdown-divider
     also share and would otherwise need compensating negative margins). */
  width: calc(100% - 16px);
  margin: 2px 8px;
  border-radius: var(--radius-button);
  text-align: left;
  cursor: pointer;
  font-size: 0.9rem;
}

.dropdown-item:hover {
  background-color: var(--surface-container);
  color: var(--primary-color);
}

.dropdown-item.logout-btn {
  color: var(--error-color);
}

.dropdown-item.logout-btn:hover {
  background-color: color-mix(in srgb, var(--error-color) 12%, transparent);
  color: var(--error-color);
}

.dropdown-item i {
  width: 20px;
  text-align: center;
  color: var(--text-secondary);
}

/* Top-level parent menu row (Home, CRM, Inventory, Apps, Settings, ...) —
   a second fixed row below .top-bar. Clicking an item loads its children
   into the sidebar (see app.js's renderTopMenu/selectTopMenu). Always
   icon+label regardless of sidebar collapse state. */
.top-bar-menu {
  height: var(--top-bar-menu-height);
  /* Same topbar-gradient/topbar-color tokens as .top-bar (core/theme-
     validator.ts) — this row sits directly beneath it at full width, so
     reusing the identical gradient value here (rather than inventing a
     second token) makes a horizontal gradient read as one continuous band
     across both bars, not two independently-restarted ones. */
  background: var(--topbar-gradient, var(--topbar-color, var(--surface-color)));
  border-bottom: 1px solid var(--border-color);
  position: fixed;
  top: var(--top-bar-height);
  left: 0;
  right: 0;
  z-index: 999;
  overflow-x: auto;
  overflow-y: hidden;
}

.top-menu-list {
  list-style: none;
  display: flex;
  align-items: stretch;
  height: 100%;
  padding: 0 12px;
  white-space: nowrap;
}

.top-menu-item {
  flex-shrink: 0;
  position: relative;
}

.top-menu-link {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 100%;
  padding: 8px 20px;
  color: var(--topbar-text, var(--text-secondary));
  text-decoration: none;
  font-size: 0.85rem;
  font-weight: 500;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: var(--transition);
}

.top-menu-link i {
  font-size: 0.95rem;
}

.top-menu-link:hover {
  color: var(--topbar-text, var(--primary-color));
  background-color: var(--surface-container);
}

.top-menu-link.active {
  color: var(--topbar-text, var(--primary-color));
  border-bottom-color: var(--topbar-text, var(--primary-color));
  font-weight: 600;
}

/* Hidden by default — this markup renders whenever a root has children
   regardless of the topMenuDropdown flag (see renderTopMenu() in app.js),
   but showing a "this has a dropdown" hint makes no sense in default/
   split-mode, where hovering doesn't actually open anything. */
.top-menu-caret {
  display: none;
  font-size: 0.65rem;
  opacity: 0.7;
}

[data-top-menu-dropdown="true"] .top-menu-caret {
  display: inline-block;
}

/* topMenuDropdown theme layout option (core/theme-validator.ts) — this
   markup is always rendered by renderTopMenu() regardless of whether the
   flag is on; only its visibility is gated by the attribute selector below,
   so there's no dependency on applyActiveTheme()'s async fetch having
   resolved before renderTopMenu() runs. Reuses .menu-tree/.menu-link/
   .submenu completely unchanged (the exact same markup/classes the sidebar
   renders via renderMenuTree()) — only the color overrides just below are
   needed, since those were written assuming a --sidebar-color background,
   not this panel's --surface-color one. */
/* position:fixed (JS-positioned on hover, see renderTopMenu() in app.js) —
   NOT position:absolute against .top-menu-item. .top-bar-menu needs
   overflow-x:auto to scroll when there are many top-level items (see its
   own rule above), and overflow-x:auto forces overflow-y to compute as auto
   too, not "visible" — so an absolutely-positioned dropdown nested inside
   would get clipped by that scroll container regardless of its own
   display:block, hiding whichever items scrolled past the clip edge (this
   is exactly what broke Settings' menu after an earlier, wrong fix that
   removed the bar's scrolling instead). Fixed positioning escapes that
   clipping entirely, at the cost of needing JS to compute where "below the
   hovered item" actually is. */
.top-menu-dropdown {
  display: none;
  position: fixed;
  min-width: 240px;
  max-height: 70vh;
  overflow-y: auto;
  background-color: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: 0 0 var(--border-radius) var(--border-radius);
  box-shadow: var(--elevation-3, var(--box-shadow));
  z-index: 1001;
  padding: 6px 0;
}

[data-top-menu-dropdown="true"] .top-menu-item:hover .top-menu-dropdown {
  display: block;
  animation: dropdownFade 0.2s ease;
}

/* JS-controlled, independent of the theme's own topMenuDropdown flag above —
   a categorized top bar's category dropdown (public/app.js's
   renderTopMenuCategorized) is the ONLY way to reach 2 of a category's 3
   modules, not a hover convenience, so it must be reliably click-openable
   regardless of that theme setting or hover/touch support at all. */
.top-menu-dropdown.show {
  display: block;
  animation: dropdownFade 0.2s ease;
}

/* Grandchildren reveal on hover too ("hover shows the children and its
   children"), not just on click — .menu-link's own click-toggle handler
   (addMenuClickHandlers) still works here too, so a touch/keyboard user can
   still open it without hovering. */
.top-menu-dropdown .menu-item:hover > .submenu {
  display: block;
}

.top-menu-dropdown .menu-link,
.top-menu-dropdown .menu-link:hover {
  color: var(--text-primary);
}

.top-menu-dropdown .submenu {
  background-color: var(--surface-container);
}

/* Main Container */
.main-container {
  display: flex;
  margin-top: var(--top-bars-height);
  min-height: calc(100vh - var(--top-bars-height));
}

/* Sidebar */
.sidebar {
  width: var(--sidebar-width);
  /* Falls back to --surface-color (same as every other card) when a theme
     doesn't set --sidebar-color — see ALLOWED_THEME_TOKENS in
     core/theme-validator.ts for why this is a separate, optional token.
     sidebar-gradient is an optional decorative overlay on top of that solid,
     contrast-checked fallback — same convention as primary-gradient/
     accent-gradient/topbar-gradient. */
  background: var(--sidebar-gradient, var(--sidebar-color, var(--surface-color)));
  border-right: 1px solid var(--border-color);
  height: calc(100vh - var(--top-bars-height));
  position: fixed;
  top: var(--top-bars-height);
  left: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  transition: var(--transition);
  overflow-y: auto;
}

/* Theme-driven top-bar fixed/static — see applyActiveTheme() in app.js,
   which sets this attribute from a theme's optional layout.topBarFixed.
   Unwinds every offset above that only makes sense when .top-bar/
   .top-bar-menu are actually fixed and reserving space at the viewport top. */
[data-top-bar-fixed="false"] .top-bar,
[data-top-bar-fixed="false"] .top-bar-menu {
  position: static;
}

[data-top-bar-fixed="false"] .main-container {
  margin-top: 0;
  min-height: 100vh;
}

[data-top-bar-fixed="false"] .sidebar {
  top: 0;
  height: 100vh;
}

[data-top-bar-fixed="false"] .main-content {
  min-height: 100vh;
}

/* combinedNav theme layout option — merges parent+child menus into one
   sidebar tree (see renderCombinedMenu() in app.js) and hides the second
   .top-bar-menu row entirely, so every offset that reserved space for BOTH
   bars (var(--top-bars-height)) only needs to clear the one remaining bar
   (var(--top-bar-height)) now. Mirrors the [data-top-bar-fixed="false"]
   block above, just with --top-bar-height instead of 0/100vh, since the
   top bar itself is still there (only the menu row is gone). */
[data-combined-nav="true"] .top-bar-menu {
  display: none;
}

[data-combined-nav="true"] .main-container {
  margin-top: var(--top-bar-height);
  min-height: calc(100vh - var(--top-bar-height));
}

[data-combined-nav="true"] .sidebar {
  top: var(--top-bar-height);
  height: calc(100vh - var(--top-bar-height));
}

[data-combined-nav="true"] .main-content {
  min-height: calc(100vh - var(--top-bar-height));
}

/* Both flags at once — see the equivalent .toast-container comment above;
   higher specificity wins over either single-attribute rule regardless of
   source order. No bar is fixed/reserving space at all in this combination. */
[data-top-bar-fixed="false"][data-combined-nav="true"] .main-container {
  margin-top: 0;
  min-height: 100vh;
}

[data-top-bar-fixed="false"][data-combined-nav="true"] .sidebar {
  top: 0;
  height: 100vh;
}

[data-top-bar-fixed="false"][data-combined-nav="true"] .main-content {
  min-height: 100vh;
}

.sidebar.collapsed {
  width: 72px;
}

.sidebar-header {
  padding: 16px;
  border-bottom: 1px solid var(--border-color);
}

.sidebar-header h3 {
  color: var(--sidebar-text, var(--text-secondary));
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.sidebar.collapsed .sidebar-header h3 {
  display: none;
}

.sidebar-nav {
  flex: 1;
  padding: 8px 0;
  overflow-y: auto;
}

.menu-tree {
  list-style: none;
}

.menu-item {
  position: relative;
}

.menu-link {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  /* Inset "state layer" pill (Material Navigation Drawer's own item shape —
     Gmail/Google Drive's sidebar) rather than the old full-bleed row with a
     left border stripe. The margin is what creates the inset; border-radius
     is what makes it a pill; overflow:hidden (kept below) clips the ripple
     and the gradient-wash ::before to that same pill shape automatically. */
  margin: 2px 12px;
  border-radius: var(--radius-pill);
  color: var(--sidebar-text, var(--text-primary));
  text-decoration: none;
  transition: var(--transition);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

/* Gradient hover wash — a low-opacity ::before overlay rather than painting
   the gradient straight onto .menu-link's own background, since a full-
   opacity theme gradient behind sidebar-text could fail contrast for some
   themes; fading a decorative overlay instead keeps text legible regardless
   of the active theme's exact gradient colors. Falls back to a solid tint
   (matches the pre-existing color-mix look) when a theme doesn't set
   --primary-gradient. */
.menu-link::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--primary-gradient, var(--sidebar-active-color, var(--primary-color)));
  opacity: 0;
  transition: opacity 0.25s ease;
}

.menu-link:hover {
  color: var(--sidebar-text, var(--text-primary));
}

.menu-link:hover::before {
  opacity: 0.18;
}

.menu-link i:first-child {
  transition: transform 0.25s ease;
}

.menu-link:hover i:first-child {
  transform: scale(1.15);
}

.menu-link.active {
  background-color: var(--sidebar-active-color, color-mix(in srgb, var(--primary-color) 12%, transparent));
  color: var(--sidebar-active-text, var(--primary-color));
  font-weight: 500;
}

.menu-link i:first-child {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  flex-shrink: 0;
}

.menu-link .menu-title {
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 0.9rem;
}

.menu-link .menu-toggle {
  font-size: 0.7rem;
  color: var(--sidebar-text, var(--text-secondary));
  transition: var(--transition);
}

.menu-link[aria-expanded="true"] .menu-toggle {
  transform: rotate(180deg);
}

.sidebar.collapsed .menu-title,
.sidebar.collapsed .menu-toggle {
  display: none;
}

.sidebar.collapsed .menu-link {
  justify-content: center;
  padding: 12px;
}

.submenu {
  list-style: none;
  /* Falls back to the page background, but a themed sidebar-color keeps
     nested items the same flat tone as their parent instead of punching a
     light-background hole into a dark sidebar. */
  background-color: var(--sidebar-color, var(--background-color));
  border-left: 2px solid var(--border-color);
  margin-left: 12px;
  padding: 4px 0;
  display: none;
}

.submenu.show {
  display: block;
}

.submenu .menu-link {
  padding: 8px 16px 8px 36px;
  font-size: 0.85rem;
}

.submenu .menu-link i:first-child {
  font-size: 0.85rem;
}

.sidebar.collapsed .submenu {
  position: absolute;
  left: 72px;
  top: 0;
  width: 220px;
  background-color: var(--sidebar-color, var(--surface-color));
  border: 1px solid var(--border-color);
  border-left: none;
  /* This is a flyout, not a resting surface — same overlay elevation as
     every other menu/dropdown fix above. */
  box-shadow: var(--elevation-3);
  z-index: 1000;
  margin-left: 0;
  border-radius: 0 var(--border-radius) var(--border-radius) 0;
  padding: 4px 0;
}

.sidebar.collapsed .menu-item:hover .submenu {
  display: block;
}

/* Main Content */
.main-content {
  flex: 1;
  /* Without this, a flex item's default min-width:auto lets wide children
     (e.g. a table with nowrap cells) push the whole page wider than the
     viewport instead of scrolling within their own container. */
  min-width: 0;
  margin-left: var(--sidebar-width);
  min-height: calc(100vh - var(--top-bars-height));
  background-color: var(--background-color);
  transition: var(--transition);
  overflow-x: hidden;
}

/* Theme-driven sidebar side — see applyActiveTheme() in app.js, which sets
   this attribute from a theme's optional layout.sidebarPosition. Mirrors
   .sidebar/.main-content's left-side rules above; the mobile slide-off
   transform is mirrored separately inside the max-width:1024px block below. */
[data-sidebar-position="right"] .sidebar {
  left: auto;
  right: 0;
  border-right: none;
  border-left: 1px solid var(--border-color);
}

[data-sidebar-position="right"] .main-content {
  margin-left: 0;
  margin-right: var(--sidebar-width);
}

[data-sidebar-position="right"] .main-content.expanded {
  margin-left: 0;
  margin-right: 72px;
}

.main-content.expanded {
  margin-left: 72px;
}

.content {
  padding: 24px;
}

/* Dashboard Styles */
.dashboard-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 24px;
  flex-wrap: wrap;
  gap: 16px;
}

.dashboard-header h1 {
  color: var(--text-primary);
  font-size: 1.75rem;
  font-weight: 600;
}

.dashboard-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
  margin-bottom: 24px;
}

.stat-card {
  --stat-accent: var(--primary-color);
  background-color: var(--surface-color);
  border-radius: 16px;
  padding: 24px;
  box-shadow: var(--box-shadow);
  border: 1px solid var(--border-color);
  transition: var(--transition);
  position: relative;
  overflow: hidden;
}

/* A soft, blurred wash of the card's own accent color in the top-right
   corner — replaces the old flat 3px top bar with something that reads as
   premium/SaaS-dashboard rather than a plain stripe. Purely decorative
   (pointer-events: none), sits behind every other child via z-index. */
.stat-card::before {
  content: '';
  position: absolute;
  top: -40px;
  right: -40px;
  width: 140px;
  height: 140px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--stat-accent) 16%, transparent);
  filter: blur(4px);
  pointer-events: none;
  z-index: 0;
}

.stat-card > * {
  position: relative;
  z-index: 1;
}

.stat-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--elevation-3);
  border-color: color-mix(in srgb, var(--stat-accent) 30%, var(--border-color));
}

/* Sets --stat-accent for the corner wash + hover border above — apply the
   same color-name class already used on .stat-card-icon (blue/green/
   orange/purple) to the outer .stat-card too. */
.stat-card.blue { --stat-accent: var(--primary-color); }
.stat-card.green { --stat-accent: var(--success-color); }
.stat-card.orange { --stat-accent: var(--warning-color); }
.stat-card.purple { --stat-accent: var(--accent-color); }

.stat-card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 16px;
}

.stat-card-title {
  color: var(--text-secondary);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.6px;
}

.stat-card-icon {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
  color: white;
  box-shadow: 0 4px 10px -2px color-mix(in srgb, var(--stat-accent) 45%, transparent);
}

.stat-card-icon.blue { background: var(--primary-gradient, var(--primary-color)); }
.stat-card-icon.green { background-color: var(--success-color); }
.stat-card-icon.orange { background-color: var(--warning-color); }
.stat-card-icon.purple { background: var(--accent-gradient, var(--accent-color)); }

.stat-card-value {
  font-size: 2.25rem;
  font-weight: 800;
  color: var(--text-primary);
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.5px;
}

.stat-card-trend {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.78rem;
  font-weight: 600;
  margin-top: 12px;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  background: var(--surface-container);
  color: var(--text-secondary);
}

.stat-card-trend.positive { color: var(--success-color); background: color-mix(in srgb, var(--success-color) 14%, var(--surface-color)); }
.stat-card-trend.negative { color: var(--error-color); background: color-mix(in srgb, var(--error-color) 14%, var(--surface-color)); }

/* Dashboard charts — a single chart card takes the full row width if the
   second (Apps, admin-only) card isn't rendered for this user. */
.dashboard-charts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 16px;
  margin-bottom: 24px;
}

.chart-card {
  background-color: var(--surface-color);
  border-radius: var(--border-radius);
  border: 1px solid var(--border-color);
  box-shadow: var(--box-shadow);
  padding: 20px;
}

.chart-card-header {
  margin-bottom: 16px;
}

.chart-card-header h3 {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text-primary);
}

.chart-card-body {
  position: relative;
  height: 260px;
}

/* Content Sections */
.content-section {
  background-color: var(--surface-color);
  border-radius: var(--border-radius);
  padding: 24px;
  margin-bottom: 24px;
  box-shadow: var(--box-shadow);
  border: 1px solid var(--border-color);
  position: relative;
  overflow: hidden;
}

.content-section::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--primary-gradient, transparent);
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  flex-wrap: wrap;
  gap: 12px;
}

.section-header h2 {
  color: var(--text-primary);
  font-size: 1.25rem;
  font-weight: 600;
}

.section-header .btn {
  padding: 8px 16px;
  font-size: 0.85rem;
}

/* Apps Grid */
.apps-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 16px;
}

.app-card {
  background-color: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  padding: 20px;
  box-shadow: var(--elevation-1);
  transition: var(--transition);
  position: relative;
  overflow: hidden;
}

.app-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--accent-gradient, transparent);
}

.app-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--elevation-4);
  border-color: var(--primary-color);
}

.app-card-header {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 12px;
}

.app-card-icon {
  width: 48px;
  height: 48px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  color: white;
  flex-shrink: 0;
}

.app-card-icon.blue { background: var(--primary-gradient, var(--primary-color)); }
.app-card-icon.green { background-color: var(--success-color); }
.app-card-icon.orange { background-color: var(--warning-color); }
.app-card-icon.red { background-color: var(--error-color); }
.app-card-icon.purple { background: var(--accent-gradient, var(--accent-color)); }
.app-card-icon.teal { background-color: var(--secondary-color); }

.app-card-info {
  flex: 1;
  min-width: 0;
}

.app-card-name {
  font-weight: 600;
  font-size: 1rem;
  color: var(--text-primary);
  margin-bottom: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-card-description {
  font-size: 0.8rem;
  color: var(--text-secondary);
  line-height: 1.4;
  /* Fixed 2-line slot regardless of actual description length, so card
     height stays consistent across a grid of cards — a long description is
     clamped with an ellipsis rather than pushing the card taller; a short
     one still reserves the same space rather than leaving a ragged gap. */
  height: 2.8em;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.app-card-description.expanded {
  height: auto;
  display: block;
  -webkit-line-clamp: unset;
  overflow: visible;
}

.app-card-description-toggle {
  background: none;
  border: none;
  padding: 0;
  margin-top: 2px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--primary-color);
  cursor: pointer;
}

.app-card-description-toggle:hover {
  text-decoration: underline;
}

.app-card-state {
  padding: 4px 10px;
  border-radius: 12px;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  white-space: nowrap;
}

.app-card-state.installed {
  background-color: rgba(76, 175, 80, 0.15);
  color: var(--success-color);
}

.app-card-state.uninstalled {
  background-color: rgba(108, 117, 125, 0.15);
  color: var(--text-secondary);
}

.app-card-state.deactivated {
  background-color: rgba(255, 152, 0, 0.15);
  color: var(--warning-color);
}

.app-card-state.to-install {
  background-color: rgba(255, 152, 0, 0.15);
  color: var(--warning-color);
}

.app-card-state.to-upgrade {
  background-color: rgba(67, 97, 238, 0.15);
  color: var(--primary-color);
}

.app-card-state.to-remove {
  background-color: rgba(244, 67, 54, 0.15);
  color: var(--error-color);
}

.app-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--border-color);
}

.app-card-actions .btn {
  flex: 1;
  min-width: fit-content;
  gap: 5px;
  padding: 5px 8px;
  font-size: 0.72rem;
}

.screen-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 400px;
  overflow-y: auto;
}

.screen-list-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: var(--border-radius);
  background-color: var(--surface-container);
}

.screen-list-item-icon {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background-color: var(--primary-container);
  color: var(--on-primary-container);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  flex-shrink: 0;
}

.screen-list-item-info {
  min-width: 0;
}

.screen-list-item-title {
  font-weight: 500;
  color: var(--text-primary);
  font-size: 0.9rem;
}

.screen-list-item-meta {
  font-size: 0.78rem;
  color: var(--text-secondary);
  font-family: 'Courier New', monospace;
}

/* Table Styles */
.table-container {
  overflow-x: auto;
}

/* Caps a table-in-a-card's height instead of letting the card keep growing
   with every row (Settings > Developers / Recent Pushes, etc.) — scrolls
   both ways within that fixed height rather than pushing the rest of the
   page down. */
.table-container-scroll {
  max-height: 320px;
  overflow-y: auto;
}

.table-search-input {
  width: 100%;
  margin-bottom: 8px;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.data-table th,
.data-table td {
  padding: 12px 16px;
  text-align: left;
  border-bottom: 1px solid var(--border-color);
}

.data-table th {
  background-color: var(--background-color);
  font-weight: 600;
  color: var(--text-secondary);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  white-space: nowrap;
}

.data-table tbody tr {
  transition: var(--transition);
}

.data-table tbody tr:hover {
  background-color: var(--background-color);
}

.data-table td {
  color: var(--text-primary);
}

.data-table .actions {
  display: flex;
  gap: 8px;
}

.data-table .btn {
  padding: 6px 12px;
  font-size: 0.8rem;
}

/* Form Styles */
.form-group {
  margin-bottom: 20px;
}

.form-group label {
  display: block;
  margin-bottom: 6px;
  font-weight: 500;
  color: var(--text-primary);
  font-size: 0.9rem;
}

.form-group input,
.form-group select,
.form-group textarea {
  width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background-color: var(--surface-color);
  color: var(--text-primary);
  font-size: 0.9rem;
  transition: var(--transition);
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 20%, transparent);
}

.form-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
}

.form-actions {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
  padding-top: 16px;
  border-top: 1px solid var(--border-color);
  margin-top: 24px;
}

/* Button Styles — Material 3 uses fully-rounded ("pill") buttons rather than
   the app's general 12px surface radius, so this gets its own token. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 20px;
  border: none;
  border-radius: var(--radius-button);
  cursor: pointer;
  font-weight: 500;
  font-size: 0.9rem;
  transition: var(--transition);
  text-decoration: none;
  position: relative;
  overflow: hidden;
}

.btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.btn-primary {
  background: var(--primary-gradient, var(--primary-color));
  color: var(--on-primary);
}

.btn-primary:hover:not(:disabled) {
  background-color: var(--secondary-color);
  box-shadow: var(--elevation-1);
  transform: translateY(-1px);
}

.btn-secondary {
  background-color: var(--surface-container);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
}

.btn-secondary:hover:not(:disabled) {
  background-color: var(--surface-container-high);
  box-shadow: var(--elevation-1);
  transform: translateY(-1px);
}

.btn-outline {
  background-color: transparent;
  border: 1px solid var(--primary-color);
  color: var(--primary-color);
}

.btn-outline:hover:not(:disabled) {
  background-color: var(--primary-container);
  border-color: var(--secondary-color);
  color: var(--secondary-color);
  transform: translateY(-1px);
  box-shadow: var(--elevation-1);
}

.btn-danger {
  background-color: var(--error-color);
  color: white;
}

.btn-danger:hover:not(:disabled) {
  background-color: #a3170f;
  box-shadow: var(--elevation-1);
  transform: translateY(-1px);
}

.btn-block {
  width: 100%;
}

.btn-sm {
  padding: 6px 14px;
  font-size: 0.8rem;
  gap: 6px;
}

.btn-lg {
  padding: 12px 24px;
  font-size: 1rem;
}

.btn-icon {
  padding: 8px;
  /* Round, not --radius-button — an icon-only button is still correctly a
     circular tap target in Material even when text buttons are squared. */
  border-radius: var(--radius-pill);
}

/* Material-style ripple: a click-feedback circle that expands from the exact
   click point and fades out. Spans are injected by app.js's delegated click
   listener onto .btn/.menu-link/.top-menu-link (each already has
   position:relative + overflow:hidden above so the ripple clips to the
   element's own bounds) and remove themselves once the animation ends. */
.ripple {
  position: absolute;
  border-radius: 50%;
  background-color: currentColor;
  opacity: 0.25;
  transform: scale(0);
  animation: ripple-effect 550ms cubic-bezier(0.2, 0, 0, 1);
  pointer-events: none;
}

@keyframes ripple-effect {
  to {
    transform: scale(2.5);
    opacity: 0;
  }
}

/* Modal Styles */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
  padding: 24px;
  animation: fadeIn 0.2s ease;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.modal {
  background-color: var(--surface-color);
  border-radius: var(--border-radius);
  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2);
  max-width: 600px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  animation: slideUp 0.3s ease;
}

@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px 24px;
  border-bottom: 1px solid var(--border-color);
}

.modal-header h3 {
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--text-primary);
}

.modal-close {
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: 1.25rem;
  cursor: pointer;
  padding: 4px;
  border-radius: var(--border-radius);
  transition: var(--transition);
}

.modal-close:hover {
  background-color: var(--background-color);
  color: var(--text-primary);
}

.modal-body {
  padding: 24px;
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  padding: 16px 24px;
  border-top: 1px solid var(--border-color);
}

/* Login Overlay */
.login-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: var(--background-color);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 3000;
  padding: 24px;
}

.login-overlay.hidden,
.auth-pending .login-overlay {
  display: none;
}

.login-container {
  width: 100%;
  max-width: 420px;
}

.login-card {
  background-color: var(--surface-color);
  border-radius: var(--border-radius);
  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.15);
  border: 1px solid var(--border-color);
  padding: 40px;
}

.login-header {
  text-align: center;
  margin-bottom: 32px;
}

.login-logo {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--primary-color);
  font-weight: 700;
  font-size: 1.5rem;
  margin-bottom: 24px;
}

.login-logo i {
  font-size: 2rem;
}

.login-logo .brand-logo-img {
  height: 40px;
  width: auto;
  max-width: 220px;
  object-fit: contain;
}

.login-header h2 {
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 8px;
}

.login-header p {
  color: var(--text-secondary);
  font-size: 0.9rem;
}

.login-footer {
  text-align: center;
  margin-top: 24px;
  padding-top: 24px;
  border-top: 1px solid var(--border-color);
}

.login-footer .text-secondary {
  font-size: 0.85rem;
}

/* Utility Classes */
.text-center { text-align: center; }
.text-secondary { color: var(--text-secondary); }
.text-primary { color: var(--primary-color); }
.text-success { color: var(--success-color); }
.text-warning { color: var(--warning-color); }
.text-error { color: var(--error-color); }

.mt-8 { margin-top: 8px; }
.mt-16 { margin-top: 16px; }
.mt-24 { margin-top: 24px; }
.mb-8 { margin-bottom: 8px; }
.mb-16 { margin-bottom: 16px; }
.mb-24 { margin-bottom: 24px; }

.d-flex { display: flex; }
.align-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-8 { gap: 8px; }
.gap-12 { gap: 12px; }
.gap-16 { gap: 16px; }
.flex-1 { flex: 1; }
.flex-wrap { flex-wrap: wrap; }

.w-100 { width: 100%; }

.hidden { display: none !important; }

/* Loading Spinner */
.loading {
  display: inline-block;
  width: 20px;
  height: 20px;
  border: 3px solid rgba(67, 97, 238, 0.3);
  border-radius: 50%;
  border-top-color: var(--primary-color);
  animation: spin 1s ease-in-out infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

.btn-loading {
  display: none;
}

.btn.loading .btn-text {
  display: none;
}

.btn.loading .btn-loading {
  display: inline-flex;
}

/* Empty State */
.empty-state {
  text-align: center;
  padding: 48px 24px;
  color: var(--text-secondary);
}

.empty-state i {
  font-size: 3rem;
  margin-bottom: 16px;
  opacity: 0.5;
}

.empty-state h3 {
  font-size: 1.1rem;
  color: var(--text-primary);
  margin-bottom: 8px;
}

.empty-state p {
  font-size: 0.9rem;
  margin-bottom: 16px;
}

/* Tabs */
.tabs {
  display: flex;
  border-bottom: 1px solid var(--border-color);
  margin-bottom: 24px;
}

.tab {
  padding: 12px 20px;
  background: none;
  border: none;
  color: var(--text-secondary);
  font-weight: 500;
  font-size: 0.9rem;
  cursor: pointer;
  position: relative;
  transition: var(--transition);
}

.tab:hover {
  color: var(--text-primary);
}

.tab.active {
  color: var(--primary-color);
}

.tab.active::after {
  content: '';
  position: absolute;
  bottom: -1px;
  left: 0;
  right: 0;
  height: 3px;
  border-radius: 3px 3px 0 0;
  background: var(--accent-gradient, var(--primary-color));
}

/* Badge */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 12px;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
}

.badge-primary { background-color: rgba(67, 97, 238, 0.15); color: var(--primary-color); }
.badge-success { background-color: rgba(76, 175, 80, 0.15); color: var(--success-color); }
.badge-warning { background-color: rgba(255, 152, 0, 0.15); color: var(--warning-color); }
.badge-error { background-color: rgba(244, 67, 54, 0.15); color: var(--error-color); }
.badge-secondary { background-color: rgba(108, 117, 125, 0.15); color: var(--text-secondary); }
/* Solid (not tinted) fill — deliberately more emphatic than .badge-error, for
   severities that should visually outrank a plain error (e.g. Error Logs'
   "critical" vs "error"). */
.badge-critical { background-color: var(--error-color); color: white; }

/* Responsive Design */
@media (max-width: 1024px) {
  .sidebar {
    transform: translateX(-100%);
  }
  
  .sidebar.open {
    transform: translateX(0);
    box-shadow: var(--box-shadow);
    z-index: 1100;
  }
  
  .main-content {
    margin-left: 0;
  }
  
  .main-content.expanded {
    margin-left: 0;
  }
  
  .sidebar-overlay {
    display: none;
    position: fixed;
    top: var(--top-bars-height);
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 99;
  }

  .sidebar-overlay.show {
    display: block;
  }

  /* Mirrors the desktop right-side rules above for this breakpoint — the
     slide-off direction and the margin resets both need the opposite side. */
  [data-sidebar-position="right"] .sidebar {
    transform: translateX(100%);
  }

  [data-sidebar-position="right"] .sidebar.open {
    transform: translateX(0);
  }

  [data-sidebar-position="right"] .main-content,
  [data-sidebar-position="right"] .main-content.expanded {
    margin-right: 0;
  }

  [data-top-bar-fixed="false"] .sidebar-overlay {
    top: 0;
  }

  [data-combined-nav="true"] .sidebar-overlay {
    top: var(--top-bar-height);
  }

  [data-top-bar-fixed="false"][data-combined-nav="true"] .sidebar-overlay {
    top: 0;
  }
}

@media (max-width: 768px) {
  .top-bar-center {
    display: none;
  }

  .company-badge {
    display: none;
  }

  .user-name {
    display: none;
  }
  
  .content {
    padding: 16px;
  }
  
  .dashboard-stats {
    grid-template-columns: 1fr;
  }
  
  .apps-grid {
    grid-template-columns: 1fr;
  }
  
  .modal {
    margin: 16px;
    max-height: calc(100vh - 32px);
  }
  
  .login-card {
    padding: 24px;
  }
  
  .form-row {
    grid-template-columns: 1fr;
  }
}

/* Settings Styles */
.settings-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 20px;
  margin-top: 20px;
}

.setting-card {
  background-color: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  padding: 20px;
  box-shadow: var(--box-shadow);
}

.setting-card h3 {
  color: var(--primary-color);
  margin-bottom: 15px;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 1rem;
}

.setting-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 0;
  border-bottom: 1px solid var(--border-color);
}

.setting-item:last-child {
  border-bottom: none;
}

.setting-item label {
  font-weight: 500;
  color: var(--text-primary);
}

.setting-item input[type="text"],
.setting-item input[type="number"],
.setting-item input[type="email"],
.setting-item select {
  padding: 8px 12px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background-color: var(--background-color);
  color: var(--text-primary);
  min-width: 150px;
}

.setting-item input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: var(--primary-color);
}

.setting-item .btn {
  padding: 8px 16px;
  font-size: 0.9rem;
}

.branding-logo-preview {
  width: 48px;
  height: 48px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background-color: var(--surface-container);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex-shrink: 0;
}

.branding-logo-preview img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.branding-logo-preview i {
  font-size: 1.25rem;
  color: var(--text-secondary);
}

/* Logo crop modal (Settings > Branding) — Cropper.js needs its target <img>
   inside a container with a real height to lay out the crop box against. */
.logo-crop-container {
  max-height: 60vh;
  overflow: hidden;
  background-color: var(--surface-container);
  border-radius: var(--border-radius);
}

.logo-crop-container img {
  display: block;
  max-width: 100%;
}

/* User/Company Detail View */
.detail-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 24px;
  flex-wrap: wrap;
  gap: 16px;
}

.detail-header h2 {
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--text-primary);
}

.detail-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border-color);
  margin-bottom: 24px;
}

.detail-tab {
  padding: 12px 20px;
  background: none;
  border: none;
  color: var(--text-secondary);
  font-weight: 500;
  font-size: 0.9rem;
  cursor: pointer;
  position: relative;
  transition: var(--transition);
}

.detail-tab:hover {
  color: var(--text-primary);
}

.detail-tab.active {
  color: var(--primary-color);
}

.detail-tab.active::after {
  content: '';
  position: absolute;
  bottom: -1px;
  left: 0;
  right: 0;
  height: 2px;
  background-color: var(--primary-color);
}

.detail-panel {
  display: none;
}

.detail-panel.active {
  display: block;
  animation: fadeIn 0.2s ease;
}

.detail-section {
  margin-bottom: 24px;
}

.detail-section h3 {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 16px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border-color);
}

.detail-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 16px;
}

.detail-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.detail-field label {
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-secondary);
}

.detail-field span {
  font-size: 0.9rem;
  color: var(--text-primary);
}

.detail-field .badge {
  align-self: flex-start;
}

/* Kanban Board */
.kanban-board {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  padding-bottom: 16px;
}

.kanban-column {
  min-width: 300px;
  max-width: 300px;
  background-color: var(--background-color);
  border-radius: var(--border-radius);
  padding: 16px;
  display: flex;
  flex-direction: column;
}

.kanban-column-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px;
  margin: -16px -16px 16px -16px;
  border-radius: var(--border-radius) var(--border-radius) 0 0;
  font-weight: 600;
  font-size: 0.85rem;
}

.kanban-column-header .badge {
  font-size: 0.7rem;
}

.kanban-cards {
  flex: 1;
  min-height: 200px;
}

.kanban-card {
  background-color: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  padding: 16px;
  margin-bottom: 12px;
  box-shadow: var(--box-shadow);
  cursor: grab;
  transition: var(--transition);
}

.kanban-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
}

.kanban-card-title {
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 8px;
}

.kanban-card-meta {
  display: flex;
  gap: 12px;
  font-size: 0.75rem;
  color: var(--text-secondary);
}

/* Activity Feed */
.activity-feed {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.activity-item {
  display: flex;
  gap: 12px;
  padding: 12px;
  background-color: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  transition: var(--transition);
}

.activity-item:hover {
  border-color: var(--primary-color);
}

.activity-icon {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: rgba(67, 97, 238, 0.1);
  color: var(--primary-color);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.activity-content {
  flex: 1;
  min-width: 0;
}

.activity-title {
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: 4px;
}

.activity-meta {
  font-size: 0.8rem;
  color: var(--text-secondary);
}

.activity-time {
  font-size: 0.75rem;
  color: var(--text-secondary);
  white-space: nowrap;
  margin-top: 4px;
}

/* Chatter/Discuss */
.chatter {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.chatter-message {
  display: flex;
  gap: 12px;
  padding: 16px;
  background-color: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
}

.chatter-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: var(--primary-color);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  flex-shrink: 0;
}

.chatter-content {
  flex: 1;
}

.chatter-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}

.chatter-author {
  font-weight: 600;
  color: var(--text-primary);
}

.chatter-time {
  font-size: 0.75rem;
  color: var(--text-secondary);
}

.chatter-body {
  color: var(--text-primary);
  line-height: 1.5;
}

.chatter-input {
  display: flex;
  gap: 12px;
  padding: 16px;
  background-color: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
}

.chatter-input textarea {
  flex: 1;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  padding: 12px;
  background-color: var(--background-color);
  color: var(--text-primary);
  resize: none;
  min-height: 60px;
  font-family: inherit;
  font-size: 0.9rem;
}

.chatter-input textarea:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
}

.chatter-input .btn {
  align-self: flex-end;
}

/* ==========================================================================
   React components: User Form, Group management, shared form widgets
   ========================================================================== */

/* Shared small utilities used across React components */
.required {
  color: var(--error-color);
  margin-left: 2px;
}

.form-hint {
  display: block;
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin-top: 6px;
  line-height: 1.4;
}

.error-message {
  display: block;
  font-size: 0.8rem;
  color: var(--error-color);
  margin-top: 6px;
}

.empty-message,
.loading-text {
  font-size: 0.85rem;
  color: var(--text-secondary);
  padding: 8px 0;
}

.alert {
  padding: 12px 16px;
  border-radius: var(--border-radius);
  font-size: 0.9rem;
  margin-bottom: 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.alert-error {
  background-color: rgba(244, 67, 54, 0.1);
  color: var(--error-color);
  border: 1px solid rgba(244, 67, 54, 0.3);
}

.alert-info {
  background-color: color-mix(in srgb, var(--primary-color) 10%, transparent);
  color: var(--primary-color);
  border: 1px solid color-mix(in srgb, var(--primary-color) 30%, transparent);
}

/* Global toast notifications — addons/base/static/src/frontend/components/Toast.tsx,
   mounted once into #toast-root (public/index.html). Stacks top-right,
   independent of #main-content so it survives view navigation. */
.toast-container {
  position: fixed;
  top: calc(var(--top-bars-height) + 16px);
  right: 16px;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 360px;
}

/* With no fixed top bar reserving space, the toast stack just needs its own
   fixed 16px inset instead of clearing var(--top-bars-height). */
[data-top-bar-fixed="false"] .toast-container {
  top: 16px;
}

/* combinedNav: only one bar's height to clear now, not the two-bar sum. */
[data-combined-nav="true"] .toast-container {
  top: calc(var(--top-bar-height) + 16px);
}

/* Both flags at once (a non-fixed top bar AND no menu row) — higher
   specificity than either single-attribute rule above, so it correctly
   wins regardless of source order: no bar is reserving space at all. */
[data-top-bar-fixed="false"][data-combined-nav="true"] .toast-container {
  top: 16px;
}

.toast {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  border-radius: var(--border-radius);
  background-color: var(--surface-color);
  box-shadow: var(--elevation-4);
  font-size: 0.9rem;
  color: var(--text-primary);
  border-left: 4px solid var(--outline-variant);
  animation: toast-slide-in 0.2s cubic-bezier(0.2, 0, 0, 1);
}

.toast-success {
  border-left-color: var(--success-color);
}

.toast-error {
  border-left-color: var(--error-color);
}

.toast-info {
  border-left-color: var(--primary-color);
}

.toast-message {
  flex: 1;
}

.toast-dismiss {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--text-secondary);
  padding: 0;
  transition: var(--transition);
}

.toast-dismiss:hover {
  color: var(--text-primary);
}

@keyframes toast-slide-in {
  from {
    opacity: 0;
    transform: translateX(24px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

/* Full-screen "Updating..." overlay shown during a deploy — #deploy-overlay
   in public/index.html, shown/hidden by app.js's deploy-status polling. */
#deploy-overlay {
  position: fixed;
  inset: 0;
  z-index: 3000;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(1px);
}

.deploy-overlay-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 20px 28px;
  border-radius: var(--border-radius);
  background-color: var(--surface-color);
  color: var(--text-primary);
  box-shadow: var(--elevation-4);
  font-size: 1rem;
  font-weight: 500;
}

.deploy-overlay-card i {
  color: var(--primary-color);
  font-size: 1.2rem;
}

.status-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 12px;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  background-color: rgba(108, 117, 125, 0.15);
  color: var(--text-secondary);
}

.status-badge.active {
  background-color: rgba(76, 175, 80, 0.15);
  color: var(--success-color);
}

.status-badge.inactive {
  background-color: rgba(244, 67, 54, 0.15);
  color: var(--error-color);
}

/* GroupList's Owner column (describeGroupOwner, groupOwnership.ts) — module
   and orphaned get their own accent; platform/custom reuse the base grey
   .status-badge look above, same as an unowned group needs no extra alarm. */
.status-badge.owner-module {
  background-color: rgba(67, 97, 238, 0.15);
  color: var(--primary-color);
}

.status-badge.owner-orphaned {
  background-color: rgba(255, 152, 0, 0.15);
  color: var(--warning-color);
}

.group-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  margin: 2px;
  border-radius: 10px;
  font-size: 0.7rem;
  font-weight: 500;
  background-color: rgba(67, 97, 238, 0.12);
  color: var(--primary-color);
}

.group-badge.more {
  background-color: rgba(108, 117, 125, 0.15);
  color: var(--text-secondary);
}

.actions-header,
.actions-cell {
  text-align: right;
  white-space: nowrap;
}

/* User Form modal (React) */
.user-form-modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  z-index: 2000;
  padding: 40px 24px;
  overflow-y: auto;
  animation: fadeIn 0.2s ease;
}

.user-form-modal {
  background-color: var(--surface-color);
  border-radius: var(--border-radius);
  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2);
  max-width: 820px;
  width: 100%;
  animation: slideUp 0.3s ease;
}

.user-form-modal .modal-error {
  margin: 0 24px;
  padding: 12px 16px;
  border-radius: var(--border-radius);
  background-color: rgba(244, 67, 54, 0.1);
  color: var(--error-color);
  border: 1px solid rgba(244, 67, 54, 0.3);
  font-size: 0.9rem;
}

.modal-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border-color);
  padding: 0 24px;
}

.tab-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px 18px;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-secondary);
  font-weight: 500;
  font-size: 0.9rem;
  cursor: pointer;
  transition: var(--transition);
}

.tab-btn:hover {
  color: var(--text-primary);
}

.tab-btn.active {
  color: var(--primary-color);
  border-bottom-color: var(--primary-color);
}

.tab-icon {
  font-size: 1rem;
  line-height: 1;
}

/* Form tab bodies shared by General / Access Rights / Preferences tabs */
.form-tab {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.form-section {
  padding-bottom: 20px;
  border-bottom: 1px solid var(--border-color);
}

.form-section:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.section-title {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 16px;
}

.form-label {
  display: block;
  margin-bottom: 6px;
  font-weight: 500;
  color: var(--text-primary);
  font-size: 0.9rem;
}

.form-input {
  width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background-color: var(--surface-color);
  color: var(--text-primary);
  font-size: 0.9rem;
  font-family: inherit;
  transition: var(--transition);
}

.form-input:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 20%, transparent);
}

.form-input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.form-textarea {
  resize: vertical;
  min-height: 90px;
}

.form-group-checkbox .form-label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

.form-checkbox {
  width: 18px;
  height: 18px;
  accent-color: var(--primary-color);
  cursor: pointer;
}

/* Access Rights tab permission preview */
.permission-preview {
  background-color: var(--background-color);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  padding: 16px;
}

.permission-preview p {
  font-size: 0.85rem;
  color: var(--text-secondary);
  margin-bottom: 8px;
}

.permission-preview ul {
  list-style: disc;
  padding-left: 20px;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.permission-preview li {
  margin-bottom: 4px;
}

/* Password field */
.password-field {
  width: 100%;
}

.password-input-wrapper {
  display: flex;
  gap: 8px;
}

.password-input-wrapper .form-input {
  flex: 1;
}

.password-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 8px;
}

.generated-notice {
  font-size: 0.8rem;
  color: var(--success-color);
}

.password-hint {
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin-top: 6px;
}

/* Avatar upload */
.avatar-upload {
  width: 100%;
}

.avatar-dropzone {
  display: inline-flex;
  align-items: center;
  gap: 16px;
  position: relative;
  border: 2px dashed var(--border-color);
  border-radius: var(--border-radius);
  padding: 12px;
  cursor: pointer;
  transition: var(--transition);
}

.avatar-dropzone:hover,
.avatar-dropzone.dragging {
  border-color: var(--primary-color);
  background-color: rgba(67, 97, 238, 0.05);
}

.avatar-dropzone.has-image {
  border-style: solid;
  padding: 0;
}

.avatar-file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  overflow: hidden;
  pointer-events: none;
}

.avatar-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background-color: var(--background-color);
  border-radius: var(--border-radius);
  color: var(--text-secondary);
  text-align: center;
  padding: 8px;
}

.avatar-icon {
  font-size: 1.75rem;
}

.avatar-text {
  font-size: 0.75rem;
  max-width: 100px;
}

.avatar-preview {
  border-radius: var(--border-radius);
  object-fit: cover;
  display: block;
}

.avatar-overlay {
  position: absolute;
  top: 8px;
  right: 8px;
}

/* Company checkbox list */
.company-checkbox-list {
  width: 100%;
}

.company-search {
  margin-bottom: 8px;
}

.company-list {
  max-height: 220px;
  overflow-y: auto;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background-color: var(--surface-color);
}

.company-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  cursor: pointer;
  border-bottom: 1px solid var(--border-color);
  transition: var(--transition);
}

.company-item:last-child {
  border-bottom: none;
}

.company-item:hover {
  background-color: var(--background-color);
}

.company-item.company-select-all {
  background-color: var(--background-color);
  font-weight: 500;
}

.company-checkbox {
  width: 16px;
  height: 16px;
  accent-color: var(--primary-color);
  flex-shrink: 0;
}

.company-name {
  font-size: 0.9rem;
  color: var(--text-primary);
}

.company-email {
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin-left: auto;
}

.company-empty {
  padding: 16px;
  text-align: center;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

/* Group tree picker (Access Rights tab) */
.group-tree-picker {
  width: 100%;
}

.group-search {
  margin: 8px 0;
}

.group-tree {
  max-height: 280px;
  overflow-y: auto;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background-color: var(--surface-color);
  padding: 4px 0;
}

.group-tree-node {
  display: flex;
  flex-direction: column;
}

.group-tree-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  transition: var(--transition);
}

.group-tree-item:hover {
  background-color: var(--background-color);
}

.group-tree-item.selected {
  background-color: rgba(67, 97, 238, 0.08);
}

.group-tree-item.implied {
  opacity: 0.75;
}

.group-expand-btn {
  background: none;
  border: none;
  color: var(--text-secondary);
  cursor: pointer;
  font-size: 0.65rem;
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.group-expand-placeholder {
  width: 18px;
  flex-shrink: 0;
}

.group-checkbox-label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  flex: 1;
  min-width: 0;
}

.group-checkbox {
  width: 16px;
  height: 16px;
  accent-color: var(--primary-color);
  flex-shrink: 0;
}

.group-name {
  font-size: 0.9rem;
  color: var(--text-primary);
}

.group-name.implied {
  color: var(--text-secondary);
  font-style: italic;
}

.group-comment {
  font-size: 0.8rem;
  color: var(--text-secondary);
}

.implied-badge {
  display: inline-flex;
  margin-left: 8px;
  padding: 1px 8px;
  border-radius: 10px;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  background-color: rgba(108, 117, 125, 0.15);
  color: var(--text-secondary);
}

.group-children {
  display: flex;
  flex-direction: column;
}

.group-empty {
  padding: 16px;
  text-align: center;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.group-summary {
  margin-top: 10px;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.implied-count {
  margin-left: 6px;
  font-style: italic;
}

/* Group create/edit form — rendered directly inside .modal with no
   .modal-header/.modal-body wrapper (unlike the generic showRecordModal
   path), so .modal's own zero padding otherwise leaves the form flush
   against the modal edges. */
.group-form {
  padding: 24px;
}

.group-form .form-section {
  border-bottom: none;
  padding-bottom: 0;
}

.implied-groups-list {
  max-height: 200px;
  overflow-y: auto;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  padding: 8px 12px;
  background-color: var(--surface-color);
}

/* Qualified with the parent .implied-groups-list to out-specificity
   .form-group label (display: block) — this label sits inside a
   .form-group, and a bare single-class selector here would lose that
   cascade fight. */
.implied-groups-list .implied-group-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  font-size: 0.9rem;
  cursor: pointer;
}

/* .form-group input is a broad rule meant for text/select/textarea
   fields (width: 100%, border, padding) — it also matches this
   checkbox since it's nested inside a .form-group, stretching it into
   a full-width bordered blob. Reset it back to a normal checkbox. */
.implied-group-item input[type="checkbox"] {
  width: auto;
  flex-shrink: 0;
  padding: 0;
  border: none;
  background: none;
}

/* Groups Management page (React) */
.group-list-page .page-header,
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  flex-wrap: wrap;
  gap: 12px;
}

.page-header h2 {
  color: var(--text-primary);
  font-size: 1.25rem;
  font-weight: 600;
}

/* Users list page (React) */
.user-list {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

.list-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

.list-header h2 {
  color: var(--text-primary);
  font-size: 1.25rem;
  font-weight: 600;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.search-box .form-input {
  min-width: 220px;
}

.log-filter-bar,
.group-filter-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.log-filter-bar select.form-input,
.group-filter-bar select.form-input {
  min-width: 160px;
  width: auto;
}

/* Unlike .log-filter-bar's parent (.user-list, a flex column with its own
   `gap`), .group-list-page has no such gap — this bar needs its own spacing
   on both sides to sit correctly between the page header and the table. */
.group-filter-bar {
  margin: 0 0 16px;
}

.group-filter-bar input[type="date"].form-input {
  width: auto;
}

.filter-date-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.85rem;
  color: var(--text-secondary);
  font-weight: normal;
}

.truncated-cell-text {
  color: var(--primary-color);
}

.list-error {
  padding: 12px 16px;
  border-radius: var(--border-radius);
  background-color: rgba(244, 67, 54, 0.1);
  color: var(--error-color);
  border: 1px solid rgba(244, 67, 54, 0.3);
  font-size: 0.9rem;
}

.list-table-container {
  overflow-x: auto;
  background-color: var(--surface-color);
  border-radius: var(--border-radius);
  border: 1px solid var(--border-color);
}

/* Bounds the Logging screens (LogList.tsx — Activity/Audit/System/Error/
   Module Logs) to a fixed height instead of letting the table grow as tall
   as its rows need and pushing the page past the viewport. Without this,
   .list-table-container's own horizontal scrollbar (and the pagination bar
   right under it) end up well below the fold on a screenful of rows,
   forcing a scroll down just to reach either one. With it, only the ROWS
   scroll (vertically, inside the bounded box) — the scrollbar and
   pagination stay in a fixed, always-visible position.
   Scoped to .log-list specifically, not the shared .user-list class
   (also used by UserList.tsx) — this changes scroll behavior, not just
   spacing, and only the Logging screens asked for it.
   140px accounts for .content's own 24px top+bottom padding (48px) plus
   the dashboard-header title bar above the table (~90px) — approximate
   since the title bar's exact height isn't a CSS variable anywhere, but
   erring slightly short (a little empty space at the bottom) is the safe
   direction versus erring long (cutting the table off). */
.log-list {
  display: flex;
  flex-direction: column;
  height: calc(100vh - var(--top-bars-height) - 140px);
}

.log-list .list-table-container {
  flex: 1;
  min-height: 0; /* a flex child needs this to actually shrink/scroll instead of overflowing its flex parent */
  overflow-y: auto;
}

.list-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.list-table th,
.list-table td {
  padding: 10px 10px;
  text-align: left;
  border-bottom: 1px solid var(--border-color);
  white-space: nowrap;
}

.list-table .action-buttons {
  justify-content: flex-start;
}

.list-table th {
  background-color: var(--background-color);
  font-weight: 600;
  color: var(--text-secondary);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.list-table th.sortable-col {
  cursor: pointer;
  user-select: none;
}

.list-table th.sortable-col:hover {
  color: var(--text-primary);
}

.list-table .sort-icon {
  margin-left: 6px;
  font-size: 0.7rem;
  opacity: 0.4;
}

.list-table .sort-icon.active {
  opacity: 1;
  color: var(--primary-color);
}

/* Audit log's Old/New Value cells — collapsed key/value view for a JSON
   record snapshot, instead of a raw JSON string (see LogList.tsx's
   AuditValueCell). */
.json-value-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px;
  border-radius: 6px;
  border: 1px solid var(--border-color);
  background: var(--background-color);
  color: var(--text-secondary);
  font-size: 0.75rem;
  font-family: inherit;
  cursor: pointer;
}

.json-value-toggle:hover {
  border-color: var(--primary-color);
  color: var(--primary-color);
}

.json-value-toggle i {
  font-size: 0.65rem;
}

.json-kv-list {
  margin-top: 6px;
  max-height: 180px;
  min-width: 220px;
  overflow-y: auto;
  border: 1px solid var(--border-color);
  border-radius: 6px;
  padding: 6px 8px;
  background: var(--surface-color);
  font-size: 0.75rem;
}

.json-kv-row {
  display: flex;
  gap: 8px;
  padding: 3px 0;
  border-bottom: 1px dashed var(--border-color);
}

.json-kv-row:last-child {
  border-bottom: none;
}

.json-kv-key {
  color: var(--text-secondary);
  font-weight: 600;
  flex: 0 0 auto;
  min-width: 90px;
}

.json-kv-value {
  color: var(--text-primary);
  word-break: break-word;
}

.json-null {
  color: var(--text-secondary);
  font-style: italic;
}

.list-table tbody tr:hover {
  background-color: var(--background-color);
}

.list-table tbody tr:last-child td {
  border-bottom: none;
}

.list-table .table-loading,
.list-table .empty {
  text-align: center;
  padding: 32px;
  color: var(--text-secondary);
  white-space: normal;
}

.user-avatar-small {
  width: 32px;
  height: 32px;
  min-width: 32px;
  flex-shrink: 0;
  border-radius: 50%;
  object-fit: cover;
  overflow: hidden;
}

.user-avatar-placeholder {
  width: 32px;
  height: 32px;
  min-width: 32px;
  flex-shrink: 0;
  border-radius: 50%;
  background-color: var(--background-color);
  border: 1px solid var(--border-color);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-size: 0.9rem;
  line-height: 1;
}

.action-buttons {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}

.pagination {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  padding-top: 8px;
}

.pagination-info {
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.pagination-controls {
  display: flex;
  align-items: center;
  gap: 12px;
}

.page-info {
  font-size: 0.85rem;
  color: var(--text-secondary);
}

/* SQL Console (Settings > SQL Console, public/app.js's loadDevSqlConsoleView)
   — a 3-pane IDE layout (file browser / editor / results, sqliteonline.com-
   style) that fills the remaining viewport instead of sitting inside the
   normal padded .content wrapper every other screen uses. */
.sql-console-shell {
  height: calc(100vh - var(--top-bars-height));
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.sql-console-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border-color);
  flex-wrap: wrap;
  flex-shrink: 0;
}

.sql-console-body {
  flex: 1;
  display: flex;
  min-height: 0;
}

.sql-console-filebrowser {
  width: 240px;
  flex-shrink: 0;
  border-right: 1px solid var(--border-color);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background-color: var(--surface-container);
}

.sql-console-filebrowser-header {
  padding: 10px 12px;
  font-weight: 600;
  font-size: 0.82rem;
  border-bottom: 1px solid var(--border-color);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}

.sql-console-filebrowser-list {
  flex: 1;
  overflow-y: auto;
}

.sql-console-file-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 7px 12px;
  font-size: 0.82rem;
  cursor: pointer;
  gap: 6px;
}

.sql-console-file-item:hover {
  background-color: var(--surface-container-high);
}

.sql-console-file-item.active {
  background-color: var(--primary-container);
}

.sql-console-file-item .filename {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}

.sql-console-file-item .delete-btn {
  opacity: 0;
  border: none;
  background: none;
  color: var(--error-color);
  cursor: pointer;
  padding: 2px 4px;
}

.sql-console-file-item:hover .delete-btn {
  opacity: 1;
}

.sql-console-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.sql-console-editor-pane {
  flex: 0 0 45%;
  min-height: 120px;
  border-bottom: 1px solid var(--border-color);
  overflow: hidden;
}

.sql-console-editor-pane .CodeMirror {
  height: 100%;
  font-size: 0.9rem;
}

.sql-console-results-pane {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.sql-console-result-tabs {
  display: flex;
  gap: 4px;
  padding: 6px 12px 0;
  border-bottom: 1px solid var(--border-color);
  overflow-x: auto;
  flex-shrink: 0;
  background-color: var(--surface-container);
}

.sql-console-result-tab {
  padding: 6px 14px;
  font-size: 0.8rem;
  cursor: pointer;
  border: 1px solid var(--border-color);
  border-bottom: none;
  border-radius: 4px 4px 0 0;
  background: var(--surface-container-high);
  white-space: nowrap;
  color: var(--text-secondary);
}

.sql-console-result-tab.active {
  background: var(--surface-color);
  font-weight: 600;
  color: var(--text-primary);
}

.sql-console-result-body {
  flex: 1;
  overflow: auto;
  padding: 10px 12px;
  background-color: var(--surface-color);
}

.sql-console-result-search {
  margin-bottom: 8px;
  max-width: 320px;
}