/*
 * UVIT Custom Edition — Apple Business Manager-style Theme
 * Font: Inter | Reference: business.apple.com
 *
 * Layout is handled by custom templ templates (shell.layout.templ).
 * This CSS only styles the custom layout classes + component refinements.
 * No !important hacks — we own the HTML structure.
 */

/* ===== Theme Color Tokens — :root fallback =====
 * Tailwind utilities bg-background / bg-muted / text-muted-foreground
 * reference these CSS vars. The .theme-uvit block below overrides
 * them (same values) when the theme class is present (main app shell).
 * :root serves standalone pages (error page, register) that don't run
 * ThemeInitScript and therefore don't get .theme-uvit on <html>.
 */
:root {
  color-scheme: light;            /* native <select> lists, scrollbars, etc. render light */
  --background: 210 20% 98%;      /* gray-50 #f9fafb */
  --foreground: 221 39% 11%;      /* gray-900 #111827 */
  --card: 0 0% 100%;              /* white */
  --card-foreground: 221 39% 11%; /* gray-900 */
  --popover: 0 0% 100%;           /* white */
  --popover-foreground: 221 39% 11%; /* gray-900 */
  --muted: 220 15% 96%;           /* gray-100 #f3f4f6 */
  --muted-foreground: 220 9% 46%; /* gray-500 #6b7280 */
  /* Primary ramp fallback — keeps login/error pages blue when .theme-uvit
     is not present. Space-separated HSL triplets (no hsl() wrapper) because
     tailwind.config.js wraps them: hsl(var(--color-primary-NNN) / opacity).
     Overridden by GenerateBrandingCSS when a tenant PrimaryColor is set. */
  --primary: 224 76% 48%;
  --color-primary-50:  213 100% 97%;
  --color-primary-100: 214 95% 93%;
  --color-primary-200: 213 97% 87%;
  --color-primary-300: 212 96% 78%;
  --color-primary-400: 213 94% 68%;
  --color-primary-500: 217 91% 60%;
  --color-primary-600: 221 83% 53%;
  --color-primary-700: 224 76% 48%;
  --color-primary-800: 226 71% 40%;
  --color-primary-900: 225 64% 33%;
  --color-primary-950: 226 57% 21%;
}
.dark {
  color-scheme: dark;             /* native <select> lists, scrollbars render dark */
  --background: 221 39% 11%;      /* gray-900 #111827 */
  --foreground: 0 0% 100%;        /* white */
  --card: 215 28% 17%;            /* gray-800 #1f2937 */
  --card-foreground: 0 0% 100%;   /* white */
  --popover: 215 28% 17%;         /* gray-800 */
  --popover-foreground: 0 0% 100%; /* white */
  --muted: 215 28% 17%;           /* gray-800 #1f2937 */
  --muted-foreground: 218 11% 65%; /* gray-400 #9ca3af */
}

/* ===== Theme Color Tokens (Light) ===== */
.theme-uvit {
  --background: 210 20% 98%;           /* gray-50 #f9fafb */
  --foreground: 221 39% 11%;           /* gray-900 #111827 */
  --card: 0 0% 100%;                   /* white */
  --card-foreground: 221 39% 11%;      /* gray-900 */
  --popover: 0 0% 100%;               /* white */
  --popover-foreground: 221 39% 11%;  /* gray-900 */
  /* Default blue primary ramp — same triplet format as :root.
     GenerateBrandingCSS overrides these when a tenant colour is set. */
  --primary: 224 76% 48%;             /* blue-700 #1d4ed8 */
  --color-primary-50:  213 100% 97%;
  --color-primary-100: 214 95% 93%;
  --color-primary-200: 213 97% 87%;
  --color-primary-300: 212 96% 78%;
  --color-primary-400: 213 94% 68%;
  --color-primary-500: 217 91% 60%;
  --color-primary-600: 221 83% 53%;
  --color-primary-700: 224 76% 48%;
  --color-primary-800: 226 71% 40%;
  --color-primary-900: 225 64% 33%;
  --color-primary-950: 226 57% 21%;
  --primary-foreground: 0 0% 100%;    /* white */
  --secondary: 220 15% 96%;           /* gray-100 #f3f4f6 */
  --secondary-foreground: 221 39% 11%; /* gray-900 */
  --muted: 220 15% 96%;               /* gray-100 */
  --muted-foreground: 220 9% 46%;     /* gray-500 #6b7280 */
  --accent: 220 15% 96%;              /* gray-100 */
  --accent-foreground: 221 39% 11%;   /* gray-900 */
  --destructive: 0 84.2% 60.2%;
  --destructive-foreground: 0 0% 98%;
  --border: 220 13% 91%;              /* gray-200 #e5e7eb */
  --input: 220 13% 91%;               /* gray-200 */
  --ring: 224 76% 48%;                /* blue-700 */
}

/* ===== Theme Color Tokens (Dark) ===== */
.dark.theme-uvit {
  --background: 221 39% 11%;          /* gray-900 #111827 */
  --foreground: 0 0% 100%;            /* white */
  --card: 215 28% 17%;                /* gray-800 #1f2937 */
  --card-foreground: 0 0% 100%;       /* white */
  --popover: 215 28% 17%;             /* gray-800 */
  --popover-foreground: 0 0% 100%;    /* white */
  --primary: 217 91% 60%;             /* blue-500 #3b82f6 */
  --primary-foreground: 0 0% 100%;    /* white */
  --secondary: 217 19% 27%;           /* gray-700 #374151 */
  --secondary-foreground: 0 0% 100%;  /* white */
  --muted: 215 28% 17%;               /* gray-800 */
  --muted-foreground: 218 11% 65%;    /* gray-400 #9ca3af */
  --accent: 217 19% 27%;              /* gray-700 */
  --accent-foreground: 0 0% 100%;     /* white */
  --destructive: 0 62.8% 30.6%;
  --destructive-foreground: 0 0% 98%;
  --border: 217 19% 27%;              /* gray-700 #374151 */
  --input: 217 19% 27%;               /* gray-700 */
  --ring: 217 91% 60%;                /* blue-500 */
}

/* ===== Typography ===== */
.custom {
  font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow: hidden; /* scrolling happens inside .custom-main */
}

.custom,
.custom body {
  line-height: 1.6;
  background: hsl(var(--background));
  /* Base text color so classless content text (plain <p>/<td>/interpolated
     text without a Tailwind text-* class) follows the theme instead of
     falling back to the UA default (black) — fixes unreadable text in dark. */
  color: hsl(var(--foreground));
}

.custom h1, .custom h2, .custom h3, .custom h4, .custom h5 {
  font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* ===== Shell Layout ===== */
.custom {
  --header-height: 4rem;
}

.custom .custom-shell {
  display: flex;
  min-height: 100vh;
  width: 100%;
  background: hsl(var(--background));
}

/* ===== Sidebar ===== */
.custom .custom-sidebar {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  width: 16rem;
  display: flex;
  flex-direction: column;
  background: hsl(var(--card));
  border-right: 1px solid hsl(var(--border));
  z-index: 40;
  overflow-y: auto;
}

.custom .custom-sidebar-logo {
  position: relative; /* contains the absolutely-positioned collapse button */
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0 1rem;
  border-bottom: 1px solid hsl(var(--border));
  flex-shrink: 0;
  height: var(--header-height);
  box-sizing: border-box;
}

.custom .custom-sidebar-logo-img {
  width: 1.5rem;
  height: 1.5rem;
  object-fit: contain;
  flex-shrink: 0;
}

.custom .custom-sidebar-logo-name {
  font-weight: 700;
  font-size: 1.375rem;
  color: hsl(var(--foreground));
  white-space: nowrap;
}

.custom .custom-sidebar-nav {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 0.5rem 0.625rem;
}

.custom .custom-sidebar-group {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

/* Bottom icon row takes the auto-margin so it stays pinned to the bottom. */

.custom .custom-sidebar-separator {
  height: 1px;
  background: hsl(var(--border));
  margin: 0.5rem -0.625rem;
}

/* ===== Sidebar collapsible submenus ===== */

/* Remove the browser-default disclosure triangle on <summary> */
.custom .custom-sidebar details > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
}
.custom .custom-sidebar details > summary::-webkit-details-marker {
  display: none;
}

/* Chevron icon: transition + rotate 180° when details is open */
.custom .custom-sidebar .custom-sidebar-chevron {
  flex-shrink: 0;
  transition: transform 0.2s ease;
}
.custom .custom-sidebar details[open] .custom-sidebar-chevron {
  transform: rotate(180deg);
}

/* Chevron toggle button — resets button chrome so it inherits the
   summary's text colour and doesn't add visual noise. The button
   captures the click (stopPropagation) so it can toggle details.open
   without the summary's hx-get navigation firing. */
.custom .custom-sidebar .custom-sidebar-chevron-btn {
  background: none;
  border: none;
  padding: 0.25rem;
  margin: -0.25rem;
  cursor: pointer;
  color: inherit;
  display: flex;
  align-items: center;
  border-radius: 0.25rem;
  flex-shrink: 0;
  line-height: 1;
}

/* Sub-item list: no default list chrome, tight vertical spacing */
.custom .custom-sidebar details > ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

/* Sub-item links: indent past the parent's icon column so they nest visually.
   The `.custom-sidebar-link` rule uses the `padding` shorthand (0.5rem 0.75rem)
   which overrides Tailwind's `pl-*` utilities (custom.css loads after main.css,
   same specificity = last rule wins).  This higher-specificity selector wins
   cleanly without !important. */
.custom .custom-sidebar details > ul .custom-sidebar-link {
  padding-left: 2.25rem; /* ≈ pl-9; positions child icon under parent text */
}

/* ===== Sidebar bottom icon-button row ===== */
.custom .custom-sidebar-bottom-row {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  padding: 0.5rem 0.625rem;
}

/* ===== Sidebar collapse toggle button ===== */

/* Desktop button: absolutely positioned at the right of the logo row. */
.custom .custom-sidebar-collapse-btn {
  position: absolute;
  right: 0.5rem;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  padding: 0.25rem;
  cursor: pointer;
  color: hsl(var(--muted-foreground));
  border-radius: 0.375rem;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  line-height: 1;
}

.custom .custom-sidebar-collapse-btn:hover {
  background: hsl(var(--accent));
  color: hsl(var(--foreground));
}

/* Collapse icon: smooth rotation to signal expand vs collapse direction */
.custom .custom-sidebar-collapse-icon {
  flex-shrink: 0;
  transition: transform 0.2s ease;
}

/* When collapsed, chevrons rotate 180° to point right (= "expand") */
.custom.sidebar-collapsed .custom-sidebar-collapse-icon {
  transform: rotate(180deg);
}

/* Hidden on mobile — the off-canvas drawer already has its own toggle */
@media (max-width: 1023px) {
  .custom .custom-sidebar-collapse-btn {
    display: none;
  }
}

/* ===== Collapsed sidebar (desktop lg+) ===== */
@media (min-width: 1024px) {
  /* Asymmetric transition: expand animates, collapse is instant. CSS reads
     the transition from the TARGET state — the default (expanded) rules carry
     the 0.2s ease (applied when removing .sidebar-collapsed → expanding), the
     collapsed rules below override to 0s (applied when adding the class →
     collapsing). Instant collapse also avoids the ApexCharts end-of-animation
     redraw snap on that direction. */
  .custom .custom-sidebar {
    transition: width 0.2s ease;
  }
  .custom .custom-main {
    transition: margin-left 0.2s ease;
  }

  /* Collapsed state: icon-only rail — snaps in instantly (transition 0s). */
  .custom.sidebar-collapsed .custom-sidebar {
    width: 4rem; /* STANDARD-SIZES: icon rail = w-16 */
    transition: width 0s;
  }
  .custom.sidebar-collapsed .custom-main {
    margin-left: 4rem;
    transition: margin-left 0s;
  }

  /* Logo row in collapsed mode: the button goes back into normal flow
     and is centered; image and name are hidden. */
  .custom.sidebar-collapsed .custom-sidebar .custom-sidebar-logo {
    justify-content: center;
    padding: 0;
    gap: 0;
  }
  .custom.sidebar-collapsed .custom-sidebar .custom-sidebar-logo-img,
  .custom.sidebar-collapsed .custom-sidebar .custom-sidebar-logo-name {
    display: none;
  }
  .custom.sidebar-collapsed .custom-sidebar .custom-sidebar-collapse-btn {
    position: static; /* back in flow so flex centers it */
    transform: none;
  }

  /* Nav items: hide text labels, chevron button, sub-items */
  .custom.sidebar-collapsed .custom-sidebar .custom-sidebar-link > span,
  .custom.sidebar-collapsed .custom-sidebar .custom-sidebar-chevron-btn,
  .custom.sidebar-collapsed .custom-sidebar details > ul {
    display: none;
  }

  /* Center the remaining icon in each nav link */
  .custom.sidebar-collapsed .custom-sidebar .custom-sidebar-link {
    justify-content: center;
    padding: 0.5rem;
    gap: 0;
  }

  /* Bottom row: center the icon buttons */
  .custom.sidebar-collapsed .custom-sidebar .custom-sidebar-bottom-row {
    justify-content: center;
    flex-wrap: wrap;
    gap: 0.25rem;
  }
}

.custom .custom-sidebar-link {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0.75rem;
  border-radius: 0.5rem;
  font-size: 1rem;   /* text-base */
  font-weight: 400;  /* font-normal */
  color: hsl(var(--foreground));
  text-decoration: none;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.custom .custom-sidebar-link:hover {
  background: hsl(var(--accent));
  color: hsl(var(--foreground));
  text-decoration: none;
}

.custom .custom-sidebar-link.active {
  background: hsl(var(--accent));  /* gray-100 / gray-700 — no bold color fill */
  color: hsl(var(--foreground));
  font-weight: 600;
}

/* ===== Main Content ===== */
.custom .custom-main {
  flex: 1;
  margin-left: 16rem;
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow-y: auto;
  /* Always reserve the scrollbar gutter so switching between a tall tab
     (scrollbar present) and a short one (no scrollbar) doesn't shift the
     content sideways by the scrollbar width — the per-tab "jump". */
  scrollbar-gutter: stable;
}

/* Header/breadcrumb bar — sticky, exact same height as sidebar logo */
.custom .custom-main > header,
.custom .custom-main header.sticky {
  background: hsl(var(--card));
  border-bottom: 1px solid hsl(var(--border));
  padding: 0 1.5rem;
  height: var(--header-height);
  min-height: var(--header-height);
  box-sizing: border-box;
  position: sticky;
  top: 0;
  z-index: 20;
  flex-shrink: 0;
  overflow: visible;
}

/* Header right-side controls — never wrap, shrink gracefully */
.custom .custom-main header .flex.items-center.gap-4 {
  flex-wrap: nowrap;
  gap: 0.5rem;
}

/* Header itself: never wrap, clip horizontal but allow dropdowns to overflow down */
.custom .custom-main > header,
.custom .custom-main header.sticky {
  flex-wrap: nowrap;
}

/* Org/site labels shrink */
.custom .custom-main header .flex.items-center.gap-2 {
  flex-shrink: 1;
  min-width: 0;
  white-space: nowrap;
}

/* Breadcrumbs shrink too.
   display:flex + align-items:center ensures the <ol> (inline-flex) is
   centered in the nav's cross axis instead of sitting on the text baseline
   of the parent's inline formatting context (which line-height:1.6 shifts). */
.custom .custom-main header nav#header {
  overflow: hidden;
  flex-shrink: 1;
  min-width: 0;
  display: flex;
  align-items: center;
}

/* Progressive hiding on narrow screens */

/* 2. Hide theme switch */
@media (max-width: 950px) {
  .custom .custom-main header a:has(#theme-switch) {
    display: none;
  }
}

/* 3. Hide warning icon */
@media (max-width: 850px) {
  .custom .custom-main header .flex.items-center.gap-4 > div:has([icon="triangle-alert"]) {
    display: none;
  }
}

/* 4. Hide breadcrumbs */
@media (max-width: 750px) {
  .custom .custom-main header nav#header {
    display: none;
  }
}

/* Content area — layout constraints and gap normalization.
   Padding is controlled per page-type via the modifier classes below so
   each page can opt into the right amount without needing !important. */
.custom .custom-main > main,
.custom .custom-main > main.grid {
  gap: 0.75rem;
  overflow-x: hidden;
  min-width: 0;
}

/* Standard pages (admin, settings, detail, forms): 1rem vertical,
   1.5rem horizontal. Neither modifier class is present on these pages,
   so the :not() selector matches and supplies the default padding. */
.custom .custom-main > main:not(.custom-list-main):not(.custom-dashboard-main) {
  padding: 1rem 1.5rem;
}

/* Dashboard: moderate padding (1rem all around, matching the Flowbite
   SaaS homepage demo content area density). */
.custom .custom-main > main.custom-dashboard-main {
  padding: 1rem;
}

/* List pages: the table is full-bleed to the page edge (rows + dividers reach
   the edge). Only the toolbar, filter chips and footer are inset — aligned
   with the table cells' 1rem padding (Flowbite e-commerce/products layout). */
.custom .custom-main > main.custom-list-main {
  padding: 0;
}
.custom .custom-list-main .custom-devices-card > :not(.custom-card-body) {
  padding-left: 1rem;
  padding-right: 1rem;
}

/* Prevent horizontal overflow on content */
.custom .custom-main {
  overflow-x: hidden;
  min-width: 0;
}

/* Full-width content — flatten the top-level page card.
   width: 100% defeats any Tailwind width utility (e.g. md:w-1/2) left on
   the outer card. In a column-flex <main> the cross axis is horizontal, so
   flex: 1 (which controls height / main axis) does NOT override width. */
.custom .custom-main > main > .custom-card.custom-card-default {
  border: none;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
  width: 100%;
}

.custom .custom-main > main > .custom-card.custom-card-default > .custom-card-body {
  padding: 0;
}

/* ===== Mobile ===== */
.custom .custom-mobile-bar {
  display: none;
}

.custom .custom-backdrop {
  display: none;
}

@media (max-width: 1023px) {
  .custom .custom-sidebar {
    transform: translateX(-100%);
    transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  }

  .custom .custom-sidebar.open {
    transform: translateX(0);
  }

  .custom .custom-main {
    margin-left: 0;
    padding-top: 3rem;
    height: 100vh;
  }

  /* Hide header completely on mobile/tablet — controls moved to mobile bar via JS */
  .custom .custom-main > header,
  .custom .custom-main header.sticky {
    display: none !important;
  }

  .custom .custom-mobile-bar {
    display: flex;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 3rem;
    padding: 0 0.75rem;
    background: hsl(var(--card));
    border-bottom: 1px solid hsl(var(--border));
    align-items: center;
    gap: 0.5rem;
    z-index: 30;
  }

  .custom .custom-mobile-btn {
    background: none;
    border: none;
    cursor: pointer;
    padding: 0.25rem;
    color: hsl(var(--foreground));
    display: flex;
    align-items: center;
  }

  .custom .custom-mobile-title {
    font-weight: 600;
    font-size: 0.875rem;
    color: hsl(var(--foreground));
    flex: 1;
  }

  /* Controls moved into mobile bar */
  .custom .custom-mobile-controls {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }

  .custom .custom-backdrop.active {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.3);
    z-index: 35;
    backdrop-filter: blur(2px);
  }

  .custom .custom-main > main {
    padding: 1rem;
  }

  /* Hide login side image on mobile, form takes full width */
  .custom .w-1\/2.print\:hidden {
    display: none;
  }

  .custom .w-1\/2:not(.print\:hidden) {
    width: 100%;
  }
}

/* Hide the card-header row on page cards entirely. The breadcrumb already
   says where you are; page-level actions (Add, Invite, Import, CSV/PDF)
   render inside the card-body toolbar instead so they sit next to the
   data they act on. !important wins against any historical "nested card
   header" rule that re-enables display:flex via deeper selectors. */
.custom .custom-main .custom-card-header {
  display: none !important;
}

/* Restore card-headers inside custom-detail-stack (configuration detail,
   device detail standalone cards). These stacks contain section cards
   whose headers carry the section title + inline Edit button. The rule
   above hides them globally; this override wins via higher specificity
   (4 classes vs 3). The device-detail side-menu variant has its own
   even-more-specific restoration rule below. */
.custom .custom-main .custom-detail-stack .custom-card-header {
  display: block !important;
}

/* ===== Search/Filter Row — restyle ===== */

/* Make the filter row a proper toolbar */
.custom .custom-main .custom-card-body > .flex.justify-between {
  margin-top: 0;
  align-items: center;
}

/* Search field — wider, integrated icon */
.custom .custom-search.custom-search-default {
  width: 100%;
  max-width: 20rem;
}

.custom .custom-search-input {
  border-radius: 9999px;
  height: 2.25rem;
  padding-left: 2.25rem;
  padding-right: 0.875rem;
  font-size: 0.8125rem;
  border: 1px solid hsl(var(--border));
  background: hsl(var(--card));
}

.custom .custom-search-input:focus {
  border-color: hsl(var(--ring));
  box-shadow: 0 0 0 3px hsl(var(--ring) / 0.12);
}


/* Export icons moved to toolbar by JS */
.custom .custom-export-icons {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

/* Toolbar right group: export + refresh together */
.custom .custom-toolbar-right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* ===== Table Card Wrapper (added by JS) ===== */
/* Info cards (overview, hardware) — not flattened by side-menu rules */
.custom .custom-info-card {
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  background: hsl(var(--card));
  padding: 1rem;
}

.custom .custom-table-card {
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  background: hsl(var(--card));
  padding: 0;
  overflow: hidden;
}

.custom .custom-table-card .custom-table {
  margin: 0;
}

.custom .custom-table-card nav {
  padding: 0.5rem 0.75rem;
  border-top: 1px solid hsl(var(--border));
}

/* ===== Cards ===== */
.custom .custom-card {
  border-radius: 0.5rem;
  border: 1px solid hsl(var(--border));
  box-shadow: none;
  background: hsl(var(--card));
}

.custom .custom-card-header {
  border-bottom: 1px solid hsl(var(--border));
  padding: 0.5rem 1.25rem;
}

.custom .custom-card-body {
  padding: 0.875rem 1.25rem;
}

.custom .custom-card-title {
  font-weight: 600;
  font-size: 1.25rem; /* text-xl */
}

/* ===== Inner sub-section cards — hide title, content in box ===== */

/* The inner card wrapper: transparent */
.custom .custom-card .custom-card.custom-card-default {
  border: none;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

/* Hide the sub-section title (side menu shows the active section) */
.custom .custom-detail-content .custom-card-header {
  display: none !important;
}

/* Content body: in a card box */
.custom .custom-detail-content .custom-card-body,
.custom .custom-detail-content .custom-card.custom-card-body,
.custom .custom-detail-content > .custom-card {
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  background: hsl(var(--card));
  padding: 0.875rem 1.25rem;
}

/* For pages without detail layout, keep sub-section headers visible */
.custom .custom-card .custom-card .custom-card-header {
  display: flex;
  border: none;
  padding: 0.5rem 0 0.25rem;
  background: transparent;
}

.custom .custom-card .custom-card .custom-card-body {
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  background: hsl(var(--card));
  padding: 0.875rem 1.25rem;
}

/* Sub-section info icon + tooltip (inserted by JS) */
.custom .custom-sub-info {
  position: relative;
  display: inline-flex;
  align-items: center;
  margin-left: 0.375rem;
}

.custom .custom-sub-info-tooltip {
  display: none;
  position: absolute;
  top: calc(100% + 0.5rem);
  left: -1rem;
  z-index: 50;
  background: hsl(var(--card));
  color: hsl(var(--muted-foreground));
  font-size: 0.8125rem;
  line-height: 1.5;
  padding: 0.625rem 0.875rem;
  border-radius: 0.5rem;
  border: 1px solid hsl(var(--border));
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  max-width: 24rem;
  width: max-content;
  font-weight: 400;
}

.custom .custom-sub-info:hover .custom-sub-info-tooltip {
  display: block;
}

/* ===== Report Icons ===== */
.custom .ri-3x {
  font-size: 1.25rem;
}

/* ===== Full-Height Scrollable Tables (infinite scroll) ===== */

/* When a table is present, lock the layout so only the table scrolls */
.custom .custom-main:has(.custom-table-scroll) {
  overflow-y: hidden;
}

.custom .custom-main:has(.custom-table-scroll) > main,
.custom .custom-main:has(.custom-table-scroll) > main.grid {
  flex: 1;
  overflow: hidden;
  min-height: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* The outer card wrapper and card-body must stretch to fill.
   min-width:0 lets descendant flex items shrink below their content's intrinsic
   width so .custom-table-scroll can scroll horizontally instead of pushing the
   table off-screen. */
.custom .custom-main:has(.custom-table-scroll) .custom-card {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  width: 100%; /* belt-and-suspenders: flex:1 controls height in column-flex, not width */
}

.custom .custom-main:has(.custom-table-scroll) .custom-card > .custom-card-header {
  flex-shrink: 0;
}

.custom .custom-main:has(.custom-table-scroll) .custom-card > .custom-card-body,
.custom .custom-main:has(.custom-table-scroll) .custom-card-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}

/* Toolbar and forms above the table must not shrink */
.custom .custom-main:has(.custom-table-scroll) .custom-card-body > .flex.justify-between,
.custom .custom-main:has(.custom-table-scroll) .custom-card-body > form {
  flex-shrink: 0;
}

/* Side-menu admin pages (config nav grid) + infinite-scroll table: the
   `:has(> .custom-detail-menu)` rule sets `display: grid !important;
   align-items: start` to keep the content card at its natural height —
   which is right for static pages but breaks the flex cascade that
   `:has(.custom-table-scroll)` needs to bound the scroll container to
   the viewport. When both coexist, stretch the grid cells so the cascade
   propagates through, and keep the nav itself anchored to the top. */
.custom .custom-main:has(.custom-table-scroll) .custom-card-body:has(> .custom-detail-menu) {
  align-content: stretch;
  align-items: stretch;
  height: 100%;
}
.custom .custom-main:has(.custom-table-scroll) .custom-card-body:has(> .custom-detail-menu) > .custom-detail-menu {
  align-self: start;
}
/* `:not(:first-child)` — the first child is the device header
   (ComputerHeader). It must keep its own `flex-direction: row` so the
   name / tags / actions stay on one line; only the content column
   below it needs the flex-column height cascade. Without the exclusion
   the header was forced to column and its content stacked + centred. */
.custom .custom-main:has(.custom-table-scroll) .custom-card-body:has(> .custom-detail-menu) > :not(.custom-detail-menu):not(:first-child) {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Device-detail tabs (horizontal tab bar + .custom-detail-stack) holding a
   primary scroll table (e.g. the Inventory tab): extend the bound-to-viewport
   cascade THROUGH the tab chrome so the table scrolls to the remaining height
   instead of growing the page — same UX as the list pages. Scoped to
   detail-only hooks (#computer-header, #computer-actions-bar, the tablist,
   .custom-detail-stack), which list pages don't have, so they're unaffected. */
.custom .custom-main:has(.custom-table-scroll) .custom-card-body > #computer-header,
.custom .custom-main:has(.custom-table-scroll) .custom-card-body > #computer-actions-bar,
.custom .custom-main:has(.custom-table-scroll) .custom-card-body > ul[role="tablist"] {
  flex-shrink: 0;
}
.custom .custom-main:has(.custom-table-scroll) .custom-detail-stack {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}
/* Only the card that holds the scroll table grows; title / header-only cards
   in the stack keep their natural height. */
.custom .custom-main:has(.custom-table-scroll) .custom-detail-stack > .custom-card:not(:has(.custom-table-scroll)) {
  flex: 0 0 auto;
}
/* Inside the detail stack, flex (not the list-tuned max-height, which is too
   tall this deep in the DOM) controls the table-scroll height — the bounded
   parents above already give it the correct available space. */
.custom .custom-main:has(.custom-table-scroll) .custom-detail-stack .custom-table-scroll {
  flex: 1;
  max-height: none;
}

/* Settings menu itself: scroll internally if the available column height
   is shorter than the nav (10 items × ~30px ≈ 300px). Otherwise on short
   viewports it just overflows out of the card. */
.custom .custom-detail-menu {
  overflow-y: auto;
  min-height: 0;
}

/* Short viewports: the side-menu (~300px) plus chrome eats the entire
   available height. The locked-table-scroll pattern clips content under
   the viewport with no way to reach it. Drop the entire lock at short
   heights — page scrolls normally. Infinite scroll still works because
   the sentinel becomes fully visible inside its (now unbounded)
   container, so the IntersectionObserver fires and auto-cascades. We
   have to undo every layer the lock cascades through, otherwise the
   inner <main>/custom-card/custom-card-body keep their `overflow: hidden` and
   the content gets clipped before it can reach custom-main's scroll. */
@media (max-height: 700px) {
  .custom .custom-main:has(.custom-table-scroll):has(.custom-detail-menu) {
    overflow-y: auto;
  }
  .custom .custom-main:has(.custom-table-scroll):has(.custom-detail-menu) > main,
  .custom .custom-main:has(.custom-table-scroll):has(.custom-detail-menu) > main.grid,
  .custom .custom-main:has(.custom-table-scroll):has(.custom-detail-menu) .custom-card,
  .custom .custom-main:has(.custom-table-scroll):has(.custom-detail-menu) .custom-card > .custom-card-body,
  .custom .custom-main:has(.custom-table-scroll):has(.custom-detail-menu) .custom-card-body {
    overflow: visible;
    min-height: 0;
  }
  .custom .custom-main:has(.custom-table-scroll):has(.custom-detail-menu) .custom-table-scroll {
    max-height: none;
    overflow: visible;
  }
}

/* The table wrapper created by JS for scroll containment.
   overflow:auto on both axes so wide tables get a horizontal scrollbar inside
   the card instead of being clipped by <main>'s overflow-x:hidden. width/min-width
   force the wrapper to its parent's width so the table's intrinsic width creates
   overflow rather than pushing the wrapper wider than the viewport. */
.custom .custom-table-scroll {
  overflow: auto;
  /* Sizing: content-natural up to the available viewport. min-height:0
     lets flex-shrink kick in when content overflows the parent's bounded
     height (so the table scrolls internally instead of pushing the page
     past the viewport). NO flex: 1 — we don't want to grow past content
     when the table is short. max-height is a hard cap with 2rem of
     breathing room before the viewport edge. */
  min-height: 0;
  max-height: calc(100vh - var(--header-height) - 2rem);
  margin-bottom: 1rem;
  width: 100%;
  min-width: 0;
  position: relative;
}

/* Sticky table header — background must match the Flowbite thead scheme:
   gray-100 in light, gray-700 in dark.  --muted = gray-100 (light) /
   gray-800 (dark); the dark override below corrects dark to gray-700. */
.custom .custom-table-scroll thead {
  position: sticky;
  top: 0;
  z-index: 10;
  background: hsl(var(--muted));
}

.custom .custom-table-scroll thead th {
  background: hsl(var(--muted));
}

/* Dark mode: thead is gray-700 (one step lighter than the gray-800 surface)
   so rows scrolling underneath are visually masked. --accent = gray-700
   in .dark.theme-uvit. The selector wins (.dark.custom has 3 classes)
   vs the base rule above (2 classes). */
.dark.custom .custom-table-scroll thead,
.dark.custom .custom-table-scroll thead th {
  background: hsl(var(--accent));
}

/* The custom theme replaces classic pagination with infinite scroll
   (initInfiniteScroll in custom.js wraps every list table and adds
   `.custom-table-scroll`). Keying the hide off `.custom-table-scroll`
   meant the server-rendered pagination nav — "Items per page", page
   links — flashed visible between the HTMX swap and JS running. Hide
   it from the start; initInfiniteScroll still reads its textContent
   for the "N total" count regardless of display. */
.custom .custom-main nav[aria-label="Pagination"] {
  display: none !important;
}

/* Loading spinner at bottom of table */
.custom .custom-infinite-loading td {
  border: none !important;
}

.custom .custom-infinite-spinner {
  display: inline-block;
  width: 1.25rem;
  height: 1.25rem;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: custom-spin 0.6s linear infinite;
}

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

/* ===== Devices list: OS side-menu beside the table ===== */
/* The Devices list puts the OS quick-filter side-menu (.custom-detail-menu) in
   an explicit 2-column row next to the table, instead of triggering the
   device-detail `.custom-card-body:has(> .custom-detail-menu)` grid. That grid
   pulls the search toolbar into a column and makes custom.js treat the table
   as a settings side-menu table (no card wrapper / no infinite scroll). These
   two declarations only re-supply `min-height: 0` (Tailwind's min-h-0 is
   purged from the build) so the existing custom-table-scroll height cascade
   threads through the layout/list wrappers. The detail-page :has rules are
   untouched. */
.custom .custom-main:has(.custom-table-scroll) .custom-devices-layout,
.custom .custom-main:has(.custom-table-scroll) .custom-devices-list {
  min-height: 0;
}

/* Narrow screens: stack the side-menu above the table as a horizontal strip,
   mirroring the device-detail nav's mobile collapse, so the 160px menu doesn't
   crowd the table. */
@media (max-width: 900px) {
  .custom .custom-devices-layout {
    flex-direction: column;
  }
  .custom .custom-devices-layout > .custom-detail-menu {
    width: 100%;
    flex-direction: row;
    flex-wrap: wrap;
  }
}

/* ===== Devices CRUD list container ===== */
/* Layout host for the redesigned Devices list (OS tabs + toolbar + chips +
   table + footer). NO visual card here — toolbar + table render flat on the
   page background (Flowbite users/list style). The thead background and row
   dividers supply the structure. This element keeps only its layout role:
   full-width flex-column host for the infinite-scroll height cascade +
   sticky header. */
.custom .custom-devices-card {
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* Flowbite CRUD table surface: white in light, gray-800 in dark, on the
     gray-50/gray-900 page. Flat and edge-to-edge like the reference — its
     table wrapper is `overflow-hidden shadow`, no border, no radius (the
     rounded-lg cards on that page are its modals). */
  background: hsl(var(--card));
  box-shadow: 0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1);
}

/* Keep the inner table flat too — no card border/bg around it. */
.custom .custom-devices-card .custom-table-card {
  border: none;
  border-radius: 0;
  background: transparent;
}

/* Override the general .custom-card-body padding so the table fills edge-to-
   edge inside the devices card (the border-t on the element itself provides
   the top separator instead of padding). */
.custom .custom-devices-card .custom-card-body {
  padding: 0;
}

/* Height cascade: make the devices card stretch to fill the card slot so
   the table's custom-table-scroll container gets a bounded height. */
.custom .custom-main:has(.custom-table-scroll) .custom-devices-card {
  flex: 1;
  min-height: 0;
  min-width: 0;
}

/* ===== Tables ===== */
.custom .custom-table {
  border-collapse: separate;
  border-spacing: 0;
  /* Fill the card / scroll-container width.  Auto table layout (the default
     for all list tables) sizes columns from content and then expands the table
     to fill the available width via this rule.  min-w-full on individual
     tables is kept as a belt-and-suspenders min-width guard. */
  width: 100%;
}

.custom .custom-table thead th {
  color: hsl(var(--muted-foreground));
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0.625rem 0.75rem;
  border-bottom: 1px solid hsl(var(--border));
  background: transparent;
}


.custom .custom-table tbody tr {
  transition: background-color 0.12s ease;
}

.custom .custom-table tbody tr:hover {
  background-color: hsl(var(--accent) / 0.6);
}

/* border-collapse: separate (set on .custom-table) means <tr> borders
   don't render — put the divider on the cells instead so each row
   shows a thin line beneath it. Skip the last row so the table doesn't
   end with a hanging divider. */
.custom .custom-table tbody td {
  padding: 0.375rem 0.75rem;
  vertical-align: middle;
  font-size: 0.8125rem;
  border-bottom: 1px solid hsl(var(--border));
}

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

/* Header icons — compact */
.custom .custom-main header .h-6.w-6 {
  height: 1.25rem;
  width: 1.25rem;
}

/* ===== Agent toolbar: compact, hide disabled action buttons ===== */
.custom #admit-all-button[disabled],
.custom #enable-all-button[disabled],
.custom #disable-all-button[disabled] {
  display: none;
}

.custom #select-all,
.custom #deselect-all {
  font-size: 0.75rem;
  padding: 0.25rem 0.75rem;
}

/* ===== Auto-save: hide save buttons + their loading spinners ===== */
.custom .custom-main button:has([icon="save"]) {
  display: none !important;
}

/* htmx-indicator handles visibility of save-related spinners; no extra rule needed. */

/* ===== Checkboxes ===== */
.custom input[type="checkbox"]:not(.custom-toggle input) {
  appearance: auto;
  -webkit-appearance: checkbox;
  accent-color: hsl(var(--primary));
  width: 1rem;
  height: 1rem;
  cursor: pointer;
}

/* ===== Toggle Switch ===== */
.custom .custom-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  user-select: none;
}

.custom .custom-toggle input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.custom .custom-toggle-slider {
  position: relative;
  display: inline-block;
  width: 2.75rem;
  height: 1.5rem;
  background-color: hsl(var(--border));
  border-radius: 9999px;
  transition: background-color 0.25s ease;
}

.custom .custom-toggle-slider::after {
  content: '';
  position: absolute;
  top: 0.125rem;
  left: 0.125rem;
  width: 1.25rem;
  height: 1.25rem;
  background: white;
  border-radius: 9999px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

.custom .custom-toggle input:checked + .custom-toggle-slider {
  background-color: hsl(var(--primary));
}

.custom .custom-toggle input:checked + .custom-toggle-slider::after {
  transform: translateX(1.25rem);
}

/* ===== Native <select> — Flowbite-consistent style =====
 * Strips the browser double-chevron (appearance: none) and replaces it with
 * a single down-pointing SVG arrow so all selects match Flowbite inputs.
 * Background / border / text / padding come from Tailwind utility classes
 * already on each <select> element — we only add what Tailwind doesn't supply:
 * the custom arrow and the native-outline reset on focus.
 * A dark-mode variant uses a lighter stroke (#9ca3af / gray-400) so the
 * chevron stays readable on dark:bg-gray-700.
 * The color-scheme rules on :root / .dark keep native option-list rendering
 * in the correct light/dark palette — do not remove those.
 */
.custom select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-position: right 0.625rem center;
  background-repeat: no-repeat;
  background-size: 1rem 1rem;
  padding-right: 2.25rem; /* leave room for the chevron; Tailwind p-2.5 covers the other sides */
}

.dark .custom select {
  /* Lighter chevron on dark:bg-gray-700 background */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

.custom select:focus {
  outline: none;
  border-color: hsl(var(--color-primary-500));
  box-shadow: 0 0 0 3px hsl(var(--color-primary-500) / 0.15);
}

/* ===== Tabs (non-detail pages) ===== */

/* Admin nav: side menu rendered server-side, parent becomes grid layout */
.custom .custom-card-body:has(> .custom-detail-menu) {
  display: grid !important;
  grid-template-columns: 10rem 1fr;
  gap: 0.75rem 1.5rem;
  flex-direction: unset;
  align-content: start;
  /* Without this, grid items default to `align-items: stretch` and the
     content card gets stretched to the full height of the row — which
     is as tall as the 10-item nav menu — leaving a huge empty card on
     short pages. `start` keeps each item at its natural height. */
  align-items: start;
}

.custom .custom-card-body:has(> .custom-detail-menu) > .custom-detail-menu {
  grid-column: 1;
  grid-row: 1 / -1;
  padding-top: 0.25rem;
}

.custom .custom-card-body:has(> .custom-detail-menu) > :not(.custom-detail-menu) {
  grid-column: 2;
}


/* Card-headers under the side-menu layout stay hidden too — same rule as
   the outer page cards. */
.custom .custom-card-body:has(> .custom-detail-menu) .custom-card-header {
  display: none !important;
}

/* Flatten ALL inner cards in the side-menu layout — no card-in-card */
.custom .custom-card-body:has(> .custom-detail-menu) .custom-card {
  border: none !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  background: transparent !important;
}

/* The inner card-body becomes the single visible card. Descendant
   selector (not `>`): the content column is wrapped in a flex div so
   the side-menu grid sees one item, so the inner card is no longer a
   direct child of the side-menu card-body. Only inner cards are
   `.custom-card` descendants here — the outer page card is an ancestor. */
.custom .custom-card-body:has(> .custom-detail-menu) .custom-card > .custom-card-body {
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  background: hsl(var(--card));
  padding: 1rem 1.25rem;
}

/* Consolidated device-detail tabs (Overview, Hardware) place several
   section cards in the content column instead of one. Each section is
   a full card with its own header — opt OUT of the single-card
   flatten + hide-header treatment above:
   - the section card itself carries the border / radius / background
   - its card-body is plain (no double border)
   - the card-header (section title) stays visible. */
.custom .custom-card-body:has(> .custom-detail-menu) .custom-detail-stack .custom-card {
  border: 1px solid hsl(var(--border)) !important;
  border-radius: 0.5rem !important;
  background: hsl(var(--card)) !important;
}

.custom .custom-card-body:has(> .custom-detail-menu) .custom-detail-stack .custom-card > .custom-card-body {
  border: none !important;
  border-radius: 0 !important;
  background: transparent !important;
}

.custom .custom-card-body:has(> .custom-detail-menu) .custom-detail-stack .custom-card-header {
  display: block !important;
  /* Breathing room so the section icon + title don't hug the card
     edge. Generous left/right + comfortable top/bottom. */
  padding: 0.875rem 1.25rem !important;
}

.custom .custom-card-body:has(> .custom-detail-menu) .custom-detail-stack .custom-card > .custom-card-body {
  padding: 0.875rem 1.25rem 1.125rem !important;
}

/* Section title: slightly smaller than a page title, with the icon
   sized to match so the header row reads as one unit. */
.custom .custom-detail-stack .custom-card-title {
  font-size: 1rem;
}

/* Inside a section card the inner `.custom-info-card` would draw a
   second border (card-in-card). The section card already frames the
   content — flatten the inner ones to plain field groups. */
.custom .custom-detail-stack .custom-info-card {
  border: none;
  border-radius: 0;
  background: transparent;
  padding: 0;
}

/* Section tables: let them size naturally and never clip. The detail
   tabs are not paginated lists — no internal scroll. */
.custom .custom-detail-stack table.custom-table {
  margin: 0;
}


/* Forms and tables inside settings: use full width */
.custom .custom-card-body:has(> .custom-detail-menu) form {
  width: 100% !important;
}

/* Remove extra top margins on tables inside settings */
.custom .custom-card-body:has(> .custom-detail-menu) .mt-6,
.custom .custom-card-body:has(> .custom-detail-menu) .mt-4 {
  margin-top: 0 !important;
}

/* Software repos: move add button to the right */
.custom .custom-card-body:has(> .custom-detail-menu) .custom-card-body > div:has(> button:first-child) {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.75rem;
}

/* Quick Deploy: scrollable search results */
.custom #deploy-search-results {
  max-height: calc(100vh - var(--header-height) - 7rem);
  overflow-y: auto;
}

.custom #deploy-search-results .custom-table {
  margin: 0;
}

/* ===== Side Menu: software / device tabs ===== */
.custom .custom-detail-menu {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  width: 10rem;
  flex-shrink: 0;
  padding-top: 0.25rem;
}

.custom .custom-detail-menu-item {
  display: block;
  padding: 0.375rem 0.75rem;
  border-radius: 0.375rem;
  font-size: 0.8125rem;
  font-weight: 500;
  color: hsl(var(--muted-foreground));
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.12s, color 0.12s;
}

.custom .custom-detail-menu-item:hover {
  background: hsl(var(--accent));
  color: hsl(var(--foreground));
  text-decoration: none;
}

.custom .custom-detail-menu-item.active {
  background: hsl(var(--primary) / 0.1);
  color: hsl(var(--primary));
  font-weight: 600;
}

/* Mobile: collapse 2-col grid to single column so the nav becomes a
   horizontal strip above the content. */
@media (max-width: 900px) {
  .custom .custom-card-body:has(> .custom-detail-menu) {
    grid-template-columns: 1fr;
  }

  .custom .custom-card-body:has(> .custom-detail-menu) > .custom-detail-menu,
  .custom .custom-card-body:has(> .custom-detail-menu):has(> .flex.justify-between.items-center:first-child) > .custom-detail-menu,
  .custom .custom-card-body:has(> .custom-detail-menu) > :not(.custom-detail-menu) {
    grid-column: 1;
    grid-row: auto !important;
  }

  .custom .custom-detail-menu {
    width: 100%;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 0.25rem;
  }

  .custom .custom-detail-menu-item {
    padding: 0.25rem 0.625rem;
    font-size: 0.75rem;
  }
}

/* ===== Settings secondary menu — Flowbite feed-demo style (w-64 / 16rem) ===== */
/*
 * Decoupled from device-detail / software .custom-detail-menu (10rem).
 * configNavbarSideMenu renders <nav class="settings-side-menu space-y-1">.
 * The :has(> nav.settings-side-menu) selector is deliberately broader than
 * the device-detail rule so it covers all settings page wrappers:
 *   - .custom-card-body (most settings pages)
 *   - plain flex div (tags page uses non-card markup)
 */

/* Two-column grid: 16rem nav + fluid content */
.custom :has(> nav.settings-side-menu) {
    display: grid !important;
    grid-template-columns: 16rem 1fr;   /* STANDARD-SIZES: nav = w-64 */
    gap: 0.75rem 1.5rem;                /* gap-3 row / gap-6 col */
    flex-direction: unset;
    align-content: start;
    align-items: start;
}

/* Nav occupies column 1 for all rows */
.custom :has(> nav.settings-side-menu) > nav.settings-side-menu {
    grid-column: 1;
    grid-row: 1 / -1;
    padding-top: 0.25rem;
    overflow-y: auto;
    min-height: 0;
}

/* All other direct children go into column 2 */
.custom :has(> nav.settings-side-menu) > :not(nav.settings-side-menu) {
    grid-column: 2;
}

/* Inner card flattening — settings content sits in full-width form cards */
.custom :has(> nav.settings-side-menu) .custom-card-header {
    display: none !important;
}

.custom :has(> nav.settings-side-menu) .custom-card {
    border: none !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    background: transparent !important;
}

.custom :has(> nav.settings-side-menu) .custom-card > .custom-card-body {
    border: 1px solid hsl(var(--border));
    border-radius: 0.5rem;
    background: hsl(var(--card));
    padding: 1rem 1.25rem;
}

/* Forms fill the available column width */
.custom :has(> nav.settings-side-menu) form {
    width: 100% !important;
}

/* Remove community-theme stacked top margins */
.custom :has(> nav.settings-side-menu) .mt-6,
.custom :has(> nav.settings-side-menu) .mt-4 {
    margin-top: 0 !important;
}

/* Software repos: align add button to the right */
.custom :has(> nav.settings-side-menu) .custom-card-body > div:has(> button:first-child) {
    display: flex;
    justify-content: flex-end;
    margin-top: 0.75rem;
}

/* Mobile: single column; nav items wrap horizontally above the content */
@media (max-width: 900px) {
    .custom :has(> nav.settings-side-menu) {
        grid-template-columns: 1fr;
    }

    .custom :has(> nav.settings-side-menu) > nav.settings-side-menu,
    .custom :has(> nav.settings-side-menu) > :not(nav.settings-side-menu) {
        grid-column: 1;
        grid-row: auto !important;
    }

    .custom nav.settings-side-menu {
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
        gap: 0.25rem;
    }

    /* Cancel space-y-1 margins when nav is horizontal */
    .custom nav.settings-side-menu > * {
        margin-top: 0 !important;
    }
}

/* ===== Confirm Delete — styled as centered modal overlay ===== */
.custom #confirm:has(#confirm-message) {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: hsl(var(--background) / 0.6);
  backdrop-filter: blur(2px);
}

.custom #confirm #confirm-message {
  max-width: 28rem;
  border-radius: 0.5rem;
  margin: 0;
  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2);
}

/* ===== Dropdown nav lists (custom-nav ul used in <details> menus) ===== */
.custom .custom-nav > li > a {
  display: flex;
  align-items: center;
  white-space: nowrap;
  cursor: pointer;
  border-radius: 0.375rem;
  padding: 0.5rem 0.75rem;
  font-size: 0.875rem;
  color: hsl(var(--foreground));
  transition: background-color 0.12s ease;
}

.custom .custom-nav > li > a:hover {
  background-color: hsl(var(--accent));
  color: hsl(var(--foreground));
}

/* ===== Native <dialog> modals ===== */

/* Reset browser default padding/border so Tailwind classes take full
   control. max-height + overflow-y-auto keep tall content scrollable. */
dialog {
  padding: 0;
  border: none;
  max-height: 90vh;
  overflow-y: auto;
}

/* Semi-transparent backdrop for showModal() */
dialog::backdrop {
  background-color: rgb(0 0 0 / 0.5);
}



/* ===== Alerts → Toast Notifications (top-right overlay) ===== */
.custom #success-message,
.custom #error-message {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 9999;
  width: auto;
  max-width: 22rem;
  min-width: 16rem;
  border-radius: 0.875rem;
  font-size: 0.8125rem;
  margin: 0;
  padding: 0.75rem 1rem;
  border: none;
  backdrop-filter: blur(12px);
  animation: custom-toast-in 0.3s ease-out;
}

@keyframes custom-toast-in {
  from { opacity: 0; transform: translateX(1rem); }
  to { opacity: 1; transform: translateX(0); }
}

/* Success toast */
.custom #success-message {
  background: hsl(142 70% 35% / 0.95);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15), 0 0 0 1px hsl(142 70% 30% / 0.3);
  color: white;
}

/* Error toast */
.custom #error-message {
  background: hsl(0 70% 45% / 0.95);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15), 0 0 0 1px hsl(0 70% 40% / 0.3);
  color: white;
}

/* Progress bar animation (auto-close indicator) */
.custom #success-message::after,
.custom #error-message[_*="wait"]::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0.875rem;
  right: 0.875rem;
  height: 2px;
  border-radius: 1px;
  background: rgba(255, 255, 255, 0.3);
  animation: custom-toast-progress 8s linear forwards;
}

@keyframes custom-toast-progress {
  from { width: calc(100% - 1.75rem); }
  to { width: 0; }
}

/* ===== Loading Overlay ===== */
.custom .custom-loading-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

.custom .custom-loading-overlay.htmx-request {
  opacity: 1;
  pointer-events: auto;
}

.custom .custom-loading-card {
  width: 5rem;
  height: 5rem;
  border-radius: 1rem;
  background: hsl(var(--card) / 0.9);
  backdrop-filter: blur(12px);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
  border: 1px solid hsl(var(--border) / 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
}

.custom .custom-loading-spinner {
  width: 2rem;
  height: 2rem;
  color: hsl(var(--primary));
  animation: custom-spinner-rotate 0.8s linear infinite;
}

.custom .custom-loading-spinner circle {
  stroke-dasharray: 90 150;
  stroke-dashoffset: 0;
}

@keyframes custom-spinner-rotate {
  to { transform: rotate(360deg); }
}

/* ===== Scrollbar ===== */
.custom ::-webkit-scrollbar { width: 6px; height: 6px; }
.custom ::-webkit-scrollbar-track { background: transparent; }
.custom ::-webkit-scrollbar-thumb { background-color: hsl(var(--muted-foreground) / 0.2); border-radius: 3px; }
.custom ::-webkit-scrollbar-thumb:hover { background-color: hsl(var(--muted-foreground) / 0.4); }

/* ===== Selection ===== */
.custom ::selection { background-color: hsl(var(--primary) / 0.15); }

/* ===== Motion =====
 *
 * Subtle transitions: 120–200 ms, ease-out. Restrained over flashy.
 * Pre-existing transitions (sidebar links, table rows, detail-menu items,
 * select controls) are defined in their own sections above and are
 * intentionally kept there for clarity.
 *
 * New additions here:
 *   - Buttons:           colour/shadow transition baseline for Flowbite hover states
 *   - Native <dialog>:   fade + small scale-in on open; backdrop fade
 *   - details.uv-dropdown: panel fade + 4 px slide from above on open
 *   - HTMX body swap:    content area opacity fade during navigation
 *
 * Accessibility: all non-essential animation is gated on
 * prefers-reduced-motion: no-preference.  The reduce block at the bottom
 * cancels ALL transitions/animations globally, including pre-existing ones.
 */

@media (prefers-reduced-motion: no-preference) {

  /* Flowbite buttons — smooth hover/focus colour shifts
     Excluded: custom-mobile-btn (has its own rules) */
  .custom button:not(.custom-mobile-btn),
  .custom a[class*="bg-primary"],
  .custom a[class*="bg-gray"] {
    transition: background-color 0.15s ease, border-color 0.15s ease,
                color 0.15s ease, box-shadow 0.15s ease;
  }

  /* Native <dialog> open — fade in + slight scale from 97 % */
  @keyframes custom-dialog-in {
    from { opacity: 0; transform: scale(0.97); }
    to   { opacity: 1; transform: scale(1); }
  }

  dialog[open] {
    animation: custom-dialog-in 0.18s ease-out;
  }

  /* Backdrop: fade in (animation restarts on every showModal() call) */
  @keyframes custom-backdrop-in {
    from { opacity: 0; }
    to   { opacity: 1; }
  }

  dialog::backdrop {
    animation: custom-backdrop-in 0.18s ease-out;
  }

  /* details.uv-dropdown panel — fade + 4 px slide from above */
  @keyframes custom-dropdown-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  details.uv-dropdown[open] > div {
    animation: custom-dropdown-in 0.15s ease-out;
  }

  /* HTMX body swap — briefly fade the content area while the new page loads
     (nav links target body; we fade #main so the sidebar stays steady) */
  #main {
    transition: opacity 0.12s ease;
  }

  body.htmx-swapping #main {
    opacity: 0;
  }
}

/* Reduced motion — cancel all transitions and animations globally,
   including pre-existing ones (sidebar links, table rows, etc.) */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ===== Device Detail — section fact strip (chips below name row) ===== */
.custom .device-fact-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem 0.625rem;
  padding-top: 0.25rem;
}

.custom .device-fact-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.5rem;
  border-radius: 9999px;
  background: hsl(var(--muted));
  color: hsl(var(--muted-foreground));
  font-size: 0.75rem;
  font-weight: 500;
  white-space: nowrap;
  border: 1px solid hsl(var(--border));
}

/* ===== Device Detail — two-phase skeleton (Flowbite animate-pulse style) ===== */
/*
 * .device-skeleton is rendered server-side inside the content panel and
 * immediately replaced by the real content when the hx-trigger="load"
 * /content request completes. The skeleton shapes match the card+row
 * structure of each section so the transition is visually smooth.
 */
.custom .device-skeleton {
  animation: device-skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

@keyframes device-skeleton-pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.4; }
}

.custom .device-skeleton-card {
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  overflow: hidden;
}

.custom .device-skeleton-header {
  height: 2.75rem;
  background: hsl(var(--muted));
  border-bottom: 1px solid hsl(var(--border));
}

.custom .device-skeleton-body {
  padding: 1rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.custom .device-skeleton-line {
  height: 0.875rem;
  background: hsl(var(--muted));
  border-radius: 0.375rem;
}

/* ===== Assignments card — inline tag-input ===== */
.custom .assign-box {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  width: 100%;
  padding: 0.375rem 0.625rem;
  border-radius: 0.5rem;
  min-height: 2.5rem;
}
.custom .assign-box:focus-within {
  outline: none;
  border-color: #3b82f6 !important;
  box-shadow: 0 0 0 3px rgb(59 130 246 / 0.15);
}
.custom .assign-chips { display: contents; }
.custom .assign-search-input {
  flex: 1 1 8rem;
  min-width: 8rem;
  border: 0;
  background: transparent;
  padding: 0.125rem 0.25rem;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: inherit;
}
.custom .assign-search-input::placeholder { color: #9ca3af; }
.custom .assign-search-input:focus { outline: none; box-shadow: none; }
/* View mode (fieldset disabled): box stays visible but muted, ✕ hidden */
.custom fieldset[disabled] .assign-chip button { display: none; }
.custom fieldset[disabled] .assign-box { opacity: 0.6; pointer-events: none; }
.custom fieldset[disabled] .assign-results { display: none; }

/* ===== Device Detail: pin header + tab bar, scroll content area (lg+) =====
 *
 * On desktop the device detail page becomes a height-bounded flex column so
 * ONLY #device-section-content (the area below the tab bar) scrolls, while
 * the breadcrumb header + identity block + actions bar + tab bar stay pinned.
 *
 * Height chain (each element propagates the bounded height downward):
 *   .custom-main            height:100vh (existing) → overflow-y:hidden
 *   main.device-detail-main flex:1 flex-col overflow:hidden min-h:0
 *   .custom-card            flex:1 flex-col min-h:0
 *   .custom-card-body       flex:1 flex-col min-h:0 overflow:hidden
 *     #computer-header      flex-shrink:0  (identity block, pinned)
 *     #computer-actions-bar flex-shrink:0  (actions row, pinned)
 *     .device-detail-tabs-section  flex:1 flex-col min-h:0 overflow:hidden
 *       DeviceTabBar div    flex-shrink:0  (tab pills, pinned)
 *       #device-section-content   flex:1 min-h:0 overflow-y:auto  ← SCROLLS
 *
 * Mobile (<lg): none of these rules apply. .custom-main keeps overflow-y:auto
 * and the whole page scrolls as before — no cramped inner scroll region.
 *
 * Infinite scroll tables (Installed Apps, Deploy, etc.) live inside
 * #device-section-content. Their .custom-table-scroll wrapper's own
 * overflow and max-height are disabled so they don't create a double-scroll;
 * #device-section-content is the single scroll container. The IntersectionObserver
 * in initInfiniteScroll uses #device-section-content as root (see custom.js).
 */
@media (min-width: 1024px) {
  /* Stop .custom-main from being the scroll container on device detail pages */
  .custom .custom-main:has(> main.device-detail-main) {
    overflow-y: hidden;
  }

  /* main → flex column, fills the available height below the breadcrumb header */
  .custom main.device-detail-main {
    display: flex !important;   /* override Tailwind grid */
    flex-direction: column;
    align-items: stretch;       /* override items-start */
    flex: 1;
    min-height: 0;
    overflow: hidden;
    /* 1.5rem top + sides; bottom = 0 because the scroll content has its own
       bottom padding. This insets the entire shell (header, action bar, tabs,
       content) uniformly so nothing touches the main-area edges. */
    padding: 1.5rem 1.5rem 0 !important;
    gap: 0 !important;
  }

  /* outer transparent card → flex column, stretches to fill main */
  .custom main.device-detail-main > .custom-card.custom-card-default {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  /* card-body → flex column, fills the card; overflow:hidden clips any
     natural-height overflow so the bounded height can propagate */
  .custom main.device-detail-main > .custom-card.custom-card-default > .custom-card-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
  }

  /* Identity header + actions bar → natural height, do not grow */
  .custom main.device-detail-main #computer-header,
  .custom main.device-detail-main #computer-actions-bar {
    flex-shrink: 0;
  }

  /* Tab bar + content wrapper → fills remaining space and propagates height */
  .custom main.device-detail-main .device-detail-tabs-section {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
  }

  /* DeviceTabBar container (the overflow-x-auto div) → natural height, pinned */
  .custom main.device-detail-main .device-detail-tabs-section > div:first-child {
    flex-shrink: 0;
  }

  /* Content region → THE scroll container for this page on desktop */
  .custom main.device-detail-main #device-section-content {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    /* Stable gutter prevents layout shift when switching between tabs that
       produce a scrollbar and those that don't */
    scrollbar-gutter: stable;
    /* Sides: 0 — the outer main.device-detail-main (1.5rem) provides the
       left/right inset; keeping 0 here ensures no double side-padding and
       keeps the content left-edge aligned with the header / tab bar.
       Top: 0 — visual gap between the pinned tab bar and the first card
       comes from the mt-4 margin on #device-section-content itself (outside
       the scroll container). A padding-top here creates a gap ABOVE the
       sticky <thead> where scrolling body rows can peek through.
       Bottom: 1rem — breathing room after the last row before the scroll boundary. */
    padding-bottom: 1rem;
  }

  /* Prevent overflow:hidden flex-item height collapse on hardware section
     cards. Section divs with overflow:hidden lose CSS automatic-minimum-size
     (their min-height collapses from auto → 0) and are squished to near-zero
     by the flex algorithm. flex-shrink:0 opts each direct child out of
     shrinking so cards render at full content height; the scroll container
     (#device-section-content with overflow-y:auto) handles any overflow. */
  .custom main.device-detail-main #device-section-content > * {
    flex-shrink: 0;
  }

  /* Tables inside device-detail content: disable the inner custom-table-scroll
     container so the table's content flows into #device-section-content
     (the single scroll container). The IntersectionObserver root is changed
     to #device-section-content in initInfiniteScroll (see custom.js). */
  .custom main.device-detail-main .custom-table-scroll {
    overflow: visible !important;
    max-height: none !important;
    margin-bottom: 0;
  }

  /* Sticky table header inside the device-detail scroll region.
     When .custom-table-scroll has overflow:visible, <thead> becomes sticky
     relative to #device-section-content (the new scroll container). Browsers
     then paint <th> cells with their own (transparent) background, letting
     scrolling body rows bleed through. Force position:sticky and a solid
     opaque background on both <thead> and every <th> cell.
     Light: gray-100 via --muted. Dark: gray-700 via --accent. */
  .custom main.device-detail-main #device-section-content thead {
    position: sticky;
    top: 0;
    z-index: 10;
    background: hsl(var(--muted));
  }
  .custom main.device-detail-main #device-section-content thead th {
    background: hsl(var(--muted));
  }
  .dark.custom main.device-detail-main #device-section-content thead {
    background: hsl(var(--accent));
  }
  .dark.custom main.device-detail-main #device-section-content thead th {
    background: hsl(var(--accent));
  }
}

/* ===== Masonry card column (Security tab) ===== */

/* The Security tab stacks cards of very different heights — revealing a
   recovery key grows one of them by several lines. A CSS grid row is as tall
   as its tallest cell, so the shorter card left a dead gap underneath. CSS
   columns pack them instead; every direct child must avoid being split
   across the column break and carry its own bottom margin (column layout
   ignores `gap` vertically). */
.custom .custom-masonry > * {
  break-inside: avoid;
  page-break-inside: avoid;
  margin-bottom: 1rem;
}
