/*
 * Cougardesk — component stylesheet.
 *
 * NOT the brand source of truth. That is tokens.css (loaded AFTER this file,
 * so its migration map wins the cascade), documented in
 * docs/design/BRAND_GUIDELINES.md. Reference --cd-* tokens here; don't add
 * raw hex — `npm run lint:css` ratchets against it.
 *
 * The header comment that used to sit here claimed the brand red was
 * "#A8353A (Pantone 1807)". It is #9D2E34 — verified against APU's own
 * production token file and the Carnegie Web Style Guide. The wrong value was
 * never used by any rule, but it was being read and copied, so it is gone.
 */

/* ─── Brand Variables ─── */
:root {
  /* Font stacks live in tokens.css (--cd-font-sans / --cd-font-mono).
     The legacy --font-sans/--font-serif that used to sit here are gone:
     --font-serif had zero refs, and --font-sans listed "Helvetica Neue"
     BEFORE "Proxima Nova", so anything using it took Helvetica on macOS
     instead of the brand face. `body` and `h1-h6` were masked (tokens.css
     loads second and re-declares both), but `.navbar-apu .navbar-brand`
     and `#sidebar .sidebar-header` outrank those selectors and really did
     render Helvetica. All four now reference --cd-font-sans directly, so
     the brand face no longer depends on stylesheet order. */

  /* Layout */
  --sidebar-width: 240px;
  --navbar-height: 48px;
  /* Where the app chrome actually ENDS, which is not --navbar-height whenever a
     banner renders above the navbar and pushes it down (env, announcement,
     impersonation). `main.js`'s cdSyncChromeOffset measures the navbar and
     overwrites this on <html> at init and on resize; the constant below is only
     the value before that runs, and for a page with no navbar at all. A fixed
     or sticky overlay must use THIS, never --navbar-height — a static `top`
     cannot measure a banner that wraps to two or three lines (CD-1985). */
  --chrome-offset: var(--navbar-height);
  --transition-speed: 0.2s;

  /* Ticket tab spacing (0–100, user preference `tab_spacing`; 80 = default).
     Set on <html> pre-paint from a cookie/localStorage — see layouts/main.hbs. */
  --tab-spacing: 80;
  /* Gap between tabs, and the matching gap above them. Piecewise: 0→2px over
     spacing 0–80, then 2→8px over 80–100. max() of the two linear pieces IS the
     piecewise curve — each dominates on its own side of 80, so there's no kink
     to hand-code. 0→0px · 50→1.25px · 80→2px · 90→5px · 100→8px */
  --tab-gap: max(
    calc(var(--tab-spacing) * 0.025px),
    calc((var(--tab-spacing) - 80) * 0.3px + 2px)
  );
}

/* ─── Dark-Mode Contrast Adaptor: anti-FOUC pre-hide ─── */
/* darkModeContrast.js (head-loaded, no defer) adds `.cd-ca-init` to <html>
   synchronously before <body> paints, so ticket message-body islands are
   hidden pre-paint and never flash unprocessed near-black-on-dark text. The
   script removes the class the instant its initial contrast pass finishes,
   with a 1.5s safety-timeout + window 'error' reveal so content can never get
   stuck hidden. The hide depends on the JS-added class AND dark mode, so with
   JS disabled (no class) or in light mode nothing is ever hidden — content is
   always visible. `visibility` (not `display`) keeps layout stable, so the
   reveal causes no reflow/jump. */
html.cd-ca-init[data-theme="dark"] .ticket-description,
html.cd-ca-init[data-theme="dark"] .comment-display,
html.cd-ca-init[data-theme="dark"] .plain-text-body,
html.cd-ca-init[data-theme="dark"] .article-content {
  visibility: hidden;
}
/* Per-element hide for lazily injected/expanded islands (tab-mode innerHTML,
   realtime comment merges): the MutationObserver adds `.cd-ca-hide` the moment
   it first sees a new island and removes it once that island's pass completes,
   normally in the next animation frame — or off LAZY_REVEAL_SAFETY_MS if the
   frame loop is starved or suspended, since nothing else would un-hide it. */
html[data-theme="dark"] .cd-ca-hide {
  visibility: hidden;
}

/* The three amber twins that stood here (.bg-light-yellow,
   .comment-card-internal and its .card-header) are gone: --cd-warning-bg and
   --cd-warning already flip, so the base rules cover both themes. Their
   literals — #3A3520, #332E1A, #B8860B — were hand-guessed dark equivalents.

   Removing them was right; what it exposed was a JS assumption, not a missing
   CSS declaration — see CD-2032 in cougardesk-web/invariants/ui-and-design.md.
   Nothing here needs a literal back.

   The zero-state gradient KEEPS a twin. It is warm NEUTRAL, not amber, and a
   gradient needs both stops moving the same direction: --cd-surface-hover to
   --cd-surface-sunken darkens in dark and lightens in light, so a single rule
   would invert it. */
[data-theme="dark"] .zero-state-illustration {
  background: linear-gradient(145deg, var(--cd-surface-hover) 0%, var(--cd-surface-sunken) 100%);
}

/* Nav tabs: +2px padding over the Bootstrap default (0.5rem 1rem) */
.nav-tabs .nav-link {
  padding: 0.625rem 1.125rem;
}

/* Dark mode: comment cards */
[data-theme="dark"] .comment-card-public .card-header {
  background-color: rgba(13, 110, 253, 0.1);
}

/* ─── Global Reset & Base ─── */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  font-family: var(--cd-font-sans);
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--cd-text);
  /* tokens.css re-declares body{background-color} from the same token and is
     linked AFTER this file, so the literal here was masked rather than broken —
     but it would have become a dark-mode bug the moment the load order moved. */
  background-color: var(--cd-surface);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}

/* Background artwork layer — opacity driven by the user's contrast preference.
   The SVG's internal alphas are authored at 2x so --bg-contrast: 100 (default)
   reproduces the original look and 200 renders the art at full strength. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: url('/img/bg-hills.svg') no-repeat bottom center / cover;
  opacity: calc(var(--bg-contrast, 100) / 200);
}

/* Ambient cougar overlay — the silhouette, lifted out of bg-hills.svg so it can
   wander. .cougar-viewport mirrors body::before exactly (fixed, behind the UI,
   contrast-driven opacity), so at rest the cougar is pixel-identical to the
   baked-in art. cougarWander.js adds .wander after 5 min of inactivity; the
   cougar strolls off the left edge, wraps around, and walks back home. The svg
   uses xMidYMax slice — the same framing as the background's cover / bottom
   center — so translateX(0) lands it on its mid-hill spot. */
.cougar-viewport {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden; /* clip the off-screen travel so it never adds a scrollbar */
  opacity: calc(var(--bg-contrast, 100) / 200);
}

.cougar-layer {
  width: 100%;
  height: 100%;
}

.cougar-bob {
  display: block;
  width: 100%;
  height: 100%;
}

/* Day-spot easter egg (part of Party Cougar, ui_preferences.party_cougar): the
   spot is now placed by cougarWander.js as part of the .cougar-walk travel
   transform (which lifts the feet onto the ridge and leans to the slope at the
   spot, geometry read from the guide path), so .cougar-spot no longer carries a
   transform of its own — it's kept only as the cougar-body wrapper. */

/* Party-cougar hat (opt-in, ui_preferences.party_cougar): the hat markup is
   always in the overlay; the server adds .party inside the user's birthday
   window. The birthday comes from PeopleSoft — no birthday value, no hat. */
.cougar-hat {
  display: none;
}
.cougar-layer.party .cougar-hat {
  display: block;
}

/* The horizontal travel (home → off the left edge → off-screen wrap → back →
   rest) is driven frame-by-frame by cougarWander.js, which sets the SVG
   `transform` attribute on .cougar-walk directly so it can also lift the feet
   onto the ridge contour and lean to the slope. The old pure-X @keyframes
   cougar-travel (and the transform transition that used to ease it home) are
   gone — the driver owns and tweens the transform itself. */

.cougar-layer.wander .cougar-bob {
  animation: cougar-bob 1.1s ease-in-out infinite;
}

/* Subtle vertical gait so a legless silhouette reads as walking, not sliding. */
@keyframes cougar-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4px); }
}

/* cougarWander.js already bails under reduced motion; this also stops the gait
   bob if the class is ever applied without it (old browsers lacking matchMedia). */
@media (prefers-reduced-motion: reduce) {
  .cougar-layer.wander .cougar-bob {
    animation: none;
  }
}

.app-body-row {
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
}

.app-main-col {
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: var(--cd-font-sans);
  font-weight: 600;
  color: var(--cd-text);
  letter-spacing: -0.01em;
}

h1 {
  font-weight: 700;
}

a {
  color: var(--cd-brand);
  text-decoration: none;
  transition: color var(--transition-speed) ease;
}

a:hover {
  color: var(--cd-brand-hover);
  text-decoration: underline;
}

/* ─── Navbar ─── */
.navbar-apu {
  background-color: var(--cd-brand-fill);
  min-height: var(--navbar-height);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
  border: none;
}

.navbar-apu .navbar-brand {
  font-family: var(--cd-font-sans);
  font-weight: 700;
  font-size: 1.25rem;
  color: var(--cd-text-on-brand) !important;
  letter-spacing: 0.01em;
  display: flex;
  align-items: center;
  gap: 10px;
}

.brand-logo {
  height: 32px;
  width: 32px;
}

.brand-logo-dark {
  display: none;
}

[data-theme="dark"] .brand-logo-light {
  display: none;
}

[data-theme="dark"] .brand-logo-dark {
  display: block;
}

.navbar-apu .navbar-brand .brand-divider {
  width: 1px;
  height: 28px;
  background-color: rgba(255, 255, 255, 0.35);
}

.navbar-apu .navbar-brand .brand-app-name {
  font-size: 1.1rem;
  font-weight: 500;
  opacity: 0.95;
}

.navbar-apu .nav-link {
  color: rgba(255, 255, 255, 0.85) !important;
  font-weight: 500;
  font-size: 0.875rem;
  padding: 0.5rem 0.9rem !important;
  border-radius: var(--cd-radius-sm);
  transition: all var(--transition-speed) ease;
}

.navbar-apu .nav-link:hover,
.navbar-apu .nav-link:focus {
  color: var(--cd-text-on-brand) !important;
  background-color: rgba(255, 255, 255, 0.12);
}

.navbar-apu .nav-link.active {
  color: var(--cd-text-on-brand) !important;
  background-color: rgba(255, 255, 255, 0.18);
}

/* CD-1898 — the collapse hamburger. Nothing styled `.navbar-toggler`, so it fell
   back to Bootstrap's default dark glyph and dark border on the brick-red bar,
   where every other glyph is `--cd-text-on-brand`. Scoped to `.navbar-apu`, so
   this covers BOTH navbars in one rule: the signed-in app shell
   (partials/navbar.hbs) and layouts/public.hbs, which share the class.

   It was only ever wrong in LIGHT mode — the glyph inherits the theme, so dark
   mode already rendered it light against the same fill. `--cd-text-on-brand` is
   pinned rather than theme-flipping (the fill it sits on does not flip either),
   which is what makes one rule correct in both. */
.navbar-apu .navbar-toggler {
  color: var(--cd-text-on-brand);
  border-color: rgba(255, 255, 255, 0.35);
}

.navbar-apu .navbar-toggler:focus,
.navbar-apu .navbar-toggler:focus-visible {
  /* Bootstrap's default focus ring is derived from its own dark border colour
     and all but disappears here. */
  box-shadow: 0 0 0 0.2rem rgba(255, 255, 255, 0.3);
}

/* The bars are a background-image SVG with the stroke colour baked into a data
   URI, so `color` cannot reach them. Re-draw the same three bars as a MASK and
   paint them with the token: the only colour this rule states is a token, and
   the glyph tracks the bar like every other icon on it.

   The `%23000` in the mask is OPACITY, not colour — an opaque stroke means
   "keep this pixel" (same rule as the `#000` mask stops elsewhere in this file).
   Bootstrap's own SVG strokes at 0.75 alpha, which as a mask would render the
   bars 25% transparent, so this re-states the path rather than reusing it. */
.navbar-apu .navbar-toggler-icon {
  background-image: none;
  background-color: var(--cd-text-on-brand);
  -webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23000' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
  mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23000' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

.navbar-apu .dropdown-menu {
  border: 1px solid var(--cd-line);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
  border-radius: var(--cd-radius-md);
  padding: 0.5rem 0;
}

.navbar-apu .dropdown-item {
  font-size: 0.875rem;
  padding: 0.45rem 1rem;
}

.navbar-apu .dropdown-item:hover {
  background-color: var(--cd-brand-soft);
  color: var(--cd-brand);
}

.navbar-apu .dropdown-item.active,
.navbar-apu .dropdown-item:active {
  background-color: var(--cd-brand-fill);
  color: var(--cd-text-on-brand);
}

/* Avatar initials — the base chip sits on an ordinary surface (e.g. the customer
   card in customers/show.hbs), so it takes the soft brand tint. The white-alpha
   fill + on-brand text belong to the navbar variant below and nowhere else:
   a white alpha over a light card renders the initial invisible. */
.avatar-initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background-color: var(--cd-brand-soft);
  color: var(--cd-brand);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.5px;
  flex-shrink: 0;
}

.navbar-apu .avatar-initials {
  background-color: rgba(255, 255, 255, 0.2);
  color: var(--cd-text-on-brand);
}

/* PeopleSoft ID photo (ADR-0032). Added by personDirectory.js only once the
   image has actually loaded, so a person without one costs no layout change —
   which is why there is no placeholder rule here and no min-height on the slot.
   The size comes from the img's width/height attributes (data-photo-size), so a
   photo occupies exactly the box its initials chip did. */
.person-photo {
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

.customer-photo-slot {
  display: inline-block;
}

.customer-photo-slot .person-photo {
  margin-right: 0.35rem;
  vertical-align: text-bottom;
}

/* New Ticket button in navbar */
.btn-new-ticket {
  background-color: rgba(255, 255, 255, 0.15);
  color: var(--cd-text-on-brand);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: var(--cd-radius-pill);
  padding: 0.2rem 0.6rem;
  font-size: 0.875rem;
  font-weight: 500;
  transition: background-color var(--transition-speed) ease;
  white-space: nowrap;
}

.btn-new-ticket:hover,
.btn-new-ticket:focus {
  background-color: rgba(255, 255, 255, 0.25);
  color: var(--cd-text-on-brand);
  border-color: rgba(255, 255, 255, 0.5);
}

/* Subtle APU badge */
.bg-apu-subtle {
  background-color: var(--cd-brand-soft);
  color: var(--cd-brand);
}

/* Keep old class for backward compat */
.bg-apu-maroon {
  background-color: var(--cd-brand-fill) !important;
}

/* ─── Sidebar ─── */
:root {
  --sidebar-collapsed-width: 58px;
  --sidebar-mobile-width: 280px;
}

#sidebar {
  min-height: 0;
  background-color: var(--cd-surface-raised);
  border-right: 1px solid var(--cd-line);
  transition: margin-right 0.25s ease, width 0.25s ease, min-width 0.25s ease,
    transform 0.25s ease;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  position: relative;
  /* Always above main content so transitions out of hover state can't be overpainted by sticky headers */
  z-index: 1045;
  scrollbar-width: none;
}

#sidebar::-webkit-scrollbar,
#sidebar *::-webkit-scrollbar {
  display: none;
}

#sidebar,
#sidebar * {
  scrollbar-width: none;
}

/* Suppress all transitions for the first paint to avoid flashes */
#sidebar.sidebar-init,
#sidebar.sidebar-init * {
  transition: none !important;
}

/* Suppress label/header transitions during hover-to-collapsed animation
   so they do not bleed over the shrinking overlay */
#sidebar.sidebar-collapsing .nav-label,
#sidebar.sidebar-collapsing .badge,
#sidebar.sidebar-collapsing .sidebar-header,
#sidebar.sidebar-collapsing hr {
  transition: none;
}

#sidebar .sidebar-header {
  font-family: var(--cd-font-sans);
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--cd-text-muted);
  padding: 0.5rem 1rem;
  overflow: hidden;
  max-height: 3rem;
  transition: max-height 0.25s ease, opacity 0.2s ease;
}

/* Clamp list items to the sidebar width so a long view name can't push its
   count badge past the (overflow-x:hidden) edge — the nav-link's flex label
   only shrinks if its <li> is bounded to the container. */
#sidebar .nav-item {
  max-width: 100%;
  min-width: 0;
}

#sidebar .nav-link {
  color: var(--cd-text-muted);
  padding: 0.3rem 0.65rem 0.3rem 12px;
  border-radius: var(--cd-radius-sm);
  font-size: 0.875rem;
  font-weight: 450;
  margin: 1px 0.25rem 1px 0;
  transition: background-color var(--transition-speed) ease,
    color var(--transition-speed) ease,
    gap 0.25s ease;
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  max-width: 100%;
  width: 100%;
}

#sidebar .nav-link i {
  font-size: 1.5rem;
  width: 30px;
  text-align: center;
  flex-shrink: 0;
  opacity: 0.7;
}

#sidebar .nav-link:hover {
  background-color: var(--cd-surface-sunken);
  color: var(--cd-text);
  text-decoration: none;
}

#sidebar .nav-link:hover i {
  opacity: 1;
}

#sidebar .nav-link.active {
  background-color: var(--cd-brand-soft);
  color: var(--cd-brand);
  font-weight: 600;
}

#sidebar .nav-link.active i {
  opacity: 1;
  color: var(--cd-brand);
}

#sidebar hr {
  margin: 0.75rem 1rem;
  border-color: var(--cd-line);
  opacity: 1;
  overflow: hidden;
  max-height: 2rem;
  transition: max-height 0.2s ease, opacity 0.15s ease;
}

#sidebar .nav-label {
  flex: 1 1 0;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: max-width 0.25s ease, opacity 0.2s ease;
}

#sidebar .badge {
  max-width: 60px;
  overflow: hidden;
  transition: max-width 0.25s ease, opacity 0.2s ease;
  flex-shrink: 0;
}

/* ── States ── */

#sidebar[data-state="expanded"] {
  width: var(--sidebar-width);
  min-width: var(--sidebar-width);
}

#sidebar[data-state="collapsed"] {
  width: var(--sidebar-collapsed-width);
  min-width: var(--sidebar-collapsed-width);
}

#sidebar[data-state="collapsed"] .nav-label,
#sidebar[data-state="collapsed"] .badge {
  max-width: 0;
  opacity: 0;
  pointer-events: none;
}

#sidebar[data-state="collapsed"] .badge {
  margin-left: 0 !important;
}

#sidebar[data-state="collapsed"] .sidebar-header,
#sidebar[data-state="collapsed"] hr {
  max-height: 0;
  opacity: 0;
}

#sidebar[data-state="collapsed"] .nav-link {
  gap: 0;
}

/* ── Sidebar view drag/drop reorder ── */
.view-nav-item {
  position: relative;
}

.sidebar-view-grip {
  position: absolute;
  left: -1px;
  top: 50%;
  transform: translateY(-50%);
  /* Kept in layout (not display:none) and merely transparent until row hover,
     mirroring .cd-drag-handle. A display:none grip is removed from the box the
     instant the pointer leaves the row mid-drag, which aborts the native HTML5
     drag before it starts (dead sidebar reorder + failing e2e, CD-933). */
  display: inline-flex;
  opacity: 0;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 22px;
  cursor: grab;
  color: var(--cd-text-muted);
  font-size: 0.85rem;
  line-height: 1;
  z-index: 2;
  user-select: none;
}

.sidebar-view-grip i {
  pointer-events: none;
}

.view-nav-item:hover .sidebar-view-grip {
  opacity: 1;
}

.sidebar-view-grip:hover {
  color: var(--cd-text);
}

.sidebar-view-grip:active {
  cursor: grabbing;
}

#sidebar[data-state="collapsed"] .sidebar-view-grip {
  display: none !important;
}

/* Drop indicators (dragging/line) come from the shared CdDragReorder CSS
   (.cd-reorder-row.cd-drop-*, CD-933) — no bespoke per-surface drag CSS. */

/* ── Profile: per-section history controls (undo/redo/restore) ── */
.pref-section {
  position: relative;
}

.pref-section > .pref-section-actions {
  position: absolute;
  top: -0.25rem;
  right: 0;
}

.pref-section-actions .btn-link {
  color: var(--cd-text-muted);
  padding: 0.1rem 0.35rem;
  text-decoration: none;
}

.pref-section-actions .btn-link:hover {
  color: var(--cd-text);
}

.pref-section-actions .btn-link:disabled {
  opacity: 0.35;
}

/* ── Profile: Appearance & UI sub-menu toggle ── */
.pref-appearance-toggle {
  color: var(--cd-text);
}

.pref-appearance-chevron {
  font-size: 0.75rem;
  transition: transform 0.15s ease;
}

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

/* ── Profile: settings section reveal flash ──
   Bootstrap already animates the collapse height; once it finishes opening we
   briefly outline the section so the eye catches where the newly revealed edges
   are. profilePrefs.js adds .cd-section-flash on shown.bs.collapse (and when it
   manually reveals the omnibar group), then drops it on animationend. */
@keyframes cd-section-flash {
  0%   { box-shadow: 0 0 0 0 rgba(157, 46, 52, 0); }
  20%  { box-shadow: 0 0 0 3px rgba(157, 46, 52, 0.4); }
  100% { box-shadow: 0 0 0 3px rgba(157, 46, 52, 0); }
}
.cd-section-flash {
  border-radius: var(--cd-radius-md);
  animation: cd-section-flash 0.9s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .cd-section-flash { animation: none; }
}

/* ── Profile: live setting previews ──
   Small inline demos beside a setting; profilePrefs.js (wirePrefPreviews)
   repaints them from the live control state. */
.pref-preview {
  font-size: 0.8125rem;
  padding: 0.5rem 0.625rem;
  border: 1px dashed var(--bs-border-color);
  border-radius: var(--cd-radius-md);
  background: var(--bs-tertiary-bg);
  max-width: 360px;
}
.pref-preview-link {
  color: var(--bs-link-color);
  text-decoration: underline;
  cursor: pointer;
}
/* Interface Animations / Animation Speed: a crossfading status label plus a
   dot tracking underneath. Both durations scale with the global --cd-anim-scale
   (0 when animations are off → frozen). The 6.4s base is deliberately slow so
   dropping the speed down reads as "slower", not a flicker. */
.pref-anim-demo {
  position: relative;
  height: 1.75rem;
}
.pref-anim-text {
  position: relative;
  display: inline-block;
  height: 1.25rem;
  font-weight: 600;
}
.pref-anim-a,
.pref-anim-b {
  position: absolute;
  left: 0;
  top: 0;
  white-space: nowrap;
  animation: pref-anim-fade-a calc(6.4s * var(--cd-anim-scale, 1)) ease-in-out infinite;
}
.pref-anim-b {
  animation-name: pref-anim-fade-b;
}
.pref-anim-dot {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  background: var(--cd-brand-fill);
  animation: pref-anim-slide calc(6.4s * var(--cd-anim-scale, 1)) ease-in-out infinite alternate;
}
@keyframes pref-anim-slide {
  from { left: 0; }
  to   { left: calc(100% - 0.6rem); }
}
@keyframes pref-anim-fade-a {
  0%, 40% { opacity: 1; }
  50%, 90% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes pref-anim-fade-b {
  0%, 40% { opacity: 0; }
  50%, 90% { opacity: 1; }
  100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .pref-anim-dot,
  .pref-anim-a,
  .pref-anim-b { animation: none; }
  .pref-anim-b { opacity: 0; }
}
/* Omnibar preset editor: fixed left column so scope names line up. */
.omnibar-preset-name {
  flex: 0 0 9rem;
  max-width: 9rem;
}

/* ── Profile: ticket sidebar layout drag list ── */
.ui-order-item[draggable="true"] {
  cursor: grab;
}

.ui-order-item.ui-order-dragging {
  opacity: 0.4;
}

.ui-order-item.ui-order-locked {
  color: var(--cd-text-muted);
  background-color: var(--cd-surface-sunken);
}

/* Hover overlays main content while reserving only the collapsed footprint.
   Negative right margin pulls following content back so the layout footprint
   stays at --sidebar-collapsed-width even though visible width is full. */
#sidebar[data-state="hover"] {
  width: var(--sidebar-width);
  min-width: var(--sidebar-width);
  margin-right: calc(var(--sidebar-collapsed-width) - var(--sidebar-width));
  box-shadow: 4px 0 12px rgba(0, 0, 0, 0.10);
}

/* ── Nav icon flair (opt-in personality) ──
   sidebarIconFlair.js sets the icon color inline; this transition (present only
   when the feature is enabled) smooths the fade in and back out. */
#sidebar.nav-flair-on .nav-link i.bi,
#sidebar.nav-flair-on .nav-link i.cd-icon-slot {
  transition: color 0.8s ease;
}

/* ── Sidebar toggle button ── */
.sidebar-toggle-wrapper {
  display: flex;
  justify-content: flex-start;
  padding: 0.4rem 0.5rem 0 9px;
  flex-shrink: 0;
}

.sidebar-toggle-btn {
  background: none;
  border: none;
  padding: 0.2rem 0.4rem;
  color: var(--cd-text-muted);
  cursor: pointer;
  font-size: 1.5rem;
  line-height: 1;
  border-radius: var(--cd-radius-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.sidebar-toggle-btn:hover,
.sidebar-toggle-btn:focus-visible {
  color: var(--cd-text);
  background-color: var(--cd-surface-sunken);
  outline: none;
}

/* Navbar variant: white on red navbar */
.navbar-apu .sidebar-toggle-btn {
  color: var(--cd-text-on-brand);
}

.navbar-apu .sidebar-toggle-btn:hover,
.navbar-apu .sidebar-toggle-btn:focus-visible {
  color: var(--cd-text-on-brand);
  background-color: rgba(255, 255, 255, 0.15);
}

/* Sidebar panels (main + management) */
.sidebar-panel {
  opacity: 1;
  transition: opacity 0.5s ease;
}

.sidebar-panel.fading-out {
  opacity: 0;
  pointer-events: none;
}

.sidebar-scrollable {
  flex: 1 1 0;
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
  scrollbar-width: none;
}

.sidebar-scrollable::-webkit-scrollbar {
  display: none;
}

.sidebar-nav-content {
  padding: 0.5rem 0.4rem 0.5rem 0;
}

/* Gear footer pinned to bottom */
.sidebar-footer {
  flex-shrink: 0;
  padding: 0.4rem 0.4rem 0.4rem 0;
}

/* Version label pinned at very bottom-left of sidebar */
.sidebar-version {
  flex-shrink: 0;
  position: absolute;
  bottom: 10px;
  right: 0px;
  padding: 0 0.85rem;
  line-height: 1;
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--cd-text-subtle);
  text-align: left;
  user-select: none;
}

#sidebar[data-state="collapsed"] .sidebar-version {
  display: none;
}

/* ── Mobile drawer ── */
.sidebar-backdrop {
  display: none;
}

@media (max-width: 991.98px) {
  .app-body-row {
    position: relative;
  }

  #sidebar {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: var(--sidebar-mobile-width);
    min-width: var(--sidebar-mobile-width);
    z-index: 1045;
    transform: translateX(-100%);
    box-shadow: 4px 0 12px rgba(0, 0, 0, 0.15);
  }

  #sidebar[data-state="drawer-open"] {
    transform: translateX(0);
  }

  #sidebar[data-state="drawer-closed"] {
    transform: translateX(-100%);
  }

  /* Mobile drawer is always "expanded" visually - show labels */
  #sidebar .nav-label {
    opacity: 1;
    pointer-events: auto;
  }

  #sidebar .badge {
    max-width: 60px;
    opacity: 1;
    pointer-events: auto;
  }

  #sidebar .sidebar-header,
  #sidebar hr {
    max-height: 3rem;
    opacity: 1;
  }

  #sidebar .nav-link {
    gap: 10px;
  }

  .sidebar-backdrop {
    display: block;
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 1040;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
  }

  .sidebar-backdrop.show {
    opacity: 1;
    pointer-events: auto;
  }
}

.sidebar-mgmt-trigger {
  width: 100%;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
}

/* Management back button */
.sidebar-mgmt-back {
  width: 100%;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--cd-text);
}

.sidebar-mgmt-back:hover {
  background-color: var(--cd-surface-sunken);
  color: var(--cd-text);
}

.sidebar-mgmt-back i {
  font-size: 1.5rem;
  width: 30px;
  text-align: center;
  flex-shrink: 0;
}

/* Tag category dot in sidebar */
.tag-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}

/* Centre the 10px dot within a 30px cell to align with queue icons */
#sidebar .nav-link .tag-dot {
  margin-left: 10px;
  margin-right: 10px;
}

/* Tag search dropdown */
.tag-search-wrapper {
  position: relative;
}

.tag-search-results {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 1050;
  background: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  border-top: none;
  border-radius: 0 0 var(--cd-radius-md) var(--cd-radius-md);
  max-height: 380px;
  overflow-y: auto;
  display: none;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

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

.tag-search-item {
  padding: 0.4rem 0.75rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
  color: var(--cd-text);
}

.tag-search-item:hover {
  background-color: var(--cd-surface-sunken);
}

.tag-search-item.active {
  background-color: var(--cd-brand-fill-hover);
  color: var(--cd-text-on-brand);
}

.tag-search-item .tag-dot {
  width: 8px;
  height: 8px;
}

.tag-search-no-results {
  padding: 0.4rem 0.75rem;
  color: var(--cd-text-muted);
  font-size: 0.8125rem;
  font-style: italic;
}

/* CD-1715/CD-1759 — every reply-bar dropdown is portaled to <body> and
   fixed-positioned by dropdownPortal.js so it escapes the overflow:hidden clip
   on .ticket-show-left / .ticket-reply-bar (the macro preview was cut off at
   short viewport heights; the CC list, @-mention popup and Tags picker were cut
   off in the maximized composer). JS sets top/left — or bottom, when it flips
   up — width, and max-height; the box still scrolls internally past that
   height.

   `right`/`bottom` are reset because .tag-search-results stretches itself with
   `left: 0; right: 0` for the in-flow case, which would fight the fixed
   coordinates. The class carries the width too, since a portaled node no longer
   matches the descendant selectors that sized it (.ticket-reply-cc
   .tag-search-results) — that is why each call site passes an explicit width. */
.cd-dropdown-floating {
  position: fixed;
  right: auto;
  bottom: auto;
  z-index: 1080;
  /* A floating box can open either way, so it can't inherit a one-sided radius:
     .tag-search-results is squared at the top because it sits flush under its
     input, and .ticket-reply-cc squared it at the bottom for its upward variant.
     Detached from both, it is rounded on all four corners and bordered all
     round, which reads correctly whichever side JS puts it on. */
  border-radius: var(--cd-radius-md);
  border: 1px solid var(--cd-line);
}

/* Macro preview box (CD-909) — shown inside the picker dropdown when a macro
   is clicked; "Use this macro" runs the real apply. */
.macro-preview {
  padding: 0.5rem 0.75rem;
}

.macro-preview-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 0.4rem;
}

.macro-preview-title {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--cd-text);
}

.macro-preview-changes {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.15rem 0.75rem;
  font-size: 0.8125rem;
  margin-bottom: 0.5rem;
}

.macro-preview-change-key {
  color: var(--cd-text-muted);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  align-self: baseline;
}

.macro-preview-body {
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  padding: 0.5rem 0.6rem;
  font-size: 0.8125rem;
  max-height: 180px;
  overflow-y: auto;
  margin-bottom: 0.5rem;
}

.macro-preview-body p {
  margin-bottom: 0.35rem;
}

.macro-preview-body img {
  max-width: 100%;
}

.macro-preview-dropped {
  margin-bottom: 0.5rem;
}

.macro-preview-actions {
  display: flex;
  gap: 0.5rem;
  /* CD-1715 — on a short viewport the card is taller than the dropdown's
     clamped max-height, so an unpinned footer scrolls out of reach and "Use
     this macro" reads as cut off all over again. Stick it to the bottom of the
     scroll box (opaque, so body text can't show through underneath). */
  position: sticky;
  bottom: 0;
  background: var(--cd-surface-raised);
  padding-top: 0.4rem;
}

/* The reply picker renders this card inside the Quill editor toolbar, whose
   `.ql-snow .ql-toolbar button` rule (specificity 0,2,1) restyles buttons:
   width:28px, height:24px, float:left, AND background:none / border:none —
   the latter two make the white-on-primary "Use this macro" button invisible.
   Quill sets no !important, so restore normal Bootstrap button rendering from
   its own per-variant CSS variables. */
.macro-preview-actions .btn {
  width: auto !important;
  height: auto !important;
  float: none !important;
  flex: 0 0 auto;
  white-space: nowrap;
  padding: 0.25rem 0.5rem !important;
  background-color: var(--bs-btn-bg) !important;
  border: var(--bs-border-width, 1px) solid var(--bs-btn-border-color) !important;
}

/* CD-1715 — the rule above pins the background to the BASE token, so Bootstrap's
   hover state (which swaps in --bs-btn-hover-bg and flips the label to white)
   lost its fill: "Back" (btn-outline-secondary) rendered white-on-white and
   vanished under the cursor. Carry the hover/active tokens through the same
   !important so the filled hover state survives. */
.macro-preview-actions .btn:hover,
.macro-preview-actions .btn:focus-visible,
.macro-preview-actions .btn:active {
  background-color: var(--bs-btn-hover-bg) !important;
  border-color: var(--bs-btn-hover-border-color) !important;
}

/* CD-1715 — Bootstrap's stock outline-secondary label (#6c757d) only reaches
   3.46:1 on the dark surface, under the 4.5:1 AA bar for body text. Drive the
   resting label from the theme-aware muted token instead (6.1:1 dark, 5.8:1
   light). Rest state only — the hover/active rules above own the filled
   states, which already pass. */
.macro-preview-actions .btn-outline-secondary {
  --bs-btn-color: var(--cd-text-muted);
}

.tag-search-loading {
  display: flex;
  justify-content: center;
  padding: 0.75rem;
}

/* CD-569 — @-mention chip (in the editor and in rendered note bodies)
   CD-2119 — the tint is --cd-brand-soft, NOT --cd-brand-fill-hover. The -fill
   tokens are the solid brick that WHITE text sits on (navbar, primary button)
   and stay deep in dark mode for that reason, so brand-coloured ink on one is
   brick on brick: 1.29:1 light, 1.74:1 dark. -soft is the tint half of the pair
   --cd-brand ink is meant for, the same as .bg-apu-subtle, #sidebar
   .nav-link.active and .provenance-chip-agent. Measured by
   test/unit/public/css/mentionChipContrast.test.js. */
.mention {
  background-color: var(--cd-brand-soft);
  color: var(--cd-brand);
  border-radius: var(--cd-radius-sm);
  padding: 0 3px;
  font-weight: 500;
  white-space: nowrap;
}

.mention-search-item-email {
  color: var(--cd-text-muted);
  font-size: 0.75rem;
  margin-left: auto;
}

.tag-search-create {
  color: var(--cd-brand);
  font-weight: 500;
  border-bottom: 1px solid var(--cd-line);
}

.tag-search-create::before {
  content: "+";
  font-weight: 700;
  margin-right: 2px;
}

/* ─── Main Content ─── */
#main-content {
  padding: 1.5rem 2rem;
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  width: 100%;
  position: relative;
}

#public-main {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
}

/* When the floating AI help button (fixed, bottom-right ~70px tall) is shown,
   reserve scroll space below the ticket sidebar's last card (Team Sharing) so
   it can clear the button. Scoped to the sidebar — the FAB only overlaps the
   bottom-right column — so the left conversation/reply pane and other pages get
   no stray bottom whitespace. CD-541 / CD-589. */
body.ai-help-btn-visible .ticket-show-sidebar {
  padding-bottom: 5.5rem;
}

.flash-alert-overlay {
  position: absolute;
  top: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  width: auto;
  min-width: 200px;
  max-width: min(480px, calc(100% - 3rem));
  z-index: 20;
  margin: 0;
  /* Wraps rather than nowrap: `max-width` caps the box but nowrap refuses to
     break, so anything longer than ~60 characters ran straight out of the
     coloured panel and across the page — the admin editor's PeopleSoft sync
     warning already did, and CD-1846 added a second long message. `width: auto`
     still shrink-wraps a short message onto one line, so the neat pill shape
     that nowrap was protecting is unaffected. The mobile rule below already
     set `normal` for exactly this reason. */
  white-space: normal;
}

.tickets-page-header {
  position: sticky;
  top: 0;
  background: var(--cd-surface-raised);
  z-index: 10;
  padding: 1.5rem 2rem 0.75rem;
  margin: 0 -2rem 1rem;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}

#tickets-results.tickets-results--loading {
  opacity: 0.6;
  transition: opacity 0.15s ease-in;
}

.ajax-spinner { display: none; }

.tickets-results--loading .ajax-spinner,
.tickets-results--skeleton .ajax-spinner { display: inline-block; }

.tickets-page-header #page-view-title {
  position: relative;
  top: -0.1rem;
  padding-bottom: 3px;
}

.tickets-page-header button#editViewBtn {
  margin-left: 0.5rem;
  font-size: 1rem;
}

/* The view header's icon is set in `h3`; without this it inherits the header's
   line-height and pushes the row taller than the title beside it. Was an inline
   `style="line-height:1"` until the `iconTag` helper took over the markup. */
.tickets-page-header #editViewIcon {
  line-height: 1;
}

.tickets-page-header #editViewBtn {
  opacity: 0;
  transition: opacity 0.15s ease-in-out;
}

.tickets-page-header:hover #editViewBtn,
.tickets-page-header #editViewBtn:focus-visible {
  opacity: 1;
}

/* ── Sprite icons (`cdi-*`) ──
   Emitted by the `iconTag` Handlebars helper / `viewIconEl()` for the handful of
   glyphs Bootstrap Icons doesn't carry. These rules exist so a sprite icon is
   INDISTINGUISHABLE from a `bi-*` font glyph at a call site: same 1em box, same
   baseline nudge, same `color:` tint. If they ever diverge, every template that
   sizes an icon would have to care which family it got — which is exactly the
   coupling the helper is there to remove.

   `fill: currentColor` is belt-and-braces: the symbols in sprite.svg already
   carry it, but a future icon from a set that doesn't would otherwise render a
   hardcoded black and ignore the view's color. */
/* The wrapper is an <i> so that bare element selectors — `#sidebar .nav-link i
   { font-size: 1.5rem; width: 30px }` and friends — reach a sprite icon exactly
   as they reach a font glyph. inline-block is what makes their `width` apply
   (Bootstrap Icons' own .bi sets the same). */
.cd-icon-slot {
  display: inline-block;
  line-height: 1;
}

/* 1em square, so it takes its size from whatever font-size the wrapper
   inherited — the same way a font glyph does. */
.cd-icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  fill: currentColor;
  flex-shrink: 0;
}

.icon-color-picker-popup {
  position: absolute;
  z-index: 1060;
  background: var(--cd-surface-raised);
  color: var(--cd-text);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  padding: 6px;
  width: 226px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.icon-color-picker-popup .edit-view-color-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 6px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--cd-line);
}

.icon-color-picker-popup .edit-view-color-label {
  font-size: 0.85rem;
  margin: 0;
  color: inherit;
}

.icon-color-picker-popup .edit-view-color-row input[type="color"] {
  width: 38px;
  height: 30px;
  padding: 2px;
  border: 1px solid var(--cd-line);
  background: var(--cd-surface-raised);
}

.icon-color-picker-popup .edit-view-icon-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

body:has(.tickets-page-header) #main-content {
  padding-top: 0 !important;
}

/* ─── Buttons ─── */

/* The resting fill and the hover fill were SWAPPED here: resting took
   --cd-brand-fill-hover and :hover below took --cd-brand-fill, so a hover made
   the button lighter instead of deeper. It rendered correctly only because
   tokens.css declares the same rule the right way round and is linked second.
   Same for the borders (--cd-focus / --cd-brand instead of the fill pair).
   Pure gap #2: real bugs, invisible because of stylesheet order. */
.btn-apu {
  background-color: var(--cd-brand-fill);
  border-color: var(--cd-brand-fill);
  color: var(--cd-text-on-brand);
  font-weight: 500;
  border-radius: var(--cd-radius-pill);
  padding: 0.45rem 1.1rem;
  transition: all var(--transition-speed) ease;
}

.btn-sm.btn-apu {
  padding: 0.25rem 0.7rem;
}

/* --cd-text-on-brand, not --cd-text-inverse: the latter flips to DARK ink in
   dark mode, which would put near-black text on the red fill.
   The shadow stays an rgba literal (there is no brand-shadow token, and two
   different alphas could not share one), but it was rgba(168, 53, 58) — the
   INCORRECT brick in decimal. Corrected to 157, 46, 52 = #9D2E34. */
.btn-apu:hover,
.btn-apu:focus {
  background-color: var(--cd-brand-fill-hover);
  border-color: var(--cd-brand-fill-hover);
  color: var(--cd-text-on-brand);
  box-shadow: 0 2px 8px rgba(157, 46, 52, 0.25);
}

.btn-apu:active {
  background-color: var(--cd-brand-active) !important;
  border-color: var(--cd-brand-active) !important;
}

.btn-apu:disabled {
  background-color: var(--cd-brand-fill);
  border-color: var(--cd-brand);
  color: rgba(255, 255, 255, 0.5);
  opacity: 0.65;
}

.btn-outline-apu {
  border-color: var(--cd-brand);
  color: var(--cd-brand);
  font-weight: 500;
  border-radius: var(--cd-radius-pill);
  padding: 0.45rem 1.1rem;
  transition: all var(--transition-speed) ease;
}

/* Same two corrections as .btn-apu:hover above. */
.btn-outline-apu:hover,
.btn-outline-apu:focus {
  background-color: var(--cd-brand-fill);
  border-color: var(--cd-brand);
  color: var(--cd-text-on-brand);
  box-shadow: 0 2px 8px rgba(157, 46, 52, 0.2);
}

/* --cd-text-on-inverse, not --cd-text-inverse. --cd-surface-inverse is dark in
   BOTH themes (#252425 / #242019) while --cd-text-inverse flips to #252425, so
   in dark mode this button's label was near-black on near-black — a live
   invisible-text bug, not a latent one. */
.btn-apu-dark {
  background-color: var(--cd-surface-inverse);
  border-color: var(--cd-text);
  color: var(--cd-text-on-inverse);
  font-weight: 500;
  border-radius: var(--cd-radius-pill);
}

/* Primitive, not --cd-surface-inverse: this button is dark in both themes. */
.btn-apu-dark:hover {
  background-color: var(--cd-slate);
  border-color: var(--cd-slate);
  color: var(--cd-text-on-inverse);
}

/* ─── Cards ─── */
.card {
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
  transition: box-shadow var(--transition-speed) ease;
  background-color: var(--cd-surface-raised);
}

.card:hover {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.card-header {
  background-color: var(--cd-surface-sunken);
  border-bottom: 1px solid var(--cd-line);
  font-weight: 600;
  font-size: 0.95rem;
  padding: 0.75rem 1.25rem;
}

/* ─── CougarDesk Observatory ───────────────────────────────────────────────
   The analytics landing is a decision map over the existing chart surfaces.
   All color roles stay on tokens so the same hierarchy survives dark mode. */
.observatory-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(13rem, 18rem);
  gap: var(--cd-space-6);
  align-items: end;
  padding: clamp(1.5rem, 4vw, 3.25rem);
  color: var(--cd-text-on-brand);
  background: linear-gradient(135deg, var(--cd-brand-active), var(--cd-brand-fill) 58%, var(--cd-surface-inverse));
  border: 1px solid var(--cd-brand-active);
  border-radius: var(--cd-radius-lg);
  box-shadow: var(--cd-shadow-lg);
}

.observatory-hero::after {
  position: absolute;
  z-index: -1;
  top: -55%;
  right: -10%;
  width: 28rem;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--cd-surface-on-brand) 20%, transparent), transparent 68%);
  content: "";
}

.observatory-hero__content {
  max-width: 52rem;
}

.observatory-eyebrow,
.observatory-section-kicker {
  display: inline-flex;
  gap: var(--cd-space-2);
  align-items: center;
  margin-bottom: var(--cd-space-2);
  font: var(--cd-type-eyebrow);
  letter-spacing: var(--cd-tracking-caps);
  text-transform: uppercase;
}

.observatory-section-kicker {
  color: var(--cd-brand);
}

.observatory-title {
  max-width: 46rem;
  margin-bottom: var(--cd-space-3);
  color: inherit;
  font: var(--cd-type-h1);
  letter-spacing: var(--cd-tracking-tight);
}

.observatory-lede {
  max-width: 48rem;
  margin-bottom: var(--cd-space-5);
  color: color-mix(in srgb, var(--cd-text-on-brand) 86%, transparent);
  font-size: var(--cd-text-md);
}

.observatory-hero__summary {
  display: grid;
  align-self: stretch;
  padding: var(--cd-space-3) var(--cd-space-4);
  background: color-mix(in srgb, var(--cd-surface-on-brand) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--cd-surface-on-brand) 26%, transparent);
  border-radius: var(--cd-radius-md);
  backdrop-filter: blur(8px);
}

.observatory-hero__summary span {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cd-space-4);
  padding: var(--cd-space-2) 0;
  border-bottom: 1px solid color-mix(in srgb, var(--cd-surface-on-brand) 18%, transparent);
  font-size: var(--cd-text-sm);
}

.observatory-hero__summary span:last-child {
  border-bottom: 0;
}

.observatory-hero__summary strong {
  font-size: var(--cd-text-xl);
}

.observatory-dashboard-card {
  --observatory-accent: var(--cd-success);
  overflow: hidden;
  border-top: 3px solid var(--observatory-accent);
  transition: box-shadow var(--transition-speed) ease, transform var(--transition-speed) ease;
}

.observatory-dashboard-card--baseline {
  --observatory-accent: var(--cd-warning);
}

.observatory-dashboard-card:hover {
  box-shadow: var(--cd-shadow-md);
  transform: translateY(-2px);
}

.observatory-dashboard-icon {
  display: inline-grid;
  width: 2.75rem;
  height: 2.75rem;
  place-items: center;
  color: var(--cd-brand);
  background: var(--cd-brand-soft);
  border-radius: var(--cd-radius-md);
  font-size: var(--cd-text-xl);
}

.observatory-status {
  display: inline-flex;
  gap: var(--cd-space-2);
  align-items: center;
  padding: var(--cd-space-1) var(--cd-space-2);
  color: var(--cd-success-fg);
  background: var(--cd-success-bg);
  border-radius: var(--cd-radius-pill);
  font-size: var(--cd-text-xs);
  font-weight: var(--cd-weight-semibold);
}

.observatory-status--baseline {
  color: var(--cd-warning-fg);
  background: var(--cd-warning-bg);
}

.observatory-status__dot {
  width: 0.45rem;
  height: 0.45rem;
  background: currentColor;
  border-radius: 50%;
}

.observatory-decision {
  padding: var(--cd-space-3);
  color: var(--cd-text);
  background: var(--cd-surface-sunken);
  border-left: 3px solid var(--observatory-accent);
  border-radius: var(--cd-radius-sm);
}

.observatory-decision span {
  display: block;
  margin-bottom: var(--cd-space-1);
  color: var(--cd-text-muted);
  font-size: var(--cd-text-2xs);
  font-weight: var(--cd-weight-bold);
  letter-spacing: var(--cd-tracking-caps);
  text-transform: uppercase;
}

.observatory-signal {
  color: var(--cd-text-muted);
  background: var(--cd-surface-sunken);
  border: 1px solid var(--cd-line);
  font-weight: var(--cd-weight-medium);
}

.observatory-baseline {
  display: flex;
  gap: var(--cd-space-2);
  align-items: flex-start;
  padding: var(--cd-space-3);
  color: var(--cd-warning-fg);
  background: var(--cd-warning-bg);
  border-radius: var(--cd-radius-sm);
  font-size: var(--cd-text-xs);
}

.observatory-baseline--ready {
  color: var(--cd-success-fg);
  background: var(--cd-success-bg);
}

/* Opportunity Inbox uses native details disclosures as evidence drawers. The
   summary remains a normal keyboard control; these rules only add hierarchy. */
.opportunity-finding {
  overflow: hidden;
  border-left: 4px solid var(--cd-info);
  box-shadow: var(--cd-shadow-sm);
}

.opportunity-finding__category {
  display: inline-block;
  margin-bottom: var(--cd-space-2);
  color: var(--cd-info-fg);
  font: var(--cd-type-eyebrow);
  letter-spacing: var(--cd-tracking-caps);
  text-transform: uppercase;
}

.opportunity-finding__icon {
  display: inline-grid;
  flex: 0 0 auto;
  width: 2.75rem;
  height: 2.75rem;
  place-items: center;
  color: var(--cd-info-fg);
  background: var(--cd-info-bg);
  border-radius: var(--cd-radius-md);
  font-size: var(--cd-text-xl);
}

.opportunity-evidence {
  border-top: 1px solid var(--cd-line);
}

.opportunity-evidence > summary {
  padding: var(--cd-space-3) 0 0;
  color: var(--cd-brand);
  cursor: pointer;
  font-size: var(--cd-text-sm);
  font-weight: var(--cd-weight-semibold);
}

.opportunity-evidence[open] > summary {
  margin-bottom: var(--cd-space-3);
}

.opportunity-evidence__body {
  padding: var(--cd-space-4);
  background: var(--cd-surface-sunken);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
}

.opportunity-evidence__inputs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--cd-space-2);
}

.opportunity-evidence__inputs > div {
  padding: var(--cd-space-3);
  background: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-sm);
}

.opportunity-evidence__inputs dt {
  margin-bottom: var(--cd-space-1);
  color: var(--cd-text-muted);
  font-size: var(--cd-text-xs);
  font-weight: var(--cd-weight-medium);
}

.opportunity-evidence__inputs dd {
  margin: 0;
  color: var(--cd-text);
  font-weight: var(--cd-weight-semibold);
}

.opportunity-evidence__guardrails {
  padding: var(--cd-space-3);
  color: var(--cd-warning-fg);
  background: var(--cd-warning-bg);
  border-left: 3px solid var(--cd-warning);
  border-radius: var(--cd-radius-sm);
}

.opportunity-inbox__empty {
  background: var(--cd-surface-sunken);
  border-style: dashed;
}

.observatory-executive,
#observatoryDashboards,
#opportunity-inbox,
#data-trust,
#screens-attention,
#navigation-paths,
#workflow-xray,
#interaction-friction,
#feature-adoption,
#reliability {
  scroll-margin-top: calc(var(--chrome-offset) + var(--cd-space-4));
}

.product-surface-state {
  display: inline-flex;
  align-items: center;
  padding: var(--cd-space-1) var(--cd-space-2);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-pill);
  font-size: var(--cd-text-xs);
  font-weight: var(--cd-weight-semibold);
  line-height: 1;
}

.product-surface-state--ready {
  color: var(--cd-success-fg);
  background: var(--cd-success-bg);
  border-color: var(--cd-success);
}

.product-surface-state--stale,
.product-surface-state--backfilling {
  color: var(--cd-warning-fg);
  background: var(--cd-warning-bg);
  border-color: var(--cd-warning);
}

.product-surface-state--refreshing {
  color: var(--cd-info-fg);
  background: var(--cd-info-bg);
  border-color: var(--cd-info);
}

.product-surface-state--error {
  color: var(--cd-danger-fg);
  background: var(--cd-danger-bg);
  border-color: var(--cd-danger);
}

.product-surface-state--unknown,
.product-surface-state--never_populated {
  color: var(--cd-text-muted);
  background: var(--cd-surface-sunken);
}

.product-surface-trust-table th {
  white-space: nowrap;
}

.observatory-briefing {
  overflow: hidden;
  border-color: var(--cd-line-strong);
}

.observatory-briefing__meta {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--cd-space-2) var(--cd-space-4);
  padding: var(--cd-space-3);
  background: var(--cd-surface-sunken);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-sm);
  font-size: var(--cd-text-sm);
}

.observatory-briefing__metric,
.observatory-briefing__finding,
.observatory-briefing__highlight {
  padding: var(--cd-space-3);
  background: var(--cd-surface-sunken);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-sm);
}

.observatory-briefing__finding dl > div {
  display: grid;
  grid-template-columns: minmax(8rem, 1fr) minmax(0, 2fr);
  gap: var(--cd-space-2);
  padding: var(--cd-space-1) 0;
  border-bottom: 1px solid var(--cd-line);
}

.observatory-briefing__finding dt {
  color: var(--cd-text-muted);
  font-weight: var(--cd-weight-medium);
  text-transform: capitalize;
}

.observatory-briefing__finding dd {
  margin: 0;
  overflow-wrap: anywhere;
}

@media (max-width: 767.98px) {
  .observatory-hero {
    grid-template-columns: 1fr;
  }

  .observatory-dashboard-card .border-top {
    align-items: stretch !important;
    flex-direction: column;
  }

  .observatory-dashboard-card form,
  .observatory-dashboard-card form .btn {
    width: 100%;
  }

  .opportunity-evidence__inputs {
    grid-template-columns: 1fr;
  }

  .observatory-briefing__meta,
  .observatory-briefing__finding dl > div {
    grid-template-columns: 1fr;
  }
}

@media (prefers-reduced-motion: reduce) {
  .observatory-dashboard-card {
    transition: none;
  }

  .observatory-dashboard-card:hover {
    transform: none;
  }
}

/* Stat cards */
.stat-card {
  border-left: 4px solid transparent;
  transition: all var(--transition-speed) ease;
  padding: 1.25rem;
  background-color: var(--cd-surface-raised);
  border-radius: var(--cd-radius-md);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
  margin-bottom: 0.5rem;
}

.stat-card.stat-new,
.stat-card.stat-card-purple {
  border-left-color: var(--cd-status-new-fg);
}

.stat-card.stat-open,
.stat-card.stat-card-red {
  border-left-color: var(--cd-brand);
}

.stat-card.stat-card-blue {
  border-left-color: var(--cd-status-pending-fg);
}

.stat-card.stat-on-hold,
.stat-card.stat-card-amber {
  border-left-color: var(--cd-warning);
}

.stat-card.stat-card-green {
  border-left-color: var(--cd-success);
}

.stat-card .stat-number {
  font-size: 2rem;
  font-weight: 700;
  line-height: 1;
  margin-bottom: 0.25rem;
}

.stat-card .stat-label {
  font-size: 0.8125rem;
  color: var(--cd-text-muted);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ─── Stat card links ─── */
a.stat-card-link {
  display: block;
  color: inherit;
  text-decoration: none;
}

a.stat-card-link:hover {
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.07);
  transform: translateY(-1px);
}

.stat-card .stat-card-cta {
  font-size: 0.72rem;
  font-weight: 500;
  opacity: 0;
  transition: opacity 0.15s;
  margin-top: 0.5rem;
  color: var(--cd-text-muted);
}

a.stat-card-link:hover .stat-card-cta {
  opacity: 1;
}

/* ─── Dashboard For You section ─── */
.for-you-most-active-label {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--cd-warning-fg);
  text-transform: uppercase;
  letter-spacing: 0.09em;
}

.for-you-featured {
  background: var(--cd-surface-raised);
  border-left: 4px solid var(--cd-warning);
  border-top: 1px solid var(--bs-border-color);
  border-right: 1px solid var(--bs-border-color);
  border-bottom: 1px solid var(--bs-border-color);
  border-radius: var(--cd-radius-md);
  padding: 0.875rem 1.125rem;
  transition: box-shadow 0.15s;
}

.for-you-featured:hover {
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.07);
}

.for-you-section-label {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--cd-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.09em;
}

.for-you-agent-avatar {
  width: 28px;
  height: 28px;
  background: var(--cd-brand-fill);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.625rem;
  font-weight: 700;
  color: white;
}

.for-you-table thead th {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--cd-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  background: var(--cd-surface-sunken);
  border-bottom: 1px solid var(--bs-border-color);
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

.for-you-footer {
  padding: 0.8rem 1rem;
  font-size: 0.8rem;
  color: var(--cd-text-muted);
  border-top: 1px solid var(--bs-border-color);
  text-align: center;
}

/* ─── Dashboard zero state ─── */
.dashboard-zero-state .card-body {
  padding: 3.5rem 2.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.zero-state-illustration {
  width: 180px;
  height: 120px;
  background: linear-gradient(145deg, var(--cd-warm-50) 0%, var(--cd-warm-100) 100%);
  border-radius: var(--cd-radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.9;
}

.zero-state-actions {
  display: flex;
  gap: 0.875rem;
  flex-wrap: wrap;
  justify-content: center;
  margin: 2rem 0 1.5rem;
}

.zero-action-card {
  flex: 1;
  min-width: 160px;
  max-width: 200px;
  background: var(--cd-surface-raised);
  border: 1.5px solid var(--bs-border-color);
  border-radius: var(--cd-radius-md);
  padding: 1.125rem 1rem;
  text-align: left;
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s, box-shadow 0.15s;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.zero-action-card:hover {
  border-color: var(--cd-brand);
  box-shadow: 0 2px 10px rgba(139, 28, 28, 0.08);
  color: inherit;
}

.zero-action-card i {
  font-size: 1.25rem;
  color: var(--cd-brand);
  margin-bottom: 0.375rem;
}

.zero-action-card strong {
  font-size: 0.8125rem;
  color: var(--cd-text);
}

.zero-action-card span {
  font-size: 0.75rem;
  color: var(--cd-text-muted);
  line-height: 1.5;
}

/* ─── Dashboard Widgets ─── */
.dash-widget {
  transition: transform 0.15s ease, opacity 0.15s ease;
}

.dash-widget.dragging {
  opacity: 0.4;
  transform: scale(0.97);
}

.dash-widget.drag-over .dash-widget-card {
  outline: 2px dashed var(--cd-brand);
  outline-offset: 2px;
}

.dash-widget-card {
  transition: box-shadow 0.2s ease;
}

.dash-widget-card:hover {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.drag-handle {
  cursor: grab;
  font-size: 1rem;
  opacity: 0.3;
  transition: opacity 0.15s ease;
}

.dash-widget:hover .drag-handle {
  opacity: 0.7;
}

.drag-handle:active {
  cursor: grabbing;
  opacity: 1;
}

.widget-ticket-list .list-group-item {
  border-left: 3px solid transparent;
  transition: border-color 0.15s ease;
}

.widget-ticket-list .list-group-item:hover {
  border-left-color: var(--cd-brand);
  background-color: var(--cd-surface-sunken);
}

/* A widget holds a full page of its view (CD-1742), so the ticket region scrolls
   inside the card rather than stretching it down the page. Capped in `rem` (not
   a row count) so the cap holds however tall the rows render; `dvh` keeps a
   short viewport from needing two scrolls to get past one widget. */
.widget-body-scroll {
  max-height: min(24rem, 55dvh);
  overflow-y: auto;
}

/* Pager sits below the scroll region and stays put while it scrolls. */
.widget-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

/* ── Task list P1 fields ── */
.task-drag-handle {
  cursor: grab;
  color: var(--cd-text-muted);
  opacity: 0.4;
  transition: opacity 0.15s ease;
  flex-shrink: 0;
}

.task-row:hover .task-drag-handle {
  opacity: 0.8;
}

.task-drag-handle:active {
  cursor: grabbing;
  opacity: 1;
}

.task-row--dragging {
  opacity: 0.4;
}

.task-row--drag-over > div:first-child {
  /* Was var(--bs-primary, #0d6efd) — a blue drop indicator in a brick app. */
  box-shadow: inset 0 2px 0 0 var(--cd-brand);
}

.task-due-date {
  width: 130px;
  flex-shrink: 0;
}

.task-due-overdue {
  color: var(--bs-danger) !important;
  border-color: var(--bs-danger) !important;
}

.task-assignee-btn {
  white-space: nowrap;
  font-size: 0.8125rem;
  flex-shrink: 0;
  max-width: 90px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.task-notes-btn {
  flex-shrink: 0;
}

.task-notes-wrap textarea {
  resize: vertical;
  min-height: 56px;
}

/* ─── Tables ─── */
.table {
  font-size: 0.875rem;
}

.table thead th {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--cd-text-subtle);
  font-weight: 600;
  border-bottom: 2px solid var(--cd-line);
  padding: 0.75rem;
  white-space: nowrap;
}

.table tbody td {
  padding: 0.7rem 0.75rem;
  vertical-align: middle;
  border-bottom: 1px solid var(--cd-line);
}

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

/* Row-reference number gutter (CD-724). Add class `row-numbered` to a table and
   an empty `<td class="row-num">` as the first cell of each row; the header gets
   `<th class="row-num">#</th>`. A pure CSS counter numbers the rows — display:none
   rows (hidden by the client text filter) don't increment, so visible rows always
   read 1..N top-to-bottom. Presentational only; not tied to row data. */
.row-numbered tbody { counter-reset: rownum; }
.row-numbered tbody > tr { counter-increment: rownum; }
.row-numbered th.row-num,
.row-numbered td.row-num {
  width: 1%;
  text-align: right;
  color: var(--cd-text-muted);
  white-space: nowrap;
}
.row-numbered td.row-num::before { content: counter(rownum); }

.cursor-pointer {
  cursor: pointer;
}

.cursor-pointer:hover {
  background-color: var(--cd-surface-hover);
}

/* ─── Badges ─── */
.badge {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 0.35em 0.65em;
  border-radius: var(--cd-radius-sm);
}

/* Badge ink is mode-STABLE, because the fill is: --cd-text-inverse would flip to
   dark ink and go dark-on-color in dark mode. That is why these were literal
   #fff — but the roles have tokens now (--cd-*-on, --cd-text-on-brand), which
   are pinned in both themes and say WHY they don't flip.
   .badge-{success,warning,danger,info,primary} are ALSO defined in tokens.css,
   which is linked after this file and therefore wins. Both copies are kept in
   agreement on purpose rather than deleting these: whichever file loads first,
   the badge renders the same, so the pair does not depend on link order. */
.badge-new {
  background-color: var(--cd-status-new-solid);
  color: var(--cd-status-new-on);
}

.badge-primary {
  background-color: var(--cd-brand-fill);
  color: var(--cd-text-on-brand);
}

.badge-info {
  background-color: var(--cd-info);
  color: var(--cd-info-on);
}

/* White on #C08A2D is ~3.1:1 — under AA at badge size. See --cd-warning-on. */
.badge-warning {
  background-color: var(--cd-warning);
  color: var(--cd-warning-on);
}

.badge-success {
  background-color: var(--cd-success);
  color: var(--cd-success-on);
}

.badge-secondary {
  background-color: var(--cd-text-muted);
  color: var(--cd-text-on-brand);
}

.badge-danger {
  background-color: var(--cd-danger);
  color: var(--cd-danger-on);
}

/* Per-user opt-out of priority color coding (My Profile → Preferences).
   Neutralizes only priority badges to a single grey; labels stay readable. */
html.no-priority-colors .badge-priority {
  background-color: var(--cd-text-muted);
  color: var(--cd-text-on-brand);
}

/* ─── Message provenance chips (APU-38921, variant A: pill chips) ─── */
.provenance-chip {
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 0.2em 0.65em;
  border-radius: var(--cd-radius-pill); /* pill, overriding the square .badge default */
  vertical-align: middle;
}

/* Staff author — brand-tinted pill */
.provenance-chip-agent {
  background-color: var(--cd-brand-soft);
  color: var(--cd-brand-hover);
  border: 1px solid var(--cd-brand-soft);
}

/* Email origin — neutral pill carrying the sending address */
.provenance-chip-email {
  background-color: var(--cd-surface-sunken);
  color: var(--cd-text-muted);
  border: 1px solid var(--cd-line);
  font-weight: 500;
  max-width: 18rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: inline-block;
}

/* ─── Filter Builder ─── */
.fb-checkbox-group {
  max-height: 180px;
  overflow-y: auto;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--cd-radius-sm);
  padding: 0.5rem;
}

/* Separates pinned (checked) items from the rest of the group (CD-907) */
.fb-checkbox-group .fb-pinned-divider {
  border-top: 1px dashed var(--bs-border-color);
  margin: 0.375rem 0;
}

.fb-selected-count {
  font-size: 0.65rem;
  vertical-align: text-bottom;
}


.inline-edit-saving {
  opacity: 0.6;
  pointer-events: none;
}

.inline-edit-success {
  border-color: var(--cd-success) !important;
  background-color: rgba(25, 135, 84, 0.08) !important;
}

.inline-edit-error {
  border-color: var(--cd-danger) !important;
  background-color: rgba(220, 53, 69, 0.08) !important;
}

/* Table view: prevent cell wrapping so .table-responsive scrolls horizontally */
[data-view-table] .table td,
[data-view-table] .table th {
  white-space: nowrap;
}

/* Subscribe star column */
.ticket-star-col {
  width: 32px;
  padding: 0 0.25rem !important;
  text-align: center;
}

/* Sortable table headers (cardSort.js): the inner .th-sort-btn provides the
   cell padding, so zero the th. tableSorter.js reuses .sortable-th without a
   button, so scope this to headers that actually contain one — otherwise the
   first header cell loses its left padding and no longer lines up with its
   column. */
.sortable-th:has(.th-sort-btn) {
  padding: 0 !important;
}

.th-sort-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  width: 100%;
  background: transparent;
  border: none;
  padding: 0.5rem 0.5rem;
  font: inherit;
  font-weight: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
}

/* The control is a <button> on AJAX lists and an <a> on the admin Users list
   (where sorting is a navigation). An anchor arrives underlined and in the link
   color; neutralize both so the two read as one header treatment. */
a.th-sort-btn {
  text-decoration: none;
}

.th-sort-btn:hover {
  text-decoration: underline;
}

.th-sort-arrow {
  font-size: 0.75rem;
  opacity: 0.4;
  line-height: 1;
}

.th-sort-arrow::before {
  content: "\F575"; /* bi-arrow-down-up (neutral) */
}

.sortable-th[aria-sort="ascending"] .th-sort-arrow {
  opacity: 1;
}

.sortable-th[aria-sort="ascending"] .th-sort-arrow::before {
  content: "\F148"; /* bi-caret-up-fill */
}

.sortable-th[aria-sort="descending"] .th-sort-arrow {
  opacity: 1;
}

.sortable-th[aria-sort="descending"] .th-sort-arrow::before {
  content: "\F141"; /* bi-caret-down-fill */
}

.ticket-list-star {
  background: transparent;
  border: none;
  padding: 0.25rem;
  color: var(--cd-text-subtle);
  cursor: pointer;
  line-height: 1;
  transition: color 0.15s;
}

.ticket-list-star:hover,
.ticket-list-star.subscribed {
  color: var(--cd-warning);
}

.ticket-card-field-star {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 0.25rem;
  border-right: 1px solid var(--cd-line);
  min-width: unset !important;
}

/* ─── Ticket Preview Panel ─── */
#ticketPreviewPanel .offcanvas-body {
  overflow-y: auto;
}

#ticketPreviewPanel .ticket-show-layout {
  min-height: auto;
}

#ticketPreviewPanel .ticket-show-header {
  position: static;
}

#ticketPreviewPanel .ticket-reply-bar {
  position: static;
}

/* ─── Right panel section labels ─── */
.details-section-label {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cd-text-muted);
  margin-bottom: 0.4rem;
}

/* ─── Forms ─── */
.form-label {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--cd-text);
  margin-bottom: 0.35rem;
}

/* The ring was rgba(168, 53, 58, 0.15) — #A8353A in decimal, the incorrect
   brick again, and the THIRD copy of it in this file (login gradient,
   .ts-control, here). rgba() literals are invisible to the hex guard. */
.form-control:focus,
.form-select:focus {
  border-color: var(--cd-focus);
  box-shadow: 0 0 0 0.2rem var(--cd-focus-ring);
}

.form-control {
  border-radius: var(--cd-radius-sm);
  border: 1px solid var(--cd-line);
  font-size: 0.9rem;
  padding: 0.5rem 0.75rem;
}

.form-select {
  border-radius: var(--cd-radius-sm);
  border: 1px solid var(--cd-line);
  font-size: 0.9rem;
}

/* ─── Alerts ─── */
.alert {
  border-radius: var(--cd-radius-md);
  font-size: 0.875rem;
  border: none;
}

/* ─── Login Page ─── */
/* Brand gradient — shared verbatim with .landing-hero / .kb-hero below.
   Stops come from the PRIMITIVE tier (--cd-brick-*, --cd-slate), which has no
   dark-mode override, because this surface must stay dark in BOTH themes. The
   semantic accents would be wrong here: --cd-brand-active lightens to #F0A6A9
   in dark, which would turn the middle stop pale pink. */
.login-wrapper {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(
    135deg,
    var(--cd-brick-700) 0%,
    var(--cd-brick-900) 50%,
    var(--cd-slate) 100%
  );
  padding: 2rem;
}

.login-card {
  width: 100%;
  max-width: 420px;
  border: none;
  border-radius: var(--cd-radius-md);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
  overflow: hidden;
}

.login-card .login-header {
  background: var(--cd-surface-raised);
  padding: 2.5rem 2rem 1.5rem;
  text-align: center;
  border-bottom: 3px solid var(--cd-brand);
}

.login-card .login-header .logo-mark {
  width: 180px;
  margin-bottom: 0.75rem;
}

.login-card .login-header h2 {
  font-size: 1.1rem;
  font-weight: 500;
  color: var(--cd-text-muted);
  margin: 0;
}

.login-card .login-body {
  padding: 2rem;
  background: var(--cd-surface-raised);
}

.login-card .login-footer {
  background: var(--cd-surface-sunken);
  padding: 1rem 2rem;
  text-align: center;
  font-size: 0.8rem;
  color: var(--cd-text-subtle);
  border-top: 1px solid var(--cd-line);
}

.login-divider {
  display: flex;
  align-items: center;
  margin: 1.25rem 0;
  color: var(--cd-text-subtle);
  font-size: 0.8rem;
}

.login-divider::before,
.login-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--cd-line);
}

.login-divider span {
  padding: 0 0.75rem;
}

/* ─── Landing Page ─── */
/* The public landing hero and the KB hero (`.kb-hero`, kb.css) are the SAME
   brand surface — only their layout/margins differ, so the paint is shared
   here rather than forked into kb.css. Same primitive-tier gradient as
   .login-wrapper above; see the note there for why not the semantic accents.
   Ink is --cd-text-on-brand (pinned #FFFFFF in both themes), NOT
   --cd-text-inverse, which flips to dark ink in dark mode (see the
   design-token rules in cougardesk-web/CLAUDE.md). */
.landing-hero,
.kb-hero {
  background: linear-gradient(
    135deg,
    var(--cd-brick-700) 0%,
    var(--cd-brick-900) 50%,
    var(--cd-slate) 100%
  );
  color: var(--cd-text-on-brand);
}

.landing-hero {
  padding: 4rem 0 5rem;
  margin: -1.5rem -0.75rem 0;
  /* Extend to full width within the container layout */
  margin-left: calc(-50vw + 50%);
  margin-right: calc(-50vw + 50%);
  padding-left: 1rem;
  padding-right: 1rem;
}

.landing-hero h1,
.kb-hero h1 {
  color: var(--cd-text-on-brand);
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
}

.landing-hero .lead,
.kb-hero .lead {
  color: rgba(255, 255, 255, 0.85);
}

.landing-hero .form-control,
.kb-hero .form-control {
  border: none;
  font-size: 1rem;
  padding: 0.75rem 1.25rem;
  border-radius: var(--cd-radius-pill) 0 0 var(--cd-radius-pill);
}

.landing-hero .form-control:focus,
.kb-hero .form-control:focus {
  box-shadow: 0 0 0 0.25rem rgba(255, 255, 255, 0.25);
}

/* Hero search button. The SHAPE is shared; the fill deliberately is not —
   the landing keeps a white button with brand text, the KB uses the solid
   brand button (white on red). Keep both in this one place so the shape
   can't drift. */
.landing-hero .btn-light,
.kb-hero .btn-apu {
  border-radius: 0 var(--cd-radius-pill) var(--cd-radius-pill) 0;
  font-weight: 600;
  padding: 0.75rem 1.5rem;
}

/* -fill, not --cd-brand: this text sits on a white button in BOTH themes,
   and --cd-brand lightens in dark. */
.landing-hero .btn-light {
  color: var(--cd-brand-fill);
}

/* Primitive: the white pill sits on the dark hero gradient in both themes, so
   its hover tint must not flip either. */
.landing-hero .btn-light:hover {
  color: var(--cd-brand-fill-hover);
  background-color: var(--cd-bone);
}

/* Negative margin to overlap cards with hero */
.mt-n3 {
  margin-top: -2rem;
}

.landing-action-card,
.kb-action-card {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  border-radius: var(--cd-radius-md);
}

.landing-action-card:hover,
.kb-action-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1) !important;
}

.landing-action-card .card-title,
.kb-action-card .card-title {
  color: var(--cd-text);
}

.landing-action-card .card-text,
.kb-action-card .card-text {
  color: var(--cd-text-muted);
}

@media (prefers-reduced-motion: reduce) {
  .landing-action-card,
  .kb-action-card {
    transition: none;
  }
  .landing-action-card:hover,
  .kb-action-card:hover {
    transform: none;
  }
}

/* ─── Ticket Detail ─── */
.ticket-description {
  /* No `white-space: pre-wrap` here: it makes the newlines that HTML emails
     (e.g. Formstack notifications) put between every <tr>/<td> render as visible
     blank lines, blowing table layouts apart — the description rendered ~4x
     taller than it should (CD-862). Plain-text bodies keep their own pre-wrap
     via the `.plain-text-body` wrapper in renderTicketBody, so line breaks
     there are still preserved. */
  word-wrap: break-word;
  line-height: 1.7;
}

/* Paragraph-spacing parity: rendered message body ⇄ the reply/description
   composer. The Quill editor forces `.ql-editor p { margin: 0 }` (see
   quill.snow.css), so what an agent types is tight and a blank line is exactly
   one empty `<p><br></p>`. When that same HTML is displayed here, Bootstrap
   Reboot's `p { margin-bottom: 1rem }` kicks in and every paragraph — including
   the empty blank-line ones — gains a margin, so the identical content reads
   with roughly double the vertical spacing the composer showed. Zero the
   paragraph margin so the displayed body matches the composer 1:1; agents get
   extra spacing by adding a blank line, exactly as in the editor. Scoped to
   these two body containers so nothing else on the page is affected. */
.comment-display p,
.ticket-description p {
  margin: 0;
}

/* Internal note styling — same amber family as .comment-card-internal below */
.bg-light-yellow {
  background-color: var(--cd-warning-bg) !important;
  border-left: 3px solid var(--cd-warning);
}

/* ─── In-flight reply bubble (CD-1873) ───
   The reply is painted into the thread the moment Send is pressed, but a staff
   reply is rendered server-side (merge fields, sanitiser), so it is shown as
   pending until the real one replaces it. Dimmed rather than skeletonised: the
   text IS the agent's, and they should be able to read it back. */
.comment-pending {
  opacity: 0.6;
}

/* The spinner in the "Sending…" line is exempt from the global 1s spinner
   delay (main.css holds .spinner-border at opacity:0 for the first second) for
   the same reason button spinners are: it confirms the click registered, and a
   send usually resolves well inside that second. */
.comment-pending .spinner-border {
  opacity: 1;
  animation: none;
}

/* ─── Comment Collapse Toggle ─── */
.comment-collapse-toggle i {
  transition: transform 0.2s ease;
}

.comment-collapse-toggle.collapsed i {
  transform: rotate(-90deg);
}

/* Preview line shown when message is collapsed; hidden when expanded */
.comment-preview:has(+ .collapse.show),
.comment-preview:has(+ .collapsing) {
  display: none;
}

/* CD-1792: the collapsed preview line is `.text-truncate` (white-space:nowrap)
   inside a `.flex-grow-1` content column. Bootstrap's `.flex-grow-1` leaves the
   flex child at min-width:auto, whose min-content width is the FULL nowrap line —
   so on a narrow/resized window the child refuses to shrink and the preview text
   spills past the card edge instead of ellipsizing (the clip never engages).
   Letting the flex child shrink to 0 restores text-truncate, same as the
   `.ticket-tab-title` pattern above. Only the collapsible row carries a preview. */
.comment-collapsible > .flex-grow-1 {
  min-width: 0;
}

/* A collapsed message is click-to-expand anywhere (CD-889); cursor hints it.
   Once expanded the body is selectable text, so drop the affordance. */
.comment-collapsible:has(.collapse:not(.show)) {
  cursor: pointer;
}

/* ─── Comment Card Modes ─── */

/* Internal = the warning (amber) family, public = the info (blue) family. Both
   were Bootstrap hexes: #FFC107 / #0D6EFD with #FFFDE7 / #FFF8E1 / #F0F7FF
   tints, none of which had a dark-mode counterpart — so an internal note kept
   its near-white amber tint on the dark canvas.
   The card and its header both take --cd-*-bg. In light that flattens a
   two-hex-step two-tone (#FFFDE7 vs #FFF8E1) nobody could see; in dark the
   token is an rgba() tint, so painting the nested header stacks the alpha and
   the two-tone comes back on its own. */
.comment-card-internal {
  border-left: 4px solid var(--cd-warning);
  background-color: var(--cd-warning-bg);
}

.comment-card-internal .card-header {
  background-color: var(--cd-warning-bg);
}

.comment-card-public {
  border-left: 4px solid var(--cd-info);
}

.comment-card-public .card-header {
  background-color: var(--cd-info-bg);
}

/* Note toggle: inactive state — amber border, muted text */
.btn-outline-warning.btn-note-inactive {
  color: var(--cd-text-muted);
  border-color: var(--cd-warning);
}

/* --cd-warning-on, not #000: see the note beside that token in tokens.css. */
.btn-outline-warning.btn-note-inactive:hover {
  color: var(--cd-warning-on);
  background-color: var(--cd-warning);
  border-color: var(--cd-warning);
}

/* ─── Avatar ─── */
.avatar {
  font-weight: 600;
  font-size: 0.8125rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background-color: var(--cd-brand-soft);
  color: var(--cd-brand);
  width: 32px;
  height: 32px;
}

/* ─── Ticket action toolbar ─── */
/* Icon-only buttons (e.g. Print) render as a square: width forced to the
   btn-sm height (line-height 1.5 × .875rem font + .5rem vertical padding +
   2px border), since aspect-ratio alone leaves the width at icon size. */
.btn-square {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: calc(1.5 * 0.875rem + 0.5rem + 2px);
  padding-left: 0;
  padding-right: 0;
}

/* ─── Subscriber avatar stack (in the ticket action toolbar) ─── */
.subscriber-bar {
  min-height: 32px;
}

/* Keep the subscribe control off the toolbar's right edge. */
.subscriber-bar .subscriber-add-wrapper {
  padding-right: 0.3rem;
}

.avatar-stack {
  display: inline-flex;
  align-items: center;
}

.avatar-stack-item {
  position: relative;
  transition: margin-left 0.15s ease;
}

/* Overlap every avatar after the first; hovering one lifts it above the rest. */
.avatar-stack-item:not(:first-child) {
  margin-left: -10px;
}

.avatar-stack-item:hover {
  z-index: 3;
}

.subscriber-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  /* A cut-out ring that separates overlapping avatars, so it wants the CARD
     colour, not ink. It was --cd-text-inverse, which happened to look right in
     both themes by coincidence (#FFFFFF ≈ the light card, #252425 ≈ the dark
     one) — a coincidence that breaks the moment either surface is retuned.
     --cd-surface-raised is what it actually means. */
  border: 2px solid var(--cd-surface-raised);
  background-color: hsl(var(--avatar-hue, 350), 45%, 42%);
  color: var(--cd-text-on-brand);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.3px;
  cursor: default;
  user-select: none;
}

.subscriber-avatar--overflow {
  background-color: var(--cd-text-muted);
  font-size: 0.7rem;
}

/* Per-avatar remove control, revealed on hover. */
.avatar-remove {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 16px;
  height: 16px;
  padding: 0;
  line-height: 1;
  border: none;
  border-radius: 50%;
  background-color: var(--cd-brand-fill);
  color: var(--cd-text-on-brand);
  font-size: 0.7rem;
  font-weight: 700;
  display: none;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.avatar-stack-item:hover .avatar-remove {
  display: inline-flex;
}

.subscriber-add-wrapper {
  position: relative;
  margin-left: 8px;
}

.avatar-stack-add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 50%;
  border: 1px dashed var(--cd-line-strong);
  background-color: transparent;
  color: var(--cd-text-muted);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.avatar-stack-add:hover,
.avatar-stack-add[aria-expanded="true"] {
  border-color: var(--cd-brand);
  color: var(--cd-brand);
  background-color: var(--cd-brand-soft);
}

.subscriber-add-popover {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 1050;
  width: 260px;
  padding: 8px;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  background-color: var(--cd-surface-raised);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

/* ─── Presence Indicator ─── */
.presence-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--cd-success);
  margin-right: 4px;
  animation: pulse 2s infinite;
}

@keyframes pulse {

  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.4;
  }
}

/* ─── Page Header ─── */
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--cd-line);
}

.page-header h1 {
  font-size: 1.5rem;
  margin-bottom: 0;
}

/* ─── Error Pages ─── */
.error-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
  text-align: center;
}

.error-page .error-code {
  font-size: 6rem;
  font-weight: 800;
  color: var(--cd-brand);
  line-height: 1;
  opacity: 0.3;
}

.error-page h2 {
  font-size: 1.5rem;
  margin-top: 0.5rem;
  margin-bottom: 0.75rem;
}

.error-page p {
  color: var(--cd-text-muted);
  max-width: 400px;
  margin-bottom: 1.5rem;
}

/* ─── Footer ─── */
/* Footer paint, shared with the KB's own footer (`.kb-footer`, kb.css) — same
   dark bar, different layout, so only the colours live here.
   The greys were literals because this surface is dark in BOTH themes and the
   inverse text token flips to dark ink — correct reasoning, but the PRIMITIVE
   ramp says the same thing without a literal: --cd-warm-* has no dark override,
   so it cannot flip either. Deliberately not --cd-text-subtle, which does flip
   and lands at 4.3:1 here, just under AA; --cd-warm-400 is ~6.9:1 on
   --cd-surface-inverse and the link grey ~8.4:1. */
.app-footer,
.kb-footer {
  background-color: var(--cd-surface-inverse);
  color: var(--cd-warm-400);
}

.app-footer {
  font-size: 0.8rem;
  padding: 1rem 0;
  text-align: center;
  margin-top: auto;
}

.app-footer a,
.kb-footer a {
  color: var(--cd-warm-300);
}

/* Footer sits on --cd-surface-inverse, dark in both themes — so hovering a link
   in dark mode used to turn it near-black and effectively erase it. */
.app-footer a:hover {
  color: var(--cd-text-on-inverse);
}

/* ─── Utility Classes ─── */
.text-apu-red {
  color: var(--cd-brand) !important;
}

.bg-apu-red {
  background-color: var(--cd-brand-fill) !important;
}

.text-apu-maroon {
  color: var(--cd-brand) !important;
}

.border-apu {
  border-color: var(--cd-brand) !important;
}

/* ─── Pagination ─── */
.page-link {
  color: var(--cd-brand);
  border: 1px solid var(--cd-line);
  font-size: 0.875rem;
}

.page-link:hover {
  color: var(--cd-brand-hover);
  background-color: var(--cd-brand-soft);
  border-color: var(--cd-focus);
}

.page-item.active .page-link {
  background-color: var(--cd-brand-fill);
  border-color: var(--cd-brand);
  color: var(--cd-text-on-brand);
}

/* ─── Responsive ─── */
@media (max-width: 991px) {
  #main-content {
    padding: 1rem !important;
  }

  .flash-alert-overlay {
    top: 1rem;
    max-width: min(480px, calc(100% - 2rem));
    white-space: normal;
  }


  .page-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.75rem;
  }
}

@media (max-width: 576px) {

  .table-responsive .table th,
  .table-responsive .table td {
    font-size: 0.8rem;
    padding: 0.45rem;
  }

  .stat-card .stat-number {
    font-size: 1.5rem;
  }

  .login-card .login-body {
    padding: 1.5rem;
  }

  .login-card .login-header {
    padding: 1.5rem 1.5rem 1rem;
  }
}

/* ─── Sortable Table Headers ─── */
.sortable-th {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

.sortable-th:hover {
  background-color: var(--cd-surface-hover);
}

.sort-icon {
  font-size: 0.7rem;
  margin-left: 4px;
  opacity: 0.35;
  transition: opacity var(--transition-speed) ease;
  vertical-align: middle;
}

.sortable-th:hover .sort-icon {
  opacity: 0.6;
}

.sortable-th[data-sort-dir="asc"] .sort-icon,
.sortable-th[data-sort-dir="desc"] .sort-icon {
  opacity: 1;
  color: var(--cd-brand);
}

/* ─── Ticket Tab Bar ─── */
.ticket-tab-bar {
  background-color: var(--cd-surface-raised);
  /* Top gap matches the gap between tabs, so a tab never runs into the navbar.
     min-height is inert at present (a tab measures ~45px) but kept as a floor. */
  padding: var(--tab-gap) 1rem 0;
  min-height: 42px;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  max-width: 100%;
}

.ticket-tabs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: var(--tab-gap);
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
}

/* Soft fade instead of a hard clip when tabs overflow on the right
   (class toggled by ticketTabs.js alongside the scroll arrows). */
.ticket-tabs.tabs-fade-right {
  /* #000 in a mask is OPACITY, not colour — black means "keep this pixel".
     Substituting a theme token would change the fade, and in dark mode a light
     token would erase the element. Never tokenize a mask stop. The marker has
     to repeat per declaration: check-css-tokens.js deliberately lets a
     comment-line marker cover only the line directly below it. */
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent); /* tokens-ok: mask opacity */
  mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent); /* tokens-ok: mask opacity */
}

.ticket-tab {
  flex: 1 1 auto;
  min-width: 116px;
  position: relative; /* so the active/hovered tab can raise itself with z-index */
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--cd-text-muted);
  background-color: var(--cd-surface-sunken);
  border: 1px solid var(--cd-line);
  border-bottom: none;
  border-radius: var(--cd-radius-md) var(--cd-radius-md) 0 0;
  cursor: pointer;
  overflow: hidden;
  max-width: 220px;
  transition: background-color var(--transition-speed) ease, color var(--transition-speed) ease,
    min-width var(--transition-speed) ease, margin-left var(--transition-speed) ease;
  user-select: none;
}

.ticket-tab:hover {
  background-color: var(--cd-surface-hover);
  color: var(--cd-text);
  z-index: 3;
}

.ticket-tab.active {
  background-color: var(--cd-surface-raised);
  color: var(--cd-brand);
  border-color: var(--cd-line);
  font-weight: 600;
  z-index: 2;
  /* ponytail: the lift reads on the tab's left/right (over its neighbours) but is
     clipped above to --tab-gap px, because .ticket-tab-bar and .ticket-tabs are both
     overflow:hidden — the <ul> is the scroll container updateArrows() drives and the
     tabs-fade-right mask needs. Unclipping it means giving up both; not worth it. */
  box-shadow: 0 0 6px rgba(0, 0, 0, 0.14);
}

/* ─── Tab spacing tiers (driven by --tab-spacing) ───
   Below 50%: tabs may overlap, but only once they genuinely don't fit —
   .tabs-overflow is toggled by updateArrows() in ticketTabs.js. */
.ticket-tabs.tabs-overflow .ticket-tab {
  margin-left: max(-14px, min(0px, calc((var(--tab-spacing) - 50) * 0.5px)));
}

/* Below 25%: unselected tabs shrink to just their ticket number. The active tab
   stays full width, and hovering a shrunken tab restores it. .tabs-compact is
   toggled by ticketTabs.js — the only place the 25 threshold lives. */
.ticket-tabs.tabs-compact .ticket-tab {
  min-width: 64px;
}

.ticket-tabs.tabs-compact .ticket-tab .ticket-tab-title {
  display: none;
}

/* A shared ticket-number prefix (e.g. "APU-000" when every open tab is APU-000xx)
   carries no information in compact mode — hide it so the differing digits lead.
   The prefix span is only emitted when renderTabs() finds a common prefix. */
.ticket-tabs.tabs-compact .tab-num-prefix {
  display: none;
}

.ticket-tabs.tabs-compact .ticket-tab.active,
.ticket-tabs.tabs-compact .ticket-tab:hover {
  min-width: 116px;
  margin-left: 0;
}

.ticket-tabs.tabs-compact .ticket-tab.active .ticket-tab-title,
.ticket-tabs.tabs-compact .ticket-tab:hover .ticket-tab-title {
  display: block;
}

.ticket-tabs.tabs-compact .ticket-tab.active .tab-num-prefix,
.ticket-tabs.tabs-compact .ticket-tab:hover .tab-num-prefix {
  display: inline;
}

.ticket-tab-number {
  font-weight: 600;
  flex-shrink: 0;
  white-space: nowrap;
}

.ticket-tab-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1 1 auto;
  color: var(--cd-text-muted);
  font-weight: 400;
}

.tab-count-indicator {
  border-radius: var(--cd-radius-sm);
  padding: 2px 6px;
  font-size: 0.75rem;
  font-weight: 600;
  flex-shrink: 0;
  align-self: center;
  margin-right: 4px;
  cursor: pointer;
  user-select: none;
}

.tab-clean-menu {
  position: fixed;
  z-index: 1080;
  min-width: 180px;
  padding: 4px;
  background-color: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.tab-clean-menu-item {
  display: block;
  width: 100%;
  padding: 6px 10px;
  border: 0;
  border-radius: var(--cd-radius-sm);
  background: transparent;
  text-align: left;
  font-size: 0.85rem;
  color: var(--cd-text);
  cursor: pointer;
}

.tab-clean-menu-item:hover:not(:disabled) {
  background-color: var(--cd-surface-hover);
}

.tab-clean-menu-item:disabled {
  color: var(--cd-text-muted);
  cursor: default;
}

.tab-scroll-arrow {
  display: none;
  align-items: center;
  justify-content: center;
  align-self: center;
  width: 30px;
  height: 30px;
  padding: 0;
  background-color: var(--cd-surface-sunken);
  border: 1px solid var(--cd-line);
  border-radius: 50%;
  cursor: pointer;
  color: var(--cd-text-muted);
  flex-shrink: 0;
}

.tab-scroll-arrow-right {
  /* Pull the arrow away from the last tab's close (X) button so they're not
     crowded together when the tab bar is full. */
  margin-left: 10px;
}

.tab-scroll-arrow-left {
  margin-right: 10px;
}

.tab-scroll-arrow.visible {
  display: flex;
}

.tab-scroll-arrow:hover {
  background-color: var(--cd-surface-hover);
  border-color: var(--cd-line);
  color: var(--cd-text);
}

.ticket-tab.active .ticket-tab-title {
  color: var(--cd-text);
}

.ticket-tab-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: var(--cd-radius-sm);
  font-size: 1.3rem;
  line-height: 1;
  color: var(--cd-text-muted);
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s ease, background-color 0.15s ease;
  flex-shrink: 0;
  margin-left: auto;
}

.ticket-tab:hover .ticket-tab-close,
.ticket-tab.active .ticket-tab-close {
  opacity: 1;
}

/* --cd-brand-soft IS this role ("subtle brand tint bg") and flips for dark; the
   literal was rgba(168, 53, 58, 0.1), the incorrect brick again. */
.ticket-tab-close:hover {
  background-color: var(--cd-brand-soft);
  color: var(--cd-brand);
}

.ticket-tab-panel-container {
  display: none;
  height: 100%;
}

.ticket-tab-panel-container.active {
  display: block;
}

.new-ticket-tab-btn {
  flex: 0 0 auto;
  width: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  font-weight: 400;
  color: var(--cd-text-muted);
  background-color: var(--cd-surface-sunken);
  border: 1px solid var(--cd-line);
  border-bottom: none;
  border-radius: var(--cd-radius-md) var(--cd-radius-md) 0 0;
  cursor: pointer;
  padding: 8px 0;
  transition: background-color var(--transition-speed) ease, color var(--transition-speed) ease;
  user-select: none;
  margin-left: 4px;
}

.new-ticket-tab-btn:hover {
  background-color: var(--cd-surface-hover);
  color: var(--cd-text);
  border-color: var(--cd-line);
}

/* ─── View Toggle ─── */
.view-toggle .btn {
  padding: 0.3rem 0.6rem;
  font-size: 0.875rem;
  line-height: 1;
  color: var(--cd-text-muted);
  border-color: var(--cd-line);
  background: var(--cd-surface-raised);
}

.view-toggle .btn.active {
  background-color: var(--cd-brand-fill);
  border-color: var(--cd-brand);
  color: var(--cd-text-on-brand);
}

.view-toggle .btn:hover:not(.active) {
  background-color: var(--cd-surface-sunken);
  color: var(--cd-text);
}

/* ─── Ticket Card View ─── */
.ticket-cards-container {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.5rem;
}

.card-body .ticket-cards-container {
  padding: 0.75rem;
}

.ticket-card {
  display: block;
  background-color: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  padding: 0;
  overflow: hidden;
  cursor: pointer;
  transition: box-shadow var(--transition-speed) ease;
  text-decoration: none;
  color: inherit;
}

.ticket-card:hover {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  text-decoration: none;
  color: inherit;
}

/* ── Card top section: id/status | body | ages ── */
.ticket-card-top {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.4rem 0.75rem;
}

.ticket-card-id-status {
  flex-shrink: 0;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 0.4rem;
}

/* Number stacked above the status badge; the row checkbox sits to its left,
   aligned to the number line (CD-859). */
.ticket-card-id-inner {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  min-width: 64px;
}

.ticket-card-number {
  font-weight: 700;
  font-size: 0.7rem;
  text-align: left;
  color: var(--cd-text);
}

.card-status-badge {
  display: block;
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-radius: var(--cd-radius-pill);
  padding: 2px 5px;
  text-align: center;
  width: 100%;
  border: none;
  cursor: pointer;
  line-height: 1.5;
}

span.card-status-badge {
  cursor: default;
}

button.dd-trigger:disabled,
button.card-status-badge:disabled {
  cursor: not-allowed;
  opacity: 0.7;
}

/* Fields populated by a macro on the new-ticket form — highlighted until the
   agent edits that field. Inputs/dropdowns get a tinted background; buttons and
   container regions (tag chips, custom fields) get just the ring. */
/* The ring was rgba(250, 204, 21, .55) and the dark surface rgba(250, 204, 21, .2)
   — Tailwind yellow-300, in a file whose other ambers were Bootstrap's #ffc107.
   Neither was ever flagged: the hex guard scans for `#`, so an rgba() literal is
   invisible to it. Both are now the amber tokens, and the dark twin goes away
   because --cd-warning-bg-hover already flips. */
.macro-applied {
  box-shadow: 0 0 0 0.15rem var(--cd-warning-ring);
  border-radius: var(--cd-radius-sm);
  transition: box-shadow 0.2s ease, background-color 0.2s ease;
}

input.macro-applied,
textarea.macro-applied,
button.dd-trigger.macro-applied {
  background-color: var(--cd-warning-bg-hover) !important;
}

/* Solid status chips. tokens.css also maps .status-bg-* (loads later, wins) — these
   stay in sync so main.css isn't self-contradictory. Colors are the mode-aware
   --cd-status-*-solid / -on tokens (the -on variant tracks each mode's fill). */
.status-bg-new {
  background-color: var(--cd-status-new-solid);
  color: var(--cd-status-new-on);
}

.status-bg-open {
  background-color: var(--cd-status-open-solid);
  color: var(--cd-status-open-on);
}

.status-bg-pending {
  background-color: var(--cd-status-pending-solid);
  color: var(--cd-status-pending-on);
}

.status-bg-on_hold {
  background-color: var(--cd-status-on_hold-solid);
  color: var(--cd-status-on_hold-on);
}

.status-bg-solved {
  background-color: var(--cd-status-solved-solid);
  color: var(--cd-status-solved-on);
}

.status-bg-closed {
  background-color: var(--cd-status-closed-solid);
  color: var(--cd-status-closed-on);
}

.status-bg-spam {
  background-color: var(--cd-status-spam-solid);
  color: var(--cd-status-spam-on);
}

.status-bg-merged {
  background-color: var(--cd-status-merged-solid);
  color: var(--cd-status-merged-on);
  opacity: 0.7;
}

/* One hover treatment for every editable status chip — darkens whatever solid
   fill is applied, mode-agnostic (replaces eight per-status hex rules). */
button.card-status-badge:hover {
  filter: brightness(0.93);
}

.ticket-card-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: 2px;
  overflow: hidden;
}

.ticket-card-title {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--cd-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.4;
}

.ticket-card-title-link {
  color: inherit;
  text-decoration: none;
}

/* Topbar title text — click to edit inline (CD-936). Only the text is the
   edit target, so the message-count bubble beside it stays a plain indicator. */
.ticket-card-title-text {
  cursor: text;
}

.ticket-card-title-link:hover,
.ticket-card-title-link:focus {
  color: inherit;
  text-decoration: none;
}

/* Admin list titles link to the item editor (CD-580). Kept looking like the
   bold name until hover/focus reveals the link, so admin tables don't turn into
   a wall of blue links. */
.admin-title-link {
  color: inherit;
  text-decoration: none;
}

.admin-title-link:hover,
.admin-title-link:focus {
  color: inherit;
  text-decoration: underline;
}

.ticket-card-description {
  font-size: 0.75rem;
  color: var(--cd-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  line-height: 1.4;
}

.ticket-card-description:hover {
  color: var(--cd-text);
  text-decoration: underline;
}

.ticket-card-ages {
  flex-shrink: 0;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 0.5rem;
  font-size: 0.7rem;
  color: var(--cd-text-muted);
  white-space: nowrap;
  padding-top: 1px;
}

/* Card inline-edit fields row */
.ticket-card-fields {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  border-top: 1px solid var(--cd-line);
  margin-top: 0;
  overflow: hidden;
}

/* Selected custom-field columns are appended after the built-in fields and
   there can be several, so the list card wraps to keep every field visible
   (the row has overflow:hidden with no scrollbar up the chain, so a nowrap
   row would clip trailing custom fields off the right edge). The built-in
   fields alone still fit one line, so the common case is unchanged. The
   topbar is a single-ticket strip in a narrow column with no custom fields,
   so it keeps the original nowrap + shrink/ellipsize behavior. */
.ticket-card--topbar .ticket-card-fields {
  flex-wrap: nowrap;
}

/* Tags column (CD-842), table cell + card field value. Chips wrap within the
   cell so a ticket with several tags doesn't force the column wide; the small
   gap replaces the fragile trailing-space spacing between badges. */
.ticket-tags-cell {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  align-items: center;
}

/* flex-shrink is load-bearing: a long agent/customer name must shrink and
   ellipsize (via the .dd-trigger overflow rules) instead of pushing trailing
   fields (Category) past the row's overflow:hidden clip, which has no
   scrollbar anywhere up the ancestor chain. */
.ticket-card-field {
  flex: 0 1 auto;
  min-width: 150px;
  padding: 0.25rem 1.25rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.ticket-card-field-label {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--cd-text-muted);
  margin-bottom: 0.125rem;
}

.ticket-card-field .form-select-sm {
  border: none;
  padding: 0;
  padding-right: 1.5rem;
  font-size: 0.8125rem;
  height: auto;
  background-position: right 0 center;
  box-shadow: none;
  cursor: pointer;
}

.ticket-card-field .form-select-sm:focus {
  box-shadow: none;
  outline: none;
}

.ticket-card-field-value {
  font-size: 0.87rem;
  font-weight: 600;
  color: var(--cd-text);
  padding: 0.15rem 0.25rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Placeholder values ("Unassigned", "None") read as data at full weight —
   mute them so real values stand out. --apu-medium-gray is theme-mapped. */
.ticket-card .placeholder-value {
  color: var(--cd-text-muted);
  font-weight: 400;
}

/* Canonical dropdown trigger - ticket-card inline-edit variant */
.dd-trigger:not(.card-status-badge):not(.dd-trigger--select):not(.btn) {
  background: transparent;
  border: none;
  font-size: 0.87rem;
  font-weight: 600;
  color: var(--cd-text);
  padding: 0.15rem 0.25rem;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  border-radius: var(--cd-radius-sm);
  transition: background-color 0.15s;
}

.dd-trigger:not(.btn):not(.card-status-badge):hover {
  background-color: var(--cd-surface-hover);
}

.dd-trigger:not(.btn):focus {
  outline: 2px solid var(--cd-brand);
  outline-offset: -1px;
}

/* Form-select variant - mimics Bootstrap's .form-select appearance */
.dd-trigger--select {
  display: block;
  width: 100%;
  padding: 0.375rem 2.25rem 0.375rem 0.75rem;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  /* Was --bs-body-color / --bs-body-bg / --bs-border-color. Bootstrap's
     variables track `data-bs-theme`, and this app themes on `data-theme`, so
     they never followed dark mode — which is why a hand-written
     [data-theme="dark"] twin had to exist further down this file. */
  color: var(--cd-text);
  background-color: var(--cd-surface-raised);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%236B655C' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 16px 12px;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-sm);
  appearance: none;
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

/* Was Bootstrap's blue focus (#86b7fe + rgba(13,110,253,.25)) — copied in with
   the .form-select mimicry and never rebranded, so focusing this control
   flashed Bootstrap blue in an all-brick app. */
.dd-trigger--select:focus {
  border-color: var(--cd-focus);
  outline: 0;
  box-shadow: 0 0 0 0.25rem var(--cd-focus-ring);
}

.dd-trigger--select-sm {
  padding: 0.25rem 2rem 0.25rem 0.5rem;
  font-size: 0.875rem;
  border-radius: var(--cd-radius-sm);
}

/* Multi-select trigger when at least one option is checked.
   The brand fill means "this FILTER is narrowing the list" — it reads against
   its unfilled siblings in a filter row. On a data-entry form it says nothing
   (a user having 1 affiliation is not a state worth shouting) and, sitting
   beside plain .dd-trigger--select fields, reads as selected-emphasis or an
   error. Opt a form field out with .dd--no-selection-fill.
   The child combinator is what makes the opt-out reachable, and it holds every
   trigger to being a DIRECT child of its .dd — which every `data-dd-multi`
   wrapper already is. Nest one and it silently loses the fill. */
.dd:not(.dd--no-selection-fill) > .dd-trigger.dd-trigger--has-selection {
  background-color: var(--cd-brand-fill-hover);
  border-color: var(--cd-focus);
  color: var(--cd-text-on-brand);
}
.dd:not(.dd--no-selection-fill) > .dd-trigger.dd-trigger--has-selection:hover,
.dd:not(.dd--no-selection-fill) > .dd-trigger.dd-trigger--has-selection:focus {
  background-color: var(--cd-brand-fill);
  border-color: var(--cd-brand);
  color: var(--cd-text-on-brand);
}

/* Fields a macro will change on submit — highlighted for review.
   The only rule in the file that used all four amber roles at once (surface,
   border, ink, ring), which is why it needed eight literals across two themes.
   The ring was rgba(255, 193, 7, .35) — Bootstrap amber, where the
   .macro-applied ring five hundred lines up was Tailwind yellow-300. Same
   visual role, two different palettes, and the guard could see neither. */
/* -fg-strong, not -fg: the deeper surface takes the stronger ink, and the two
   are a pair for that reason. The literal this replaced (#664d03 on #fff3cd)
   was 7.21:1, and -fg would land at 5.02:1 — passing, but a visible step down
   on the one control whose whole job is to say "this field is about to change".
   -fg-strong holds 7.65:1 light / 7.61:1 dark. */
.dd-trigger.will-change {
  background-color: var(--cd-warning-bg-hover);
  border-color: var(--cd-warning-border);
  color: var(--cd-warning-fg-strong);
  box-shadow: 0 0 0 2px var(--cd-warning-ring);
}

/* Per-section clear button - X overlays trigger's reserved right padding so
   wrapper width stays constant across resting and selected states. */
.dd--with-clear {
  display: inline-block;
  position: relative;
}
.dd--with-clear .dd-trigger {
  border-radius: var(--cd-radius-pill);
  min-width: 7rem;
  text-align: center;
}
.dd--with-clear .dd-trigger--has-selection {
  text-align: left;
  padding-left: 0.75rem;
  padding-right: 2.25rem;
}
.dd-clear {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  padding: 0;
  font-size: 0.85rem;
  line-height: 1;
  background-color: var(--cd-brand-fill-hover);
  border: 1px solid var(--cd-focus);
  border-left: 1px solid rgba(255, 255, 255, 0.4);
  color: var(--cd-text-on-brand);
  border-radius: 0 var(--cd-radius-pill) var(--cd-radius-pill) 0;
  cursor: pointer;
}
.dd-clear[hidden] {
  display: none;
}
.dd-clear:hover,
.dd-clear:focus {
  background-color: var(--cd-brand-fill);
  border-color: var(--cd-brand);
  border-left-color: rgba(255, 255, 255, 0.5);
  color: var(--cd-text-on-brand);
  outline: 0;
}
.dd-clear:focus-visible {
  box-shadow: 0 0 0 0.2rem rgba(193, 71, 79, 0.35);
}

/* Search input with inline clear button */
.search-input-wrap {
  position: relative;
  display: inline-block;
}
.search-input-wrap .search-input {
  padding-right: 1.75rem;
}
.search-clear {
  position: absolute;
  top: 50%;
  right: 0.25rem;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  padding: 0;
  font-size: 1rem;
  line-height: 1;
  background: transparent;
  border: 0;
  color: var(--cd-text-muted);
  border-radius: 50%;
  cursor: pointer;
}
.search-clear[hidden] {
  display: none;
}
.search-clear:hover,
.search-clear:focus {
  color: var(--cd-text);
  background-color: rgba(0, 0, 0, 0.06);
  outline: 0;
}
.search-clear:focus-visible {
  box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}

/* Shared dropdown overlay */
.dd-overlay {
  position: fixed;
  z-index: 9999;
  background: var(--cd-surface-raised);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--cd-radius-md);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  max-height: 240px;
  display: flex;
  flex-direction: column;
}

.dd-overlay .form-control-sm {
  border: none;
  border-bottom: 1px solid var(--bs-border-color);
  border-radius: var(--cd-radius-sm) var(--cd-radius-sm) 0 0;
  font-size: 0.8125rem;
}

.dd-overlay .form-control-sm:focus {
  box-shadow: none;
}

.dd-results {
  overflow-y: auto;
  max-height: 190px;
}

.dd-item {
  padding: 0.35rem 0.75rem;
  font-size: 0.8125rem;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

/* Category items can contain long "Root › Leaf" paths — allow them to wrap
   so the full label is always visible rather than truncated by the overlay.
   The min-width here must match (or be covered by) the JS positionOverlay
   floor of 560px for category_id fields. */
/* CD-555 — the status picker has a small, fixed set of options; let it grow to
   fit them so no scrollbar appears (the generic 190px cap is for long lists). */
.dd-overlay--status {
  max-height: none;
}

.dd-overlay--status .dd-results {
  max-height: none;
}

/* Width (incl. the wide category/team floor) is set by dropdown.js
   positionOverlay, capped to the space beside the trigger so it can't overflow a
   narrow container like the 520px new-ticket panel. No CSS min-width here — it
   would override that cap and re-introduce the overflow. */
.dd-overlay--category .dd-item {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  align-items: flex-start;
  line-height: 1.4;
}

.dd-item-subtext {
  font-size: 0.75rem;
  color: var(--cd-text-muted);
  margin-left: 0.5rem;
}

.dd-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}

/* Non-selectable group header (e.g. department label above its teams) */
.dd-group-header {
  padding: 0.3rem 0.75rem 0.15rem;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--cd-text-muted);
  cursor: default;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Items rendered under a group header are indented beneath it. */
.dd-item--in-group {
  padding-left: 1.5rem;
}

/* A category that has children (CD-1864). Deliberately NOT styled like
   .dd-group-header above: this row is selectable — ADR-0002 lets any category,
   branch or leaf, be filed on a ticket — so it stays sentence case, full size,
   full colour and pointer-cursor, and differs from a header on every one of
   those axes as well as weight. Keyed off having children, not off depth: a
   nested category with children is a branch too. Left padding is set inline
   per row from the category's depth. */
.dd-item--branch {
  font-weight: 600;
}

/* Multi-select checkbox marker */
.dd-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  border: 1.5px solid var(--bs-border-color);
  border-radius: var(--cd-radius-sm);
  flex-shrink: 0;
}

.dd-check--on {
  background-color: var(--cd-brand-fill);
  border-color: var(--cd-brand);
}

.dd-check--on::after {
  content: '';
  display: block;
  width: 4px;
  height: 7px;
  border: 2px solid var(--cd-text-on-brand);
  border-top: none;
  border-left: none;
  transform: rotate(45deg) translate(-1px, -1px);
}

.dd-item:hover {
  background-color: var(--cd-surface-sunken);
}

.dd-item.active {
  background-color: var(--cd-brand-soft);
  color: var(--cd-brand);
  font-weight: 500;
}

.dd-item--focused {
  background-color: var(--cd-surface-sunken);
}

.ticket-card-field-action {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.4rem 0.75rem;
}

.ticket-card-field-action .btn {
  white-space: nowrap;
}

/* Success flash for auto-saved card fields */
.card-edit-search.update-success {
  outline: 2px solid var(--cd-success);
  outline-offset: 1px;
}

/* Self-assign "Take It" hover button on queue ticket cards */
.ticket-card-agent-label-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.125rem;
}

.ticket-card-agent-label-row .ticket-card-field-label {
  margin-bottom: 0;
}

.take-it-btn {
  background: transparent;
  border: 1px solid var(--cd-focus);
  border-radius: var(--cd-radius-pill);
  color: var(--cd-focus);
  font-size: 0.65rem;
  font-weight: 500;
  line-height: 1;
  padding: 0.1rem 0.35rem;
  margin-left: 1rem;
  cursor: pointer;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.12s ease, background-color 0.12s ease, color 0.12s ease;
}

.take-it-btn--hidden {
  display: none;
}

.take-it-btn i {
  font-size: 0.75rem;
  margin-right: 0.15rem;
}

.ticket-card:hover .take-it-btn,
.ticket-card:focus-within .take-it-btn,
.take-it-btn:focus-visible {
  opacity: 1;
}

.take-it-btn:hover,
.take-it-btn:focus-visible {
  background-color: var(--cd-brand-fill-hover);
  color: var(--cd-text-on-brand);
}

/* Pinned "Take It" row inside assignee dropdown */
.dd-item--take-it {
  font-weight: 600;
  border-bottom: 1px solid var(--cd-line);
}

/* ── Status-coloured selects & values ── */
.status-color-new {
  color: var(--cd-status-new-fg);
}

.status-color-open {
  color: var(--cd-status-open-fg);
}

.status-color-pending {
  color: var(--cd-status-pending-fg);
}

.status-color-on_hold {
  color: var(--cd-status-on_hold-fg);
}

.status-color-solved {
  color: var(--cd-status-solved-fg);
}

.status-color-closed {
  color: var(--cd-status-closed-fg);
}

/* Status-coloured outlines for card view trigger buttons.
   Uses the mode-aware -fg tokens, so dark mode lightens each border
   automatically (no per-status dark override needed). */
.dd-trigger.status-color-new {
  border: 1.5px solid var(--cd-status-new-fg);
}

.dd-trigger.status-color-open {
  border: 1.5px solid var(--cd-status-open-fg);
}

.dd-trigger.status-color-pending {
  border: 1.5px solid var(--cd-status-pending-fg);
}

.dd-trigger.status-color-on_hold {
  border: 1.5px solid var(--cd-status-on_hold-fg);
}

.dd-trigger.status-color-solved {
  border: 1.5px solid var(--cd-status-solved-fg);
}

.dd-trigger.status-color-closed {
  border: 1.5px solid var(--cd-status-closed-fg);
}

.status-color-new,
.status-color-open,
.status-color-pending,
.status-color-on_hold,
.status-color-solved,
.status-color-closed {
  font-weight: 600;
}

.ticket-msg-count {
  font-size: 0.7rem;
  font-weight: 500;
  color: var(--cd-text-muted);
  white-space: nowrap;
  margin-left: 0.5rem;
}

/* "Matched in conversation" (ticket 04) — explains a search hit whose title looks
   unrelated to the query. Soft info tint, so it reads as an annotation on the row
   rather than competing with the status chip. */
.cd-matched-conversation {
  font-size: 0.68rem;
  font-weight: 500;
  color: var(--cd-info-fg);
  background-color: var(--cd-info-bg);
  white-space: nowrap;
  margin-left: 0.5rem;
  vertical-align: middle;
}

.ticket-attach-indicator {
  font-size: 0.7rem;
  color: var(--cd-text-muted);
  white-space: nowrap;
  margin-left: 0.5rem;
}

.ticket-card-copy-url {
  margin-left: 1rem;
  background: transparent;
  border: 0;
  padding: 0;
  color: var(--cd-text-muted);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
}

.ticket-card-copy-url:hover {
  color: var(--cd-text);
}

.ticket-card-copy-url.copied {
  color: var(--cd-success);
}

.ticket-cards-empty {
  grid-column: 1 / -1;
  text-align: center;
  color: var(--cd-text-muted);
  padding: 2rem;
}

/* Only the chevron survives as a dark override, and only because a data: URI
   cannot reference a custom property — the stroke has to be a literal, so the
   one role in this control that tokens genuinely cannot express keeps a twin.
   Surface, border, text and the focus ring are all handled by the tokenized
   base rule now (search .dd-trigger--select above); their four literal
   restatements here are deleted.

   Note both stroke values are hex written as `%23xxxxxx`, which
   scripts/check-css-tokens.js cannot see — it scans for `#`. Percent-encoded
   colors inside url() are a structural blind spot in that guard, not an
   oversight in the baseline. */
[data-theme="dark"] .dd-trigger--select {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23A79E90' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

/* ─── Ticket Show Layout (scrollable columns + fixed reply bar) ─── */

/* Force body to exactly viewport height so flex chain constrains children.
   Without this, body's min-height: 100vh allows it to grow taller than
   the viewport, and flex-grow-1 children expand instead of constraining.

   IMPORTANT: scope to #main-content > .ticket-show-layout so the rule does
   NOT fire when .ticket-show-layout appears inside a preview offcanvas or
   a tab panel (which would lock scroll on the tickets list page). */
body:has(#main-content > .ticket-show-layout),
body.ticket-show-active {
  height: 100vh;
  max-height: 100vh;
  overflow: hidden;
}

/* Hide footer when ticket show layout is active (full-height view) */
body:has(#main-content > .ticket-show-layout)>.app-footer,
body.ticket-show-active>.app-footer {
  display: none;
}

/* Ensure intermediate flex containers constrain height (not grow indefinitely) */
body:has(#main-content > .ticket-show-layout)>.d-flex.flex-grow-1,
body.ticket-show-active>.d-flex.flex-grow-1 {
  min-height: 0;
  overflow: hidden;
  flex: 1 1 0;
}

body:has(#main-content > .ticket-show-layout) #sidebar,
body.ticket-show-active #sidebar {
  min-height: 0;
  overflow-y: scroll;
}

body:has(#main-content > .ticket-show-layout)>.d-flex.flex-grow-1>.flex-grow-1,
body.ticket-show-active>.d-flex.flex-grow-1>.flex-grow-1 {
  min-height: 0;
  overflow: hidden;
  flex: 1 1 0;
}

#main-content:has(.ticket-show-layout),
body.ticket-show-active #main-content {
  padding: 0 !important;
  overflow: hidden;
  min-height: 0 !important;
}

.ticket-show-layout {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  flex: 1 1 0;
}

.ticket-show-header {
  flex-shrink: 0;
  padding: 0.5rem 1.5rem;
  background: var(--cd-surface-raised);
  border-bottom: 1px solid var(--cd-line);
}

/* Topbar uses the shared .ticket-card component - only needs a margin wrapper */
.ticket-main-topbar {
  margin: 0 0 1rem;
}

/* Topbar variant - not clickable, slightly more generous top-section padding */
.ticket-card--topbar {
  cursor: default;
}

.ticket-card--topbar:hover {
  box-shadow: none;
}

.ticket-card--topbar .ticket-card-top {
  padding: 0.5rem 1rem;
}

/* The topbar lives in the narrower .ticket-show-main column, so the list
   view's 150px field floor can overflow even after shrinking - lower it just
   enough to still fit the field labels. */
.ticket-card--topbar .ticket-card-field {
  min-width: 110px;
}

/* Inline title-edit input (replaces .ticket-card-title on click) */
.ticket-card-title-input {
  flex: 0 1 auto;
  min-width: 0;
  font-size: 0.875rem;
  font-weight: 600;
  border: none;
  border-bottom: 2px solid var(--cd-brand);
  outline: none;
  background: transparent;
  padding: 0;
  margin: 0;
  width: 300px;
}

.property-customer-btn {
  display: block;
  width: 100%;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--cd-radius-pill);
  padding: 0.25rem 0.5rem;
  text-align: left;
  font-size: 0.875rem;
  line-height: 1.5;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: inherit;
}

.property-customer-btn:hover {
  color: var(--cd-brand);
}

.property-field-saving {
  opacity: 0.6;
}

.property-field-saved>.tag-search-wrapper>input,
.property-field-saved .property-customer-btn {
  color: var(--cd-success);
  transition: color 0.3s;
}

.property-field-error>.tag-search-wrapper>input,
.property-field-error .property-customer-btn {
  color: var(--cd-danger);
  transition: color 0.3s;
}

.ticket-show-body {
  flex: 1 1 0;
  overflow: hidden;
  display: flex;
  min-height: 0;
}

.ticket-show-left {
  flex: 2;
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}

.ticket-show-main {
  flex: 1 1 0;
  overflow-y: auto;
  padding: 1rem 1.5rem;
  min-height: 0;
  min-width: 0;
}

.ticket-show-sidebar {
  flex: 1;
  overflow-y: auto;
  scrollbar-width: none;
  padding: 1rem;
  border-left: 1px solid var(--cd-line);
  min-width: 280px;
  max-width: 380px;
}

.ticket-show-sidebar::-webkit-scrollbar {
  display: none;
}

/* ── Sidebar sections: collapsible headers + chevron (wired by ticketSidebarSections.js) ── */
.sidebar-section-header {
  cursor: pointer;
  position: relative;
  padding-right: 2.25rem;
  user-select: none;
}

.sidebar-section-chevron {
  position: absolute;
  right: 1rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--cd-text-muted);
  transition: transform var(--transition-speed) ease;
}

.ticket-show-sidebar > .card.section-collapsed > :not(.card-header) {
  display: none;
}

.card.section-collapsed .sidebar-section-chevron {
  transform: translateY(-50%) rotate(180deg);
}

.ticket-reply-bar {
  flex-shrink: 0;
  border-top: 2px solid var(--cd-line);
  padding: 0.75rem 1.5rem;
  background-color: var(--cd-surface-raised);
}

.ticket-show-left.reply-expanded .ticket-show-main {
  display: none;
}

/* Expanded reply - flex column chain so editor fills column without overflow */
.ticket-show-left.reply-expanded .ticket-reply-bar {
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.ticket-show-left.reply-expanded #replyExpandedForm {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.ticket-show-left.reply-expanded #replyForm {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.ticket-show-left.reply-expanded .reply-editor-wrap {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.ticket-show-left.reply-expanded #replyQuillEditor {
  flex: 1;
  min-height: 0 !important;
  display: flex;
  flex-direction: column;
}

.ticket-show-left.reply-expanded #replyQuillEditor .ql-toolbar {
  flex-shrink: 0;
}

/* Same two-axis scrollbar loop as the compact composer above — `max-height:
   none` doesn't exempt it, because `flex: 1; min-height: 0` is what bounds this
   one, and a bounded conditional scroll container is all the loop needs. */
.ticket-show-left.reply-expanded #replyQuillEditor .ql-container {
  flex: 1;
  min-height: 0;
  max-height: none;
  overflow-y: auto;
  scrollbar-gutter: stable;
}

.ticket-show-left.reply-expanded #replyQuillEditor .ql-editor {
  min-height: 100%;
}

/* ─── Thread Tab Bar (Messages / Attachments / History / Side Conversations) ─── */
.thread-tab-bar {
  display: flex;
  gap: 0;
  border-bottom: 2px solid var(--cd-line);
  margin-bottom: 1rem;
  padding: 0;
}

.thread-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  padding: 0.45rem 1rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--cd-text-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.15s, border-color 0.15s;
}

.thread-tab:hover {
  color: var(--cd-text-muted);
  border-bottom-color: var(--cd-line);
}

.thread-tab.active {
  color: var(--cd-brand);
  border-bottom-color: var(--cd-brand);
}

/* Thread history list */
.thread-history-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-bottom: 1rem;
}

.thread-history-item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem 0.5rem;
  padding: 0.4rem 0.6rem;
  border-radius: var(--cd-radius-md);
  background: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  font-size: 0.8125rem;
}

.thread-history-meta {
  color: var(--cd-text-muted);
  flex-shrink: 0;
}

.thread-history-change {
  color: var(--cd-text-muted);
}

.thread-history-val {
  display: inline-block;
  padding: 0 0.35rem;
  border-radius: var(--cd-radius-sm);
  background: var(--cd-surface-sunken);
  color: var(--cd-text-muted);
  font-style: normal;
}

/* Material green (#e8f5e9/#2e7d32) with a hand-written dark twin, in a file
   whose other successes are --cd-success-*. Both tokens flip, so the twin goes. */
.thread-history-val-new {
  background: var(--cd-success-bg);
  color: var(--cd-success-fg);
}

/* Reply bar collapsed toolbar */
.reply-collapsed-toolbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* The thread audience on the collapsed reply toolbar (#1551).

   Two rules, and the second is the one that took a review to get right:

   1. It never pushes the row to a second line — CD-1944's rule for this bar.
   2. It never degrades to NOTHING. Clipping the names with `overflow: hidden` was
      the first attempt and it was wrong twice over: `text-overflow: ellipsis` does
      not apply to flex children, so it clipped mid-name with no cue, and at the
      narrowest widths it left the label sitting beside an empty box — which looks
      exactly like the empty-audience state. So the count chip is rendered
      alongside the names and CSS picks: the chip wins when the list is long
      (`data-shape="chip"`, decided server-side at CC_INLINE_MAX) or when this
      toolbar is too narrow for names, whichever comes first.

   The toolbar is its own size container so the switch reads ITS width, not the
   viewport's — a wide window with a narrow preview pane must still collapse
   (same reasoning as the action row's `replybar` container, CD-864). */
.reply-collapsed-toolbar {
  container-type: inline-size;
  container-name: replytoolbar;
}

.reply-audience-line {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin-left: auto;
  min-width: 0;
  flex-shrink: 1;
  overflow: hidden;
}

.reply-audience-line .reply-audience-label,
.reply-audience-line .reply-audience-empty {
  white-space: nowrap;
  flex-shrink: 0;
}

.reply-audience-names {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  min-width: 0;
  white-space: nowrap;
}

.reply-audience-chip {
  border: 1px solid var(--cd-line);
  color: var(--cd-text-muted);
  background: var(--cd-surface-raised);
  font-size: 0.8125rem;
  padding: 0.15rem 0.5rem;
  border-radius: var(--cd-radius-pill);
  white-space: nowrap;
  flex-shrink: 0;
}

/* Wide enough, and few enough names to spell out: names only. */
.reply-audience-line[data-shape="inline"] .reply-audience-chip {
  display: none;
}

/* Too narrow for names whatever the count: the chip takes over. 34rem is where the
   three action buttons plus two realistic names stop fitting; `verify:reply-bar`
   sweeps the whole range either side of it rather than trusting this number. */
@container replytoolbar (max-width: 34rem) {
  .reply-audience-line[data-shape="inline"] .reply-audience-names {
    display: none;
  }

  .reply-audience-line[data-shape="inline"] .reply-audience-chip {
    display: inline-flex;
    align-items: center;
  }
}

.reply-action-btn {
  border: 1px solid var(--cd-line);
  color: var(--cd-text-muted);
  background: var(--cd-surface-raised);
  font-size: 0.8125rem;
  padding: 0.3rem 0.75rem;
  border-radius: var(--cd-radius-pill);
  transition: background-color 0.15s, border-color 0.15s;
}

.reply-action-btn:hover {
  background: var(--cd-surface-hover);
  border-color: var(--cd-line);
  color: var(--cd-text);
}

/* Nine literals across three rules for two states. The hover deepens the
   surface AND the ink, which is the pair --cd-warning-bg-hover /
   --cd-warning-fg-strong exists for; the dark twin is gone because every token
   here flips. */
.reply-action-note {
  color: var(--cd-warning-fg);
  border-color: var(--cd-warning);
  background: var(--cd-warning-bg);
}

.reply-action-note:hover {
  background: var(--cd-warning-bg-hover);
  border-color: var(--cd-warning);
  color: var(--cd-warning-fg-strong);
}

/* Internal-note amber tint for the reply bar — same pair as
   .comment-card-internal itself, so it uses the same two tokens. */
.ticket-reply-bar.comment-card-internal {
  background-color: var(--cd-warning-bg);
  border-top-color: var(--cd-warning);
}

.ticket-reply-bar.comment-card-internal .ql-toolbar,
.ticket-reply-bar.comment-card-internal .ql-container {
  border-color: var(--cd-warning-border);
}

/* --cd-warning-bg, not the barest #FFFEF5 it replaces. In dark the token is an
   rgba() tint, so this editor nested inside the already-amber reply bar stacks
   the alpha and separates itself the way the old second literal did by hand. */
.ticket-reply-bar.comment-card-internal .ql-editor {
  background-color: var(--cd-warning-bg);
}

/* Three more dark twins deleted — #3A3520 / #B8860B / #5C4E1F / #332E1A were
   all hand-guessed equivalents of tokens that already flip. */

/* Reply bar header row */
.reply-header {
  background-color: var(--cd-surface-sunken);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
}

.ticket-reply-bar.comment-card-internal .reply-header {
  background-color: var(--cd-warning-bg);
  border-color: var(--cd-warning-border);
}

/* Reply attachment chips (paperclip uploads attached to draft) */
.reply-attachment-chips:empty {
  display: none !important;
}

.reply-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.2rem 0.5rem;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-pill);
  background-color: var(--cd-surface-sunken);
  font-size: 0.8125rem;
  line-height: 1.2;
}

.reply-chip .chip-name {
  max-width: 16ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.reply-chip .chip-size {
  color: var(--cd-text-muted);
  font-size: 0.75rem;
}

.reply-chip .chip-remove {
  border: 0;
  background: transparent;
  padding: 0 0.15rem;
  font-size: 1rem;
  line-height: 1;
  color: var(--cd-text-muted);
  cursor: pointer;
}

.reply-chip .chip-remove:hover {
  color: var(--cd-danger);
}

/* Custom toolbar buttons (AI, KB) in Quill toolbar - override Quill snow's width:28px/float:left */
.ql-snow .ql-toolbar .ql-reply-custom,
.ql-reply-custom {
  display: inline-flex !important;
  align-items: center;
  gap: 3px;
  padding: 2px 6px;
  background: none;
  border: none;
  border-radius: var(--cd-radius-md);
  font-size: 0.8125rem;
  cursor: pointer;
  color: var(--cd-text-muted) !important;
  height: 24px;
  width: auto !important;
  float: none !important;
  white-space: nowrap;
}

.ql-snow .ql-toolbar .ql-reply-custom:hover,
.ql-reply-custom:hover {
  background-color: var(--cd-surface-hover);
  color: var(--cd-text) !important;
}

/* --cd-text-subtle at full opacity in BOTH themes rather than dimming
   --cd-text-muted to 0.6. The old rule did the latter in light and then had to
   undo it in dark (`color:#777; opacity:1`) because #777 at 0.6 on the dark
   toolbar was near-invisible — a token that already de-emphasises makes the
   second pass unnecessary, and avoids double-dimming. */
.ql-reply-custom:disabled {
  color: var(--cd-text-subtle) !important;
  opacity: 1;
  cursor: not-allowed;
}

/* No [data-theme="dark"] block here: this used to carry three, re-stating the
   same declarations with cool-grey literals (#CCC / #3A3A3A / #FFF / #777)
   because the light values were literals too. The surface tier already flips
   every one of those roles, so the rules above now cover both themes. */

/* AI Suggest failure state (CD-906): red pulsing button with x-eyed robot +
   pill callout above the toolbar. Extra .ai-suggest-error class keeps these
   selectors more specific than the themed .ql-reply-custom color overrides. */
/* --cd-danger-fg, not --cd-danger. The solid hue is FIXED at #B23A40 in both
   themes, which is too dark to read as an alert on the dark toolbar — that is
   exactly why dark twins existed here lightening it to #ff6b6b / #ff8787.
   --cd-danger-fg is the ink role and does flip (#8A2A2E -> #E88C90), so the
   base rules now serve both themes and both twins are deleted. (Correcting my
   own earlier pass: the Bootstrap #dc3545 here became --cd-danger, when the ink
   token was the right one.)
   The hover wash was rgba(220, 53, 69, .12) — Bootstrap danger in decimal, and
   invisible to the hex guard for the same reason as the amber rings. */
.ql-snow .ql-toolbar .ql-reply-custom.ai-suggest-error,
.ql-reply-custom.ai-suggest-error {
  color: var(--cd-danger-fg) !important;
}

.ql-snow .ql-toolbar .ql-reply-custom.ai-suggest-error:hover,
.ql-reply-custom.ai-suggest-error:hover {
  color: var(--cd-danger-fg) !important;
  background-color: var(--cd-danger-bg);
}

/* x-eyed robot inline SVG — sized/aligned like the bi-robot font glyph */
.ai-robot-x {
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  flex-shrink: 0;
}

/* Anchor for the callout (toolbar is static by default) */
.ql-toolbar.ai-callout-anchor {
  position: relative;
}

/* Pill callout that sits just above the reply toolbar */
.ai-error-callout {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 0;
  z-index: 30;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 360px;
  padding: 4px 8px 4px 12px;
  border-radius: var(--cd-radius-pill);
  background-color: var(--cd-danger);
  color: var(--cd-danger-on);
  font-size: 0.8125rem;
  line-height: 1.3;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}

.ai-error-callout .ai-robot-x {
  flex-shrink: 0;
}

.ai-error-callout .ai-error-callout-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ai-error-callout .ai-error-callout-retry {
  background: none;
  border: none;
  padding: 0;
  color: var(--cd-danger-on);
  font-size: 0.8125rem;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
  white-space: nowrap;
}

.ai-error-callout .ai-error-callout-close {
  background: none;
  border: none;
  padding: 0 2px;
  color: rgba(255, 255, 255, 0.75);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.ai-error-callout .ai-error-callout-retry:hover,
.ai-error-callout .ai-error-callout-close:hover {
  color: var(--cd-danger-on);
}

.ai-error-callout-hide {
  opacity: 0;
}

/* Subtle motion, gated on reduced-motion preference (static fallback above) */
@media (prefers-reduced-motion: no-preference) {
  .ql-reply-custom.ai-suggest-error {
    animation: ai-suggest-error-pulse 0.45s ease-in-out 2;
  }

  .ai-error-callout {
    animation: ai-error-callout-in 0.2s ease-out;
  }

  .ai-error-callout-hide {
    transition: opacity 0.3s ease-out;
  }
}

@keyframes ai-suggest-error-pulse {
  0%, 100% { background-color: transparent; }
  50% { background-color: rgba(220, 53, 69, 0.25); }
}

@keyframes ai-error-callout-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

.ql-snow .ql-toolbar .ql-reply-template,
.ql-reply-template {
  height: 24px;
  font-size: 0.8125rem;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  padding: 0 4px;
  background: none;
  cursor: pointer;
  max-width: 160px;
  width: auto !important;
  float: none !important;
}

/* Tags dropdown in reply bar — opens upward */
.reply-update-fields #reply-tags-results {
  top: auto;
  bottom: 100%;
  border-radius: var(--cd-radius-md) var(--cd-radius-md) 0 0;
  border-top: 1px solid var(--cd-line);
  border-bottom: none;
  max-height: 260px;
}

/* The reply action row is a size-query container so it can tighten only when its
   OWN width is short (preview pane / narrow split / small window), not on wide
   desktops where there's room for full names (CD-864). Scoped to the row itself
   (not the whole form) to keep layout containment off the Quill editor. */
.reply-action-row {
  container-type: inline-size;
  container-name: replybar;
}

/* The Send button's position must not depend on the draft-status text.
   #draftStatus is a flex sibling immediately BEFORE Send, and the autosave cycle
   rewrites it constantly with labels of wildly different widths — hidden,
   'Saved', 'Saving…', 'Not saved', 'Draft restored', 'Saved on this device',
   'Saved reply draft not loaded — reopen with Reply'. In flow, each of those
   re-sized this group, and once the action row wrapped (which the longer labels
   themselves cause) Send moved with it: measured across the real label set, its
   left edge ranged over 872px, and on an agent's recording it slid 38px between
   'Draft restored' and 'Saved' alone. Worse than the movement is WHERE it lands
   — on the wrapped line Send sits directly under the Public/Note toggle and
   overlaps its span, so an agent aiming for Send hit Note and posted an internal
   note instead of emailing the customer, which is exactly what was reported.
   Out of flow, Send's fixed 6.5rem box is this group's entire width, so Send is
   pinned for every label; the status renders to its left and may be any length.
   Keep #draftStatus's own show/hide — it no longer moves anything. */
.reply-send-group {
  position: relative;
}

/* CD-2056 — the tip row, and what gives way in it.
   The draft-saved notice used to be `position: absolute; right: 100%` inside
   .reply-send-group. Out of flow is what kept it from moving Send (CD-1944) and
   is also exactly why it painted over the Tags/Attach buttons — the report. It
   now sits on the merge-field tip's line instead, so the CD-1944 guarantee holds
   by construction: nothing is beside Send to move it.

   The reporter named what gives way when the row runs out of room: "start
   hiding the edge of the tip rather than hiding the Saved".

   MEASURED, because the obvious explanation is wrong. `overflow: hidden` on the
   tip is what zeroes its automatic minimum size (per flexbox, a scroll
   container's automatic minimum is 0), so `min-width: 0` beside it is
   belt-and-braces rather than the mechanism — dropping either alone still
   truncates. On the notice the two declarations are likewise redundant with each
   other and load-bearing TOGETHER: remove both and the long status
   ("Saved reply draft not loaded — reopen with Reply") compresses from 272px to
   129px at a 360px pane, which is the reporter's rule inverted. Either one holds
   it; `npm run verify:reply-tip-row` is what established that, and what will say
   so again if these move. */
.reply-tip-row {
  min-width: 0;
}

.reply-tip-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.reply-draft-status {
  flex-shrink: 0;
  white-space: nowrap;
}

/* An UNCONDITIONAL cap, so overflow is always paid in ellipses (CD-1944).
   CD-864 capped these only below 55rem and left wide layouts uncapped "so names
   show in full" — but the four values are the only thing in the row that can
   wrap, so uncapped means a long team + agent + category wrap the field group
   instead of eliding. Measured with 45-character values: 3 lines at 1400px, on
   a row with room to spare. The ticket asks for the opposite ("shorten or cut
   off long text. it should show … For overflow"), so the cap now applies at
   every width and merely tightens as the pane narrows. 16rem still shows far
   more than the 10rem the old narrow tier allowed.

   12rem, not more, so widening the pane never COSTS a pill. Above 55rem the cap
   relaxes, which makes each pill wider — at 16rem that pushed the group past the
   space it had just gained and the measuring pass dropped Category on the way
   from 860px to 960px. Growing the window must never take a field away. */
.reply-update-fields .dd-trigger.card-edit-search:not(.btn) {
  max-width: 12rem;
}

/* When the reply area is short, tighten it so a long team or agent name
   ellipsizes rather than pushing the action row past 780px. Thresholds are in
   rem (not px) so they scale with the root font under text-zoom. */
@container replybar (max-width: 55rem) {
  /* :not(.btn) lifts specificity to (0,4,0) so it wins over the base
     .dd-trigger:not(...) rule's max-width:100% (same specificity, later in the
     file) — otherwise the cap is silently overridden and never applies. */
  .reply-update-fields .dd-trigger.card-edit-search:not(.btn) {
    max-width: 10rem;
  }
}

/* Label hiding is step ONE of replyBarOverflow.js's ordered list, not a
   container query. CD-864 hid them below 53.5rem, which is a statement about the
   PANE while the thing that decides whether they fit is the VALUES: at 780px
   with short values the labels fit fine and were hidden anyway, and at 860px
   with a long category they did not fit and were shown. Two mechanisms writing
   the same pixels also disagreed about what was hidden, so the measuring pass
   read a layout it had not produced. See .reply-hide-field-labels above. */

/* ── CD-1944: Send is never on a second line ──────────────────────────────────
   CD-864 kept the row on one line down to 780px by ellipsizing the field values
   and hiding their labels. Below that the row still WRAPPED, and because
   .reply-action-row was justify-content:between with one wrapping bag of
   controls, the thing that dropped was the Send button — landing under the
   Public/Note toggle, which is the mis-click CD-864 set out to kill in the first
   place.

   The fix is TWO levels of flex, and the selectors below depend on the nesting:

     .reply-action-row              flex-nowrap, exactly two children
       .reply-update-fields         grows/shrinks   (empty <div> for non-staff)
         .reply-mode-group          Public/Note — never shrinks, collapses LAST
         .reply-bar-fields          Status/Team/Agent/Category + macro-staged
                                    chips — the ONLY box allowed to shrink/wrap
         .reply-bar-tools           Tags + attach — never shrinks, collapses 1st
       .reply-send-group            LAST, pinned right

   Send is unpushable because it is the last child of a nowrap row, not because
   of anything about its own size. Public / Note / Tags / attach / Send therefore
   stay on that line at every width; when space runs out they give up TEXT, not
   their box, in the order Tags -> Send -> Public/Note. The text is visually
   hidden (clip), never display:none, so each control keeps its accessible name —
   and every one of them also carries a title, so the icon-only form is
   identifiable by hover too. */
.reply-action-row > .reply-update-fields {
  flex: 1 1 auto;
  min-width: 0;
}

.reply-action-row > .reply-send-group {
  flex: 0 0 auto;
}

.reply-update-fields > .reply-mode-group,
.reply-update-fields > .reply-bar-tools {
  flex: 0 0 auto;
}

/* min-width:0 on the group AND its children: a flex item's automatic minimum
   size is its content, so without this the field pills refuse to shrink and
   push the row wider than the pane instead of wrapping. */
.reply-update-fields > .reply-bar-fields,
.reply-update-fields > .reply-bar-fields > * {
  min-width: 0;
}

.reply-update-fields > .reply-bar-fields {
  flex: 1 1 auto;
}

/* Was an inline style="width:6.5rem" on the button. An inline width cannot be
   relaxed by the container query below, so it lives here. The fixed width is
   what makes Send's box the same size for every draft-status label (see
   .reply-send-group above). */
.reply-send-btn {
  width: 6.5rem;
}

/* ── The field pills never take a second line (CD-1944) ──────────────────────
   Whether "Category: super extra long name…" fits is a fact about the value's
   rendered width, which no @container threshold can see — a wide pane with one
   long category wrapped while a narrow pane with four short values did not. So
   `replyBarOverflow.js` measures and hides in priority order, and everything
   here just has to make that measurable and reversible. */
.reply-update-fields > .reply-bar-fields,
.reply-field-pills {
  flex-wrap: nowrap;
}

/* A pill must NOT shrink. Two reasons, and the second is the bug this fixes:
   a shrinking pill has no stable natural width, so `scrollWidth` stops
   answering "how much room does this want" — the measuring pass reads a group
   that has already been squeezed to fit, concludes it fits, and hides nothing.
   Meanwhile the pill's own children overflow the squeezed box and paint over the
   Tags button beside them, because the row is nowrap so there is nowhere to
   wrap to and nothing clips them. Fixed size, then measured, then hidden. */
.reply-field-pills,
.reply-field-pill {
  flex: 0 0 auto;
}

/* Backstop, not the mechanism: the pass runs a frame after a resize, and for
   that frame the group can be wider than its allotment. Clipping means the worst
   case is a value cut short, never a value painted across the Tags button. Safe
   because the pickers portal their menus to <body> (CD-1715/CD-1759), so there
   is no popup inside this box to clip. */
.reply-update-fields > .reply-bar-fields {
  overflow: hidden;
}

/* Step 1 of giving way: the labels go, the values stay. Visually hidden rather
   than removed, so the field identity survives for a screen reader — the
   trigger buttons have no aria-label, same reasoning as CD-864's own rule. */
.reply-action-row.reply-hide-field-labels .reply-field-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* `hidden` has to actually hide. The pill carries Bootstrap's `.d-flex`, whose
   `display: flex` is `!important`, so it beats the UA sheet's
   `[hidden] { display: none }` and the attribute becomes decorative — the pill
   stays painted, `scrollWidth` never shrinks, and the measuring loop hides all
   four and still reports overflow. Costs an `!important` to win back. */
.reply-field-pill[hidden],
.reply-fields-more[hidden] {
  display: none !important; /* tokens-ok: outranks Bootstrap's !important .d-flex */
}

/* The `…` affordance. flex-shrink:0 so the way back to a hidden pill can never
   itself be squeezed out; `hidden` until JS hides something. */
.reply-fields-more {
  flex: 0 0 auto;
  line-height: 1;
  padding: 0.125rem 0.5rem;
  font-size: 0.8125rem; /* tokens-ok: matches the row's own inline size */
}

/* Expanded: the SAME element, floated above the row. Not a re-parented copy —
   the pills stay where ticketShow.js and dropdown.js resolve them. */
.reply-update-fields > .reply-bar-fields.is-fields-expanded {
  position: absolute;
  bottom: calc(100% + 0.25rem);
  left: 0;
  z-index: 1030;
  flex-wrap: wrap;
  max-width: 100%;
  padding: 0.5rem 0.75rem;
  background: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  box-shadow: var(--cd-shadow-md);
}

/* The panel's containing block is the ROW, not .reply-update-fields. Anchored to
   the inner group it sat 4px above THAT group's top — but the group is centred in
   a taller row, so the panel still overlapped the Send button beside it. Against
   the row's own top it clears every control on the row by construction. */
.reply-action-row {
  position: relative;
}

/* In the panel there is room, so let the values show in full. */
.reply-bar-fields.is-fields-expanded .dd-trigger.card-edit-search:not(.btn) {
  max-width: none;
}

/* The fixed controls give up their LABEL, not their box — driven by
   replyBarOverflow.js's one ordered list, not by @container thresholds.
   Thresholds fired on the container's width while the pill pass fired on
   measured content, and the two fought: widening 480px -> 520px handed Send its
   text back, which cost ~60px, which took a pill away. One list, applied only on
   the way down, makes "growing the window never removes anything" true by
   construction.

   Visually hidden (clip), never display:none, so every control keeps its
   accessible name — and each also carries a title, so the icon-only form is
   identifiable by hover. One selector list, one declaration: the three used to
   be three pasted copies inside three separate @container blocks, which plain
   CSS gave no way to share. */
.reply-collapse-tags #replyTagsBtn .reply-btn-text,
.reply-collapse-send .reply-send-btn .reply-btn-text,
.reply-collapse-mode .reply-mode-group .reply-btn-text {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* The icon's Bootstrap .me-1 would leave a dangling gap once the label is gone.
   A class + element selector outranks the utility without !important. */
.reply-collapse-tags #replyTagsBtn > i.bi,
.reply-collapse-send .reply-send-btn > i.bi,
.reply-collapse-mode .reply-mode-group .btn > i.bi {
  margin-right: 0;
}

/* Send's fixed box exists so the draft-status label can't resize it; with no
   label there is nothing to keep a 6.5rem box around. */
.reply-collapse-send .reply-send-btn {
  width: auto;
}

/* Quill editor in reply bar — compact styling. --cd-line matches the two other
   Quill mounts (see .reply-editor-wrap / .automation-body-editor). */
.ticket-reply-bar .ql-toolbar {
  border: 1px solid var(--cd-line);
  border-bottom: none;
  border-radius: var(--cd-radius-md) var(--cd-radius-md) 0 0;
  padding: 4px 6px;
}

/* `overflow-y: auto` makes overflow-x compute to `auto` as well — per CSS
   spec a `visible` on one axis becomes `auto` once the other is not `visible`.
   So this is a TWO-axis conditional scroll container, which nothing here asked
   for, and with space-consuming scrollbars (macOS "Show scroll bars: Always",
   which several agents run) the two axes feed each other: a vertical scrollbar
   steals ~15px of WIDTH, which decides whether a horizontal one is needed, and
   that one steals ~15px of HEIGHT, which decides whether the vertical one is.
   A body whose content sits within a scrollbar's width of both limits — an
   email signature with a full-width table is the common way in — then has TWO
   self-consistent layouts, and the editor flips between them on any repaint.
   Measured from an agent's screen recording: the content jumped 4 CSS px, tens
   of times a second, for the whole 19s clip. It reads as the ticket "moving
   around as you type" and makes clicking to fix a typo land in the wrong place.
   `scrollbar-gutter: stable` reserves the vertical gutter permanently, so
   content width no longer depends on the vertical scrollbar and the cross-axis
   coupling — the loop's only closure — is gone. Don't swap this for
   `overflow-x: hidden`: wide pasted content (those signature tables) would be
   clipped with no way to scroll to it. */
.ticket-reply-bar .ql-container {
  border: 1px solid var(--cd-line);
  border-radius: 0 0 var(--cd-radius-md) var(--cd-radius-md);
  min-height: min(235px, 30vh);
  max-height: 50vh;
  overflow-y: auto;
  scrollbar-gutter: stable;
  font-size: 0.9rem;
}

.ticket-reply-bar .ql-editor {
  min-height: min(215px, 27vh);
  padding: 8px 12px;
}

.ticket-reply-bar .ql-editor.ql-blank::before {
  font-style: normal;
  color: var(--cd-text-subtle);
}

.ticket-reply-cc {
  font-size: 0.8125rem;
}

.ticket-reply-cc .badge {
  font-weight: 500;
  font-size: 0.75rem;
  padding: 0.25em 0.5em;
}

.ticket-reply-cc .badge .btn-close {
  font-size: 0.45rem;
  padding: 0;
  margin-left: 0.25rem;
}

/* KB article search dropdown in reply bar — opens upward */
.ticket-reply-bar #kb-search-results {
  top: auto;
  bottom: 100%;
  border-radius: var(--cd-radius-md) var(--cd-radius-md) 0 0;
  border-top: 1px solid var(--cd-line);
  border-bottom: none;
  max-height: 300px;
}

.ticket-reply-bar #kb-search-results #kb-article-list {
  max-height: 240px;
  overflow-y: auto;
}

/* Copyable field styling */
.copyable-field {
  cursor: pointer;
  border-radius: var(--cd-radius-sm);
  padding: 1px 3px;
  margin: -1px -3px;
  transition: background-color 0.15s ease;
}

.copyable-field:hover {
  background-color: var(--cd-surface-hover);
}

.copyable-field .copy-icon {
  font-size: 0.7rem;
  opacity: 0.4;
  margin-left: 2px;
  transition: opacity 0.15s ease;
}

.copyable-field:hover .copy-icon {
  opacity: 1;
}

/* In note mode, Notify is the first item — remove the To→Notify gap */
.comment-card-internal #replyNotifyRow {
  margin-left: 0 !important;
}

/* CC search dropdown in reply bar — opens upward */
.ticket-reply-cc .tag-search-results {
  top: auto;
  bottom: 100%;
  border-radius: var(--cd-radius-md) var(--cd-radius-md) 0 0;
  border-top: 1px solid var(--cd-line);
  border-bottom: none;
  width: 300px;
  right: auto;
}

/* Linked ticket search dropdown — fixed position to escape overflow clipping */
.js-link-ticket-results {
  position: fixed;
  min-width: 340px;
  max-width: 540px;
  z-index: 9999;
}

/* Tab mode: ticket show inside tab panels */
.ticket-tab-panel-container .ticket-show-layout {
  height: 100%;
}

/* Preview mode: ticket show inside the offcanvas preview panel.
   The full-page layout uses fixed heights + internal scrolling, but inside
   the preview offcanvas the content should flow naturally and let the
   offcanvas-body handle scrolling.  Override all the height/overflow
   constraints so nothing collapses to 0px. */
#ticketPreviewBody .ticket-show-layout {
  height: auto;
  flex: none;
}

#ticketPreviewBody .ticket-show-body {
  flex: none;
  overflow: visible;
  flex-direction: column;
}

#ticketPreviewBody .ticket-show-left {
  flex: none;
}

#ticketPreviewBody .ticket-show-main {
  flex: none;
  overflow: visible;
}

#ticketPreviewBody .ticket-show-sidebar {
  overflow: visible;
  max-width: none;
  border-left: none;
  border-top: 1px solid var(--cd-line);
}

#ticketPreviewBody .ticket-reply-bar {
  display: none;
}

#ticketPreviewBody .ticket-properties-bar,
#ticketPreviewBody .ticket-props-panel {
  display: none;
}

/* Responsive: stack columns on small screens */
@media (max-width: 991.98px) {
  .ticket-show-body {
    flex-direction: column;
    overflow-y: auto;
  }

  .ticket-show-left,
  .ticket-show-main,
  .ticket-show-sidebar {
    overflow-y: visible;
    flex: none;
    max-width: none;
    min-width: 0;
  }

  .ticket-show-sidebar {
    border-left: none;
    border-top: 1px solid var(--cd-line);
  }
}


/* ─── Client Filter ─── */
[data-filter-input] {
  max-width: 300px;
}

[data-filter-no-results] {
  font-size: 0.875rem;
}

/* ─── Global Search ─── */
.global-search-wrapper {
  width: 300px;
  min-width: 200px;
  flex-shrink: 1;
}

/* Center the search in the navbar on desktop via a 3-column flex layout: the
   left nav and the right actions each flex:1 (equal), so the search sits
   centered between them. In-flow (not absolute) so it can never overlap the
   New Ticket button when space is tight — it shrinks instead. Mobile keeps the
   stacked collapse flow. The wrapper stays position:relative (markup utility) so
   the results dropdown still anchors to it.

   Measured caveat (CD-1875, 1280px, seeded admin): "centered" is the intent, not
   the outcome. Both side columns are flex:1 1 0 but the right actions floor at
   their min-content (~512px vs the left nav's ~259px), so the search lands ~130px
   left of #navbarNav's midpoint. It IS wider than it would otherwise be (413 vs
   its 300px basis). Stated so nobody reads the paragraph above as a promise and
   "fixes" a bug that isn't there.

   Scoped to .navbar-app (the app shell's navbar, partials/navbar.hbs) — NOT to
   .navbar-apu, which layouts/public.hbs also uses with the same #navbarNav id.
   The public navbar has no search column, so matching it made its two <ul>s
   equal flex:1 columns with me-auto stomped by the !important below, leaving
   Sign In at the 50% mark instead of the right edge (CD-1875). */
@media (min-width: 992px) {
  .navbar-app #navbarNav {
    align-items: center;
  }
  .navbar-app #navbarNav > .navbar-nav {
    flex: 1 1 0;
    margin-right: 0 !important; /* override me-auto so the side columns stay equal */
  }
  .navbar-app #navbarNav > .d-flex {
    flex: 1 1 0;
    justify-content: flex-end;
  }
  .navbar-app .global-search-wrapper {
    /* Grow to fill the centre on wide screens, capped so the wider right-actions
       column can still balance the left nav and keep the search centred; shrinks
       on tight screens so it can't collide with the actions. */
    flex: 1 1 300px;
    max-width: 520px;
  }
}

/* The trailing element is a <button> in the navbar and an .input-group-text
   elsewhere — pill both so the group's right edge matches its left. */
.global-search-wrapper .input-group-text,
.global-search-wrapper .input-group > .btn:last-child {
  border-color: rgba(255, 255, 255, 0.25);
  color: rgba(255, 255, 255, 0.7);
  border-top-right-radius: var(--cd-radius-pill);
  border-bottom-right-radius: var(--cd-radius-pill);
  padding-right: 0.75rem;
}

.global-search-wrapper .form-control {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.25);
  color: var(--cd-text-on-brand);
  border-top-left-radius: var(--cd-radius-pill);
  border-bottom-left-radius: var(--cd-radius-pill);
  padding-left: 0.9rem;
}

.global-search-wrapper .form-control::placeholder {
  color: rgba(255, 255, 255, 0.5);
}

.global-search-wrapper .form-control:focus {
  background: rgba(255, 255, 255, 0.15);
  border-color: rgba(255, 255, 255, 0.4);
  color: var(--cd-text-on-brand);
  box-shadow: none;
}

.global-search-results {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 1055;
  background: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
  max-height: 420px;
  overflow-y: auto;
  margin-top: 2px;
}

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

.global-search-section {
  padding: 0.35rem 0.75rem;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  color: var(--cd-text-muted);
  letter-spacing: 0.05em;
  border-bottom: 1px solid var(--cd-line);
}

.global-search-item {
  display: block;
  padding: 0.5rem 0.75rem;
  text-decoration: none;
  color: var(--cd-text);
  font-size: 0.85rem;
  border-bottom: 1px solid var(--cd-line);
  transition: background 0.1s;
}

.global-search-item:hover,
.global-search-item.active {
  background: var(--cd-surface-sunken);
  color: var(--cd-text);
  text-decoration: none;
}

.global-search-item .search-title {
  font-weight: 500;
}

.global-search-item .search-meta {
  font-size: 0.75rem;
  color: var(--cd-text-muted);
}

.global-search-empty {
  padding: 1rem;
  text-align: center;
  color: var(--cd-text-muted);
  font-size: 0.85rem;
}

/* ─── Omnibar (top-pinned search overlay) ─── */
.cd-omnibar-dialog {
  max-width: 40rem;
  width: 90vw;
  margin: 25vh auto 0; /* pin ~a quarter down; results grow below, input stays put */
}

.cd-omnibar-content {
  overflow: hidden; /* clip the results list to the modal's rounded corners */
}

.cd-omnibar-input-wrap {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.65rem 1.15rem;
}

/* Secret notifications/alerts strip, ArrowUp-revealed above the input (see
   globalSearch.js openNotifPanel). Hidden by default (.d-none) — never part
   of the default zero state, only reachable via the keyboard. */
.cd-omnibar-notif-panel {
  max-height: min(40vh, 320px);
  overflow-y: auto;
  /* --cd-line flips, so the [data-theme="dark"] twin that restated this as #444
     is gone. */
  border-bottom: 1px solid var(--cd-line);
}

.cd-omnibar-icon {
  font-size: 1.1rem;
  color: var(--cd-text-muted);
  flex-shrink: 0;
}

.cd-omnibar-input.form-control {
  border: 0;
  padding: 0.3rem 0;
  font-size: 1.1rem;
  background: transparent;
  box-shadow: none;
}

.cd-omnibar-input.form-control:focus {
  box-shadow: none;
}

/* Reuse the navbar result rows, but in-flow under the input (not an absolute
   dropdown) and sitting on the modal's own background. */
.cd-omnibar .global-search-results {
  position: static;
  z-index: auto;
  max-height: min(60vh, 460px);
  margin-top: 0;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--cd-line);
  border-radius: 0;
  box-shadow: none;
}

/* Loading bar: a 3px sliver of light sweeping under the input while a search is
   in flight (and while more results are still pending). Sticky so it stays put
   under the input as the list scrolls; sits above the first result. */
.cd-omnibar-loader {
  position: sticky;
  top: 0;
  z-index: 3;
  height: 3px;
  overflow: hidden;
  background: transparent;
}

.cd-omnibar-loader::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  height: 3px;
  width: 34%;
  /* -pill, not the literal 3px it was: on a 3px-tall bar those are the same
     paint, and the token says "fully rounded" instead of restating the height. */
  border-radius: var(--cd-radius-pill);
  /* Was a Tailwind blue scanline (#93c5fd / #d4e9ff / rgba(125,180,255,0)) — the
     last off-brand palette in the file, on the global search bar of an all-brick
     app. Like the skeleton shimmer, this keeps a dark twin: the highlight has to
     be LIGHTER than the body in both themes, and no token pair moves that way in
     both (--cd-brick-500 is lighter than --cd-brand in light, darker in dark). */
  background: linear-gradient(90deg,
    transparent, var(--cd-brand) 45%, var(--cd-brick-500) 50%, var(--cd-brand) 55%, transparent);
  filter: blur(0.4px);
  animation: cd-omnibar-scan 1.15s ease-in-out infinite alternate;
}

[data-theme="dark"] .cd-omnibar-loader::before {
  background: linear-gradient(90deg,
    transparent, var(--cd-brand) 45%, var(--cd-brand-active) 50%, var(--cd-brand) 55%, transparent);
}

@keyframes cd-omnibar-scan {
  from { left: 0; }
  to { left: 66%; }
}

@media (prefers-reduced-motion: reduce) {
  .cd-omnibar-loader::before { animation: none; width: 100%; opacity: 0.5; }
}

/* Person peek: a small profile image (or initials circle) beside the name. */
.peek-head-person {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.peek-head-person .peek-head-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.peek-avatar {
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  object-fit: cover;
  background: var(--cd-surface-sunken);
}

.peek-avatar-initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--cd-text);
  text-transform: uppercase;
}

/* ─── Signature / Reply Editor (Quill snow) ─── */

/* Wrapper hides the raw textarea once Quill mounts */
.reply-editor-wrap textarea {
  display: block; /* visible by default; JS hides it after init */
}

/* Give the Quill editor a form-control-like appearance.
   Painting surface/line/text from the tier-3 tokens in the BASE rule is what
   lets the [data-theme="dark"] twin below be deleted — see the note there. */
.reply-editor-wrap .ql-toolbar.ql-snow {
  border: 1px solid var(--cd-line);
  border-bottom: none;
  border-radius: var(--cd-radius-md) var(--cd-radius-md) 0 0;
  background-color: var(--cd-surface-raised);
  color: var(--cd-text);
}

.reply-editor-wrap .ql-container.ql-snow {
  border: 1px solid var(--cd-line);
  border-radius: 0 0 var(--cd-radius-md) var(--cd-radius-md);
  font-size: 0.9rem;
  min-height: 120px;
  background-color: var(--cd-surface-raised);
  color: var(--cd-text);
}

.reply-editor-wrap .ql-editor {
  min-height: 120px;
}

/* Dark-mode COMPOSE legibility (see /js/darkModeComposeContrast.js).
   Quill editors render email-destined HTML whose inline colors are authored for
   a WHITE email background; on the dark canvas the dark ones (signature greys,
   near-black headings, pasted quotes) go invisible. The JS marks each element
   that fails a WCAG contrast check and stores a re-lit color (hue kept, only
   lightness moved) in a custom property. This rule paints it for DISPLAY only —
   the element's real `color` is untouched, so quill.root.innerHTML (what gets
   emailed) and the recipient's white-background render are unchanged. The
   read-only message-body islands are handled separately by darkModeContrast.js. */
[data-theme="dark"] .ql-editor [data-cd-ca] {
  color: var(--cd-ca-color) !important;
}

/* Toolbar glyphs, for EVERY Quill mount and BOTH themes.
   Quill snow ships its own #444 stroke, so this used to exist only as a
   dark-mode override — and was copy-pasted per mount (.reply-editor-wrap,
   .automation-body-editor), so a third mount silently got black-on-dark icons.
   --cd-text-muted already flips, so one unscoped rule replaces both copies and
   covers mounts that don't exist yet. Deliberately NOT scoped to a wrapper. */
.ql-snow .ql-stroke {
  stroke: var(--cd-text-muted);
}

.ql-snow .ql-fill {
  fill: var(--cd-text-muted);
}

.ql-snow .ql-picker {
  color: var(--cd-text-muted);
}

/* ── Workflow/automation action message-body editors (CD-727) ──
   The mount div (.automation-body-editor) is created in automationEdit.js with
   no height, so Quill's height:100% collapsed it to a single line. Give it its
   own full-width line in the flex action row and a form-control-like box with a
   real min-height, matching the reply editor. */
.automation-body-editor {
  flex: 1 1 100%;
}

.automation-body-editor .ql-toolbar.ql-snow {
  border: 1px solid var(--cd-line);
  border-bottom: none;
  border-radius: var(--cd-radius-md) var(--cd-radius-md) 0 0;
  background-color: var(--cd-surface-raised);
  color: var(--cd-text);
}

.automation-body-editor .ql-container.ql-snow {
  border: 1px solid var(--cd-line);
  border-radius: 0 0 var(--cd-radius-md) var(--cd-radius-md);
  font-size: 0.9rem;
  min-height: 140px;
  background-color: var(--cd-surface-raised);
  color: var(--cd-text);
}

.automation-body-editor .ql-editor {
  min-height: 140px;
}

/* The five [data-theme="dark"] rules that stood here are gone: three restated
   the border/surface/text above with cool-grey literals, and two duplicated the
   shared .ql-snow glyph rules in the reply-editor section. Only min-height
   differs between this mount and that one now. */

/* ─── Print Styles ─── */
@media print {

  .navbar-apu,
  #sidebar,
  .app-footer,
  .btn,
  .no-print {
    display: none !important;
  }

  #main-content {
    padding: 0 !important;
  }

  body {
    background: white;
    color: black;
  }
}

/* ── Searchable Dropdown Group Headers ── */

.tag-search-group-header {
  padding: 4px 10px 2px;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--cd-text-muted);
  background: var(--cd-surface-sunken);
  border-bottom: 1px solid var(--cd-line);
  user-select: none;
}

/* ── Notification Bell & Dropdown ── */

.notification-dropdown {
  width: 380px;
  border-radius: var(--cd-radius-md);
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}

.notification-item {
  display: flex;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--cd-line);
  color: var(--cd-text);
  transition: background-color 0.15s;
  cursor: pointer;
}

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

.notification-item:hover {
  background-color: var(--cd-surface-hover);
  text-decoration: none;
}

.notification-item-unread {
  background-color: var(--cd-info-bg);
}

.notification-item-unread:hover {
  background-color: var(--cd-info-bg-hover);
}

.notification-unread-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--cd-brand-fill);
  flex-shrink: 0;
}

/* Notification preferences on profile */
.notif-pref-item {
  transition: background-color 0.15s;
}

.notif-pref-item:hover {
  background-color: var(--cd-surface-hover);
}

.notif-team-label {
  font-size: 0.7rem;
  color: var(--cd-success);
}

/* CD-1797: one column per delivery channel (Email / Bell). The column header and
   every row share .notif-channels/.notif-channel, and the cell width is fixed
   here rather than left to content — a header label is wider than the switch it
   labels, so letting either size the column drifts them out of alignment. */
.notif-channels {
  gap: 0.5rem;
  flex: 0 0 auto;
}

.notif-channel {
  width: 3.25rem;
  flex: 0 0 3.25rem;
}

.notif-channel-head {
  background-color: var(--cd-surface-sunken);
  border-bottom: 1px solid var(--cd-line);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--cd-text-muted);
}

.notif-channel-head .bi {
  font-size: 0.85rem;
  line-height: 1;
  margin-bottom: 0.15rem;
}

/* A channel this notification type has no transport for. Not a disabled switch:
   there is no setting behind it to disable, and a greyed-out switch reads as
   "you could turn this on". */
.notif-channel-na {
  color: var(--cd-text-subtle);
  cursor: help;
}

/* The copy column takes the leftover width; the per-team checkbox list that
   profilePrefs.js injects is given .w-100 so it wraps onto its own line instead
   of squeezing the switches. */
.notif-pref-copy {
  flex: 1 1 16rem;
  min-width: 0;
}

/* Bootstrap's .form-switch indents for a label this switch does not have. */
.notif-channel .form-switch {
  padding-left: 0;
}

.notif-channel .form-switch .form-check-input {
  margin-left: 0;
  float: none;
}

/* My Tasks — shared by the navbar dropdown, the bell's Tasks tab, and the
   My Tasks page. Deliberately mirrors .notification-item so the two lists read
   as the same kind of thing. */
.my-task-item {
  border-bottom: 1px solid var(--cd-line);
  color: var(--cd-text);
  transition: background-color 0.15s;
}

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

.my-task-item:hover {
  background-color: var(--cd-surface-hover);
}

/* Tab strip inside the bell dropdown, shown only when Tasks lives there.
   Its own class, not Bootstrap's .nav-link — the dropdown sits inside
   .navbar-apu, which colours every .nav-link white and would render these
   labels invisible against the dropdown's white background. */
.notif-tabs {
  gap: 0.25rem;
  border-bottom: 1px solid var(--cd-line);
}

.notif-tab-btn {
  border: none;
  border-bottom: 2px solid transparent;
  background: none;
  padding: 0.3rem 0.6rem;
  font-size: 0.8rem;
  color: var(--cd-text);
}

.notif-tab-btn:hover {
  color: var(--cd-brand);
}

.notif-tab-btn.active {
  border-bottom-color: var(--cd-brand);
  font-weight: 600;
}

[data-theme="dark"] .notification-item-unread {
  background-color: rgba(13, 110, 253, 0.1);
}

[data-theme="dark"] .notification-item-unread:hover {
  background-color: rgba(13, 110, 253, 0.15);
}

/* ─── Tom Select - APU brand overrides ─── */

/* Strip Bootstrap form-select chrome from the wrapper div Tom Select creates */
.ts-wrapper.form-select {
  padding: 0 !important;
  border: none !important;
  background-image: none !important;
  box-shadow: none !important;
  font-size: inherit !important;
}

/* Apply our border/radius/font to the visible control. Surface + text come from
   the tier-3 tokens rather than being left to Tom Select's own white/near-black,
   which is what forced the dark-mode block that used to follow this section. */
.ts-control {
  border: 1px solid var(--cd-line) !important;
  border-radius: var(--cd-radius-sm) !important;
  font-size: 0.9rem !important;
  box-shadow: none !important;
  cursor: pointer;
  min-height: unset !important;
  background-color: var(--cd-surface-raised) !important;
  color: var(--cd-text) !important;
}

.ts-control input {
  color: var(--cd-text) !important;
}

/* Focus ring - APU red.
   The ring was `rgba(168, 53, 58, 0.15)` — #A8353A in decimal, i.e. the
   incorrect brick this repo has been correcting elsewhere (real one is
   #9D2E34). An rgba() literal is invisible to scripts/check-css-tokens.js,
   which only scans hex, so it survived the hex sweep unnoticed.
   --cd-focus-ring is the token for this and flips for dark. */
.ts-wrapper.focus .ts-control {
  border-color: var(--cd-focus) !important;
  box-shadow: 0 0 0 0.2rem var(--cd-focus-ring) !important;
}

/* Small variant passthrough */
.ts-wrapper.form-select-sm .ts-control {
  padding-top: 0.25rem !important;
  padding-bottom: 0.25rem !important;
  padding-left: 0.5rem !important;
  font-size: 0.875rem !important;
}

/* Dropdown panel */
.ts-dropdown {
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  font-size: 0.9rem;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  min-width: 100%;
  background-color: var(--cd-surface-raised);
  color: var(--cd-text);
}

.ts-dropdown .option {
  color: var(--cd-text);
}

/* Linked ticket row - dropdown panel can expand, control button cannot.
   Bootstrap5 TomSelect sets min-width:7rem + flex:1 1 auto on the control input,
   and moves it from position:absolute back into flow on open - pin it to zero. */
.add-link-form .ts-dropdown {
  width: max-content;
  min-width: 100%;
}

.add-link-form .ts-control>input {
  flex: 0 0 0 !important;
  min-width: 0 !important;
  margin: 0 !important;
}

.ts-wrapper.ts-dropup .ts-dropdown {
  top: 0;
  bottom: auto;
  transform: translateY(calc(-100% - 2px));
  margin-top: 0;
  box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.1);
}

/* Selected/hovered option */
.ts-dropdown .option.selected,
.ts-dropdown .option:hover,
.ts-dropdown .option.active {
  background-color: var(--cd-brand-fill-hover);
  color: var(--cd-text-on-brand);
}

/* ─── Tom Select - dark mode ───
   Six of the seven rules that lived here are gone: they restated the control's
   and dropdown's surface / border / text with cool-grey literals, which the
   tokenized base rules above now handle in both themes. The dark focus state
   also lifted the control to #333; dropped, because --cd-surface-raised is
   already a distinct panel surface on the warm dark ramp.

   This one rule stays because it is a real design DIVERGENCE, not a restatement:
   light paints hover, active and selected all brand-red, while dark separates
   hover/active (neutral) from selected (brand) — worth keeping, since on a dark
   panel a red hover is hard to tell from the current selection. */
[data-theme="dark"] .ts-dropdown .option:hover,
[data-theme="dark"] .ts-dropdown .option.active {
  background-color: var(--cd-surface-hover) !important;
  color: var(--cd-text) !important;
}

/* ── Ticket Preview Popover ── */
.ticket-preview-popover {
  position: fixed;
  z-index: 1090;
  top: var(--chrome-offset);
  /* overridden by JS when tickets-page-header is present */
  right: 0;
  width: clamp(320px, 40vw, 600px);
  height: calc(100dvh - var(--chrome-offset));
  /* overridden by JS */
  font-size: 1rem;
  background: var(--bs-body-bg);
  border-left: 1px solid var(--bs-border-color);
  box-shadow: -4px 0 24px rgba(0, 0, 0, 0.12);
  overflow: visible;
  transform: translateX(100%);
  transition: transform 0.2s ease;
  pointer-events: none;
}

.ticket-preview-popover.show {
  transform: translateX(0);
  pointer-events: auto;
}

.ticket-preview-popover.d-none {
  display: none !important;
}

.ticket-preview-scroll {
  overflow-y: auto;
  overflow-x: hidden;
  height: 100%;
  background: var(--cd-surface-raised);
  padding: 0.75rem 0.75rem 0.75rem;
}

.ticket-preview-card-header {
  position: relative;
  z-index: 1;
  background: var(--cd-surface-raised);
  border-bottom: 1px solid var(--bs-border-color);
  padding: 0.5rem 0.75rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.ticket-preview-agent {
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The assignee is a picker now, but it still has to read as the header's name
   line rather than as a form control wedged into it. */
button.ticket-preview-agent {
  min-width: 0;
  max-width: 100%;
  padding: 0.15rem 0.4rem;
  border: 1px solid transparent;
  border-radius: var(--cd-radius-sm);
  background: none;
  color: inherit;
  text-align: left;
  font-size: inherit;
}

button.ticket-preview-agent:hover:not(:disabled),
button.ticket-preview-agent[aria-expanded="true"] {
  border-color: var(--bs-border-color);
  background: var(--cd-surface-sunken);
}

button.ticket-preview-agent:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

/* The status chip is a picker too; keep the affordance subtle so the chip
   still reads as a status rather than as a button. */
button.ticket-preview-chip {
  border: 1px solid transparent;
  cursor: pointer;
}

button.ticket-preview-chip:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.ticket-preview-subject {
  padding-top: 0.25rem;
}

.ticket-preview-subject-number {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--bs-secondary-color);
  margin-bottom: 0.2rem;
}

.ticket-preview-subject-title {
  font-weight: 600;
  line-height: 1.4;
}

.ticket-preview-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.5rem;
}

.ticket-preview-chip {
  display: inline-block;
  padding: 0.3em 0.65em;
  border-radius: var(--cd-radius-sm);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1;
}

/* The status chip's colours come from .status-bg-<status> in tokens.css. A
   status with no matching class would otherwise inherit nothing and render
   white-on-white — that is how CD-1711 presented, and it reads as a missing
   ticket rather than a broken chip.
   :where() holds this at ZERO specificity, so any .status-bg-* wins over it
   whatever order the stylesheets load in. main.hbs deliberately loads
   tokens.css after main.css today, but a chip that vanishes if someone
   reorders two <link> tags is not a guard. */
:where(.ticket-preview-chip) {
  background-color: var(--cd-surface-sunken);
  color: var(--bs-body-color);
  border: 1px solid var(--bs-border-color);
}

.ticket-preview-fields {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.2rem 0.75rem;
  margin: 0;
  font-size: 0.8125rem;
}

.ticket-preview-fields dt {
  color: var(--bs-secondary-color);
  font-weight: 500;
}

.ticket-preview-fields dd {
  margin: 0;
  /* Team and category names are admin-authored and can be long; wrap rather
     than widen the fixed-width panel. */
  overflow-wrap: anywhere;
}

.ticket-preview-divider {
  margin: 0.5rem 0 0.75rem;
  border-color: var(--bs-border-color);
  opacity: 1;
}

.ticket-preview-close-tab {
  position: absolute;
  top: 0;
  left: -2.75rem;
  width: 2.75rem;
  height: 2.75rem;
  border: none;
  border-radius: var(--cd-radius-md) 0 0 var(--cd-radius-md);
  background: var(--cd-surface-raised);
  border-top: 1px solid var(--bs-border-color);
  border-left: 1px solid var(--bs-border-color);
  border-bottom: 1px solid var(--bs-border-color);
  box-shadow: -4px 0 8px rgba(0, 0, 0, 0.1);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  color: var(--bs-secondary-color);
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ticket-preview-close-tab:hover {
  color: var(--bs-body-color);
  background: var(--cd-surface-sunken);
}

.ticket-preview-card-body {
  background: var(--cd-surface-raised);
  border-radius: var(--cd-radius-md);
  padding: 0.75rem 1rem 1rem;
  border: 1px solid var(--bs-border-color-translucent);
}

.ticket-preview-description {
  line-height: 1.5;
}

.ticket-preview-description-inner p {
  margin: 0 0 0.5rem;
}

.ticket-preview-description-inner p:last-child {
  margin-bottom: 0;
}

.ticket-preview-description-inner img {
  max-width: 100%;
  height: auto;
}

.ticket-preview-ai-content {
  white-space: pre-wrap;
}

.ticket-preview-messages-header {
  display: flex;
  align-items: center;
  font-weight: 600;
  margin-bottom: 0.375rem;
  padding-bottom: 0.375rem;
  border-bottom: 1px solid var(--bs-border-color-translucent);
}

.ticket-preview-message {
  border-bottom: 1px solid var(--bs-border-color-translucent);
}

.ticket-preview-message:last-child {
  border-bottom: none;
}

.ticket-preview-message.is-internal {
  background: rgba(var(--bs-warning-rgb), 0.06);
  border-radius: var(--cd-radius-md);
}

.ticket-preview-message-header {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.4rem 0;
  cursor: pointer;
  user-select: none;
}

.ticket-preview-message-header:hover {
  color: var(--cd-brand);
}

.ticket-preview-avatar {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: var(--cd-brand-fill);
  color: var(--cd-text-on-brand);
  font-size: 0.7rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  text-transform: uppercase;
}

.ticket-preview-message-author {
  font-weight: 500;
}

.ticket-preview-message-date {
  font-size: 0.75rem;
  white-space: nowrap;
}

.ticket-preview-chevron {
  font-size: 0.7rem;
  transition: transform 0.15s ease;
  flex-shrink: 0;
}

.ticket-preview-message-header[aria-expanded="true"] .ticket-preview-chevron {
  transform: rotate(180deg);
}

.ticket-preview-message-body {
  padding: 0 0 0.5rem 1.875rem;
  line-height: 1.6;
  overflow: hidden;
}

.ticket-preview-message-body img {
  max-width: 100%;
  height: auto;
}

/* Pill outer corners on small button groups (Bootstrap zeros inner corners) */
.btn-group-sm>.btn {
  --bs-btn-border-radius: var(--cd-radius-pill);
}

/* Removable list rows (Team Sharing, Linked Tickets, etc.) */
.removable-row {
  padding: 0.25rem 0.5rem;
  margin: 0 -0.5rem;
  border-radius: var(--cd-radius-sm);
  transition: background-color 0.1s ease;
}

.removable-row:hover {
  background-color: var(--cd-surface-hover);
}

.removable-row .remove-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--cd-text-muted);
  text-decoration: none;
  background: transparent;
  border: 0;
  border-radius: var(--cd-radius-pill);
}

.removable-row .remove-btn:hover,
.removable-row .remove-btn:focus {
  background-color: var(--cd-surface-hover);
  color: var(--cd-danger);
}

/* Mobile - full width */
@media (max-width: 576px) {
  .ticket-preview-popover {
    width: 100%;
  }
}

/* Organization page - department edit toggle */
.dept-edit-toggle {
  transition: background-color 0.15s ease, color 0.15s ease;
}

.dept-edit-toggle:hover,
.dept-edit-toggle:focus-visible {
  background-color: var(--cd-surface-hover);
  color: var(--cd-brand) !important;
  outline: none;
}

/* Organization page - clickable team card hover */
.team-card .card {
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}

.team-card:hover .card,
.team-card:focus-within .card {
  background-color: var(--cd-surface-hover);
  box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.08);
}

/* Organization page - normalize Save button height to match siblings */
.dept-edit-form .btn-sm.btn-apu {
  padding: 0.25rem 0.75rem;
}

/* Organization page - department header bar */
.dept-header {
  background-color: var(--cd-brand-soft);
  border-bottom: 1px solid var(--cd-line);
  border-left: 4px solid var(--cd-brand);
  font-size: 1.3rem;
  cursor: pointer;
}

.dept-header > .bi-building {
  color: var(--cd-brand);
}

/* Organization page - the header's collapse trigger, laid over the whole bar.
   Only the INTERACTIVE children are lifted above it: those must receive their
   own clicks, and being outside the button is what stops them firing the
   collapse. Everything else — the department name, the chevron, the icon, the
   plain badges — deliberately stays under the overlay so clicking any of it
   still toggles the section, which is what the header has always done.
   Both layers are z-index auto, so DOM order decides: the overlay MUST stay
   the header's first child (pinned by orgDeptHeaderStructure.test.js). */
.dept-toggle-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
}

.dept-header a,
.dept-header button:not(.dept-toggle-overlay) {
  position: relative;
}

.dept-toggle-overlay:focus-visible {
  outline: 2px solid var(--cd-brand);
  outline-offset: -2px;
}

/* Organization page - department card (card view) */
.dept-card-icon {
  color: var(--cd-brand);
  font-size: 1.25rem;
  line-height: 1.2;
}

/* Organization page - team count badge (on-brand maroon tint) */
.dept-team-count {
  background-color: var(--cd-brand-fill);
  color: var(--cd-text-on-brand);
  border-color: var(--cd-brand-hover) !important;
}

/* Organization page - shaded accordion body behind team cards */
.department-group .accordion-body {
  background-color: var(--cd-surface-sunken);
}

/* Organization page - collapse chevron (rotates with the header state).
   pointer-events:none is load-bearing, not tidiness: the rotate below is a
   non-none transform, which creates a stacking context and paints the chevron
   ABOVE the absolutely-positioned trigger overlay. Clicking the one control
   that most obviously means "expand/collapse" then did nothing at all. It is
   decoration, so it declines clicks and they fall through to the overlay. */
.dept-chevron {
  display: inline-block;
  line-height: 1;
  transition: transform 0.2s ease;
  pointer-events: none;
}

/* aria-expanded moved off the header and onto the overlay button, so the
   chevron is now a later SIBLING of the state-carrying element, not a child. */
.dept-toggle-overlay[aria-expanded="true"] ~ .dept-chevron {
  transform: rotate(180deg);
}

/* Compact in-form tab toggle (e.g. Customer / CC composite field) */
.field-tabs {
  display: flex;
  gap: 1rem;
  border-bottom: 1px solid var(--bs-border-color);
  margin-bottom: 0.5rem;
}

.field-tab {
  background: none;
  border: 0;
  padding: 0.25rem 0.125rem;
  margin-bottom: -1px;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--bs-body-color);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: border-color 0.15s ease;
}

.field-tab.active {
  border-bottom-color: var(--cd-brand);
}

.new-ticket-form {
  background-color: var(--bs-tertiary-bg);
  padding: 1.2rem;
  border-radius: var(--cd-radius-lg);
}

/* Stack tab panes in a single grid cell so the container sizes to the
   tallest pane and toggling does not push siblings up or down. */
.field-tab-panes {
  display: grid;
}

.field-tab-pane {
  grid-column: 1;
  grid-row: 1;
  visibility: hidden;
  pointer-events: none;
}

.field-tab-pane.active {
  visibility: visible;
  pointer-events: auto;
}

/* ─── New Ticket Slide-Out Panel (custom drawer, replaces Bootstrap offcanvas) ─── */
.new-ticket-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 520px;
  max-width: 100vw;
  background-color: var(--cd-surface-raised);
  color: var(--cd-text);
  border-left: 1px solid var(--cd-line);
  box-shadow: -2px 0 12px rgba(0, 0, 0, 0.08);
  z-index: 1045;
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform 0.3s ease;
  visibility: hidden;
  outline: none;
}

.new-ticket-panel.is-open,
.new-ticket-panel.is-collapsed {
  visibility: visible;
}

.new-ticket-panel.is-open {
  transform: translateX(0);
}

.new-ticket-panel.is-collapsed {
  /* Panel fully off-screen; only the chevron handle (positioned at panel's left
     edge, translated -100%) remains in the viewport. */
  transform: translateX(100%);
}

@media (max-width: 575.98px) {
  .new-ticket-panel {
    width: 100vw;
  }
}

.new-ticket-panel-handle {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translate(-100%, -50%);
  width: 32px;
  height: 64px;
  padding: 0;
  background-color: inherit;
  color: inherit;
  border: 1px solid var(--cd-line);
  border-right: none;
  border-radius: var(--cd-radius-md) 0 0 var(--cd-radius-md);
  box-shadow: -2px 0 6px rgba(0, 0, 0, 0.06);
  cursor: pointer;
  display: none;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  line-height: 1;
  transition: background-color 0.15s ease;
}

.new-ticket-panel.has-draft .new-ticket-panel-handle,
.new-ticket-panel.is-open .new-ticket-panel-handle {
  display: flex;
}

.new-ticket-panel-handle:hover .handle-icon {
  /* Bootstrap Icons ignore font-weight; simulate boldness via stacked text-shadow */
  text-shadow: 0 0 0.6px currentColor, 0 0 0.6px currentColor;
}

.new-ticket-panel-handle:focus-visible {
  outline: 2px solid var(--cd-brand);
  outline-offset: -2px;
}

.new-ticket-panel-handle .handle-icon {
  display: none;
}

.new-ticket-panel.is-open .handle-icon-open {
  display: inline;
}

.new-ticket-panel.is-collapsed .handle-icon-collapsed {
  display: inline;
}

.new-ticket-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem;
  border-bottom: 1px solid var(--cd-line);
  flex-shrink: 0;
}

.new-ticket-panel-title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 500;
  display: flex;
  align-items: center;
}

.new-ticket-panel-discard {
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--cd-radius-pill);
  color: inherit;
  padding: 0.25rem 0.5rem;
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.new-ticket-panel-discard:hover {
  background-color: rgba(220, 53, 69, 0.1);
  color: var(--cd-danger);
}

.new-ticket-panel-discard:focus-visible {
  outline: 2px solid var(--cd-danger);
  outline-offset: 1px;
}

.new-ticket-panel-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 1rem;
}

/* Slide-out panel: the submit control is a fixed footer — a flex child of the
   panel and a sibling of the scrolling body, so it stays put at the bottom and
   never moves while the fields scroll. Its submit button is form-associated
   (form="newTicketForm") since it now sits outside the <form>. */
.new-ticket-panel-footer {
  flex: 0 0 auto;
  padding: 0.75rem 1rem;
  background: var(--cd-surface-raised);
  border-top: 1px solid var(--cd-line);
}

.new-ticket-panel-footer #nt-submit-group {
  display: flex;
  width: 100%;
}

.new-ticket-panel-footer #nt-submit-btn {
  flex: 1 1 auto;
  justify-content: center;
}

.new-ticket-panel-footer #nt-status-toggle {
  flex: 0 0 auto;
}

/* ─── New Ticket custom fields: responsive grid (CD-522) ───
   Custom fields flow into as many columns as the surface width allows —
   multi-column on the wide create tab, a single column in the ~520px
   slide-out panel (260px min guarantees one column there). Multiselect
   fields and any field with help text span the full row for readability.
   Pure layout, so it lives in CSS and adapts to both surfaces with no JS. */
.nt-cf-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0.75rem 1rem;
  align-items: start;
}

.nt-cf-grid .nt-cf-cell--full {
  grid-column: 1 / -1;
}

/* ─── New Ticket custom fields: collapsible panel (CD-522 Slice 2) ───
   Departments with >=5 fields get a full-width toggle in place of the plain
   "Custom Fields" label. Expanded it's a quiet header with a "Minimize"
   affordance; collapsed it's a solid APU-red bar (the cue that fields are
   hidden) and the grid is removed from flow. Asymmetric by design — red only
   appears when there's something hidden to reveal. */
.nt-cf-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  width: 100%;
  margin-bottom: 0.5rem;
  padding: 0.375rem 0.625rem;
  border: 1px solid transparent;
  border-radius: var(--cd-radius-sm);
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.nt-cf-toggle:hover {
  background: var(--cd-surface-hover);
}

.nt-cf-toggle .nt-cf-toggle-action {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  flex: none;
  font-size: 0.85em;
  font-weight: 500;
  opacity: 0.75;
}

/* Collapsed: solid APU-red bar. */
.nt-cf-toggle.is-collapsed,
.nt-cf-toggle.is-collapsed:hover {
  background: var(--cd-brand-fill);
  border-color: var(--cd-brand);
  color: var(--cd-text-on-brand);
}

.nt-cf-toggle.is-collapsed:hover {
  background: var(--cd-brand-hover);
  border-color: var(--cd-brand-hover);
}

.nt-cf-toggle.is-collapsed .nt-cf-toggle-action {
  opacity: 0.9;
}

/* When collapsed, drop the grid from flow entirely. */
.is-cf-collapsed .nt-cf-grid {
  display: none;
}

/* ─── Ticket update indicators (tab dot + banner) ─────────── */

.ticket-tab.has-dot::before {
  content: '';
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background-color: var(--cd-brand-fill);
  flex-shrink: 0;
}

.ticket-update-banner-host {
  flex-shrink: 0;
}

.ticket-update-banner-host:empty {
  display: none;
}

.ticket-update-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  margin: 8px 1.5rem 0;
  background-color: rgba(128, 0, 0, 0.08);
  border: 1px solid rgba(128, 0, 0, 0.18);
  border-left: 4px solid var(--cd-brand);
  border-radius: var(--cd-radius-md);
  color: var(--cd-text);
  font-size: 0.875rem;
  transition: padding 0.2s ease, font-size 0.2s ease;
}

.ticket-update-banner-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
}

.ticket-update-banner-body > i {
  flex-shrink: 0;
}

.ticket-update-banner-text {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  mask-image: linear-gradient(to right, #000 92%, transparent 100%); /* tokens-ok: mask opacity, see .tabs-fade-right */
  -webkit-mask-image: linear-gradient(to right, #000 92%, transparent 100%); /* tokens-ok: mask opacity */
}

.ticket-update-banner-time {
  font-size: 0.8125rem;
}

.ticket-update-banner-close {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border: none;
  background: transparent;
  color: inherit;
  font-size: 1.25rem;
  line-height: 1;
  border-radius: var(--cd-radius-sm);
  cursor: pointer;
}

.ticket-update-banner-close:hover {
  background-color: var(--cd-surface-hover);
}

.ticket-update-banner.compact {
  padding: 4px 10px;
  font-size: 0.8125rem;
  border-left-width: 3px;
}

.ticket-update-banner.compact .bi-arrow-clockwise {
  display: none;
}

/* Only the two rgba tints are a real divergence (the light rule uses a maroon
   tint, this one a rosier, more transparent wash). The other two declarations
   were hand-copies of what the base rule's tokens already resolve to in dark —
   border-left-color: #f08080 against --cd-brand's #E07A7F, and color: #e6e6e6
   against --cd-text's #ECE7DF — so they are dropped, not translated. */
[data-theme="dark"] .ticket-update-banner {
  background-color: rgba(240, 128, 128, 0.12);
  border-color: rgba(240, 128, 128, 0.30);
}

@media (prefers-reduced-motion: reduce) {
  .ticket-update-banner {
    transition: none;
  }
}

/* ─── Impersonation ("acting as") banner ─── */
/* Worst instance of the --cd-text-inverse bug: in dark mode the whole
   "you are acting as <user>" banner was near-black text on the near-black
   inverse surface. The one banner that must never be missable. */
.impersonation-banner {
  background-color: var(--cd-surface-inverse);
  color: var(--cd-text-on-inverse);
  border-bottom: 2px solid var(--cd-warning);
  font-size: 0.875rem;
  line-height: 1.3;
}

.impersonation-banner strong {
  color: var(--cd-warning);
}

.impersonation-banner .btn-light {
  --bs-btn-color: var(--cd-surface-inverse);
}

/* ─── Global search navbar loading indicator ─── */
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* ─── Re-randomize (view icon/color) button: one soft spin per click ─── */
@keyframes cd-spin-once {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.cd-spin-once {
  display: inline-block;
  animation: cd-spin-once 0.5s ease;
}

@media (prefers-reduced-motion: reduce) {
  .cd-spin-once { animation: none; }
}

.search-nav-spinner {
  display: inline-block;
  opacity: 0;
  animation:
    spin 0.6s linear infinite,
    cd-spinner-appear 0.15s linear 1s forwards;
}

/* ─── Delayed spinners: nothing paints for loads under 1s ─── */
/* Every async region uses Bootstrap's .spinner-border as its busy indicator;
   a spinner that flashes for a 200ms fetch reads as jank. Hold spinners
   invisible for 1s — CSS animations restart whenever the element is inserted
   or re-displayed, so the delay re-arms on every load with no JS changes.
   Button spinners are exempt: they confirm a click registered (and the
   button label changes with them). */
.spinner-border {
  opacity: 0;
  animation:
    cd-spinner-appear 0.15s linear 1s forwards,
    spinner-border var(--bs-spinner-animation-speed, 0.75s) linear infinite;
}

button .spinner-border,
.btn .spinner-border {
  opacity: 1;
}

@keyframes cd-spinner-appear {
  to { opacity: 1; }
}

/* ─── Busy regions: the number on screen is not the current one ─── */
/* A region that is fetching still shows its PREVIOUS values, and they look
   exactly as authoritative as fresh ones. That is the whole problem: a reader
   cannot tell a settled 0 from a stale 0, or from one that is about to change.
   Dimming says "this is being replaced" without a word of copy.

   Dimmed rather than skeletonised, the same call CD-1873 made for the in-flight
   reply bubble: the value IS real and a moment ago it was correct, so it should
   stay readable. A skeleton is for when there is nothing to show at all.
   `.skeleton-line` below remains the tool for that case.

   The 1s delay is the delayed-spinner rule above, for the same reason and by
   the same mechanism: a 200ms fetch that dims and undims reads as a flicker, and
   a CSS animation re-arms whenever the element is inserted or the attribute
   flips, so no JS has to know about the timing. Keep the two delays equal — they
   describe one event, and a dim arriving before its spinner reads as a fault.

   `aria-busy` is the trigger because four modules already set it
   (analyticsActions, replyPending, ticketTags, scopeTabFilterAjax) and nothing
   was listening. The state was already being tracked; it just had no visual
   form. It also keeps the semantic channel primary, which matters because
   opacity alone lowers contrast and cannot be the only signal. */
[aria-busy="true"] {
  animation: cd-busy-dim 0.15s linear 1s forwards;
}

/* The pending reply already dims itself to 0.6 and also sets aria-busy, so
   without this it would compound to ~0.33 and read as disabled rather than
   in-flight. One dim per element. */
.comment-pending[aria-busy="true"] {
  animation: none;
}

@keyframes cd-busy-dim {
  to { opacity: 0.55; }
}

/* ─── Provisional values: real, settled, and not the one you asked for ─── */
/* The third state, and the one with no visual form until now. A value can be
   finished loading AND still not answer the question: `session.visit_minutes`
   withholds its newest UTC day until 00:31 so the 30-minute successor band is
   observable, so the median on screen is the latest COMPLETE day standing in
   for today's.

   That is not busy (nothing is going to replace it) and not missing (the number
   is real), so neither of the treatments above fits. It gets a tint instead:
   full opacity, because the value is trustworthy for what it is, plus a mark
   saying it is a stand-in.

   Deliberately NOT a dim. Dim means "this is changing"; a reader who learns
   that and then sees a dimmed value that never changes has been taught
   something false. One meaning per treatment.

   The copy beside it stays. The tint is the non-verbal channel — a reader
   skimming numbers should be able to SEE which one carries a caveat without
   reading the caveat, and the caveat is still there when they look. Tint alone
   is never the only signal: it is a background colour, so it must always
   travel with text a screen reader can reach. */
.cd-provisional {
  background: var(--cd-warning-bg);
  border-radius: var(--cd-radius-sm);
  padding: 0 0.25rem;
}

/* ─── Search loading skeleton & tiered-results banner ─── */
/* THESE VALUES LOOK BACKWARDS AND ARE NOT. Do not "fix" them by swapping.
   The consumers set `background-size: 200% 100%`, so the image is WIDER than its
   box, and a percentage background-position resolves as
   `(container_width - image_width) * position`. That first term is NEGATIVE
   here, so a RISING percentage pushes the image further LEFT. Counting down from
   200% to -200% is what sweeps the highlight left-to-right.
   It shipped the intuitive way round and swept right-to-left for real (CD-2046).
   Measured, not reasoned: `npm run verify:shimmer-direction` samples the painted
   pixels. Same trap in `tabFxSheen` below. */
@keyframes skeleton-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* The shimmer needs a base and a LIGHTER highlight, and it keeps its dark twin
   for that reason: no single token pair moves the same direction in both themes
   (--cd-surface-sunken is lighter than --cd-line in light and darker in dark),
   so the highlight would invert and the sweep would read as a dark smear. */
.skeleton-line {
  height: 1rem;
  border-radius: var(--cd-radius-sm);
  background: linear-gradient(90deg, var(--cd-warm-200) 25%, var(--cd-warm-100) 50%, var(--cd-warm-200) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s infinite;
  width: 100%;
  margin-bottom: 0.5rem;
}

.skeleton-line--wide { width: 85%; }
.skeleton-line--medium { width: 60%; }

.skeleton-card {
  height: 5rem;
  border-radius: var(--cd-radius-md);
  background: linear-gradient(90deg, var(--cd-warm-200) 25%, var(--cd-warm-100) 50%, var(--cd-warm-200) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s infinite;
  margin-bottom: 0.75rem;
}

.search-loading-banner {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--cd-brand-soft);
  border: 1px solid var(--cd-brand-soft);
  border-radius: var(--cd-radius-md);
  padding: 0.5rem 0.75rem;
  font-size: 0.875rem;
  color: var(--cd-brand-hover);
  margin-bottom: 0.75rem;
}

/* Kept, and tokenized: --cd-line-strong is LIGHTER than --cd-surface-hover on
   the dark ramp, which is the direction a shimmer highlight has to move. */
[data-theme="dark"] .skeleton-line,
[data-theme="dark"] .skeleton-card,
[data-theme="dark"] .search-shell-count {
  background: linear-gradient(90deg, var(--cd-surface-hover) 25%, var(--cd-line-strong) 50%, var(--cd-surface-hover) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s infinite;
}

/* The [data-theme="dark"] .search-loading-banner rule is deleted outright. Its
   three declarations were all already handled by the base rule's tokens
   (--cd-brand-soft and --cd-brand-hover both flip), and two of them were
   rgba(168, 53, 58, …) — the incorrect brick yet again. It turned up SIX times
   in this file as an rgba() literal, plus two hex copies in the login/hero
   gradient, and outside CSS in analytics.js and layouts/print.hbs. */

/* Freeze the skeleton shimmer under reduced-motion — a static grey block
   still communicates "loading" without the moving highlight. */
@media (prefers-reduced-motion: reduce) {
  .skeleton-line,
  .skeleton-card,
  .search-shell-count,
  [data-theme="dark"] .skeleton-line,
  [data-theme="dark"] .skeleton-card,
  [data-theme="dark"] .search-shell-count {
    animation: none;
    background-position: 0 0;
  }
}

/* ─── Navigating-to-search indicator (/js/searchNavIndicator.js) ─── */
/* Enter in the navbar search NAVIGATES to /search/results, and that query can
   run for seconds on the full corpus (ADR-0018). The browser keeps showing the
   OUTGOING document until the new one paints, so the affordance is painted here
   — an opaque layer over the content region, not a dimmed scrim, so it reads as
   "this page is being replaced" rather than "a dialog is open". */
.cd-search-nav {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 12rem;
  padding: 1rem;
  background: var(--cd-surface);
}

/* No content region to anchor to (or it's hidden — e.g. ticket tab mode puts
   d-none on #main-content): cover the viewport below the navbar instead. */
.cd-search-nav--fixed {
  position: fixed;
  /* `inset: auto` used to sit BELOW these four and reset every one of them, so
     this overlay was not positioned by them at all — a fixed box with auto
     insets paints at its static position, wherever in the flow it happens to be
     appended. The `top` the ticket reported as wrong was dead code (CD-1985). */
  top: var(--chrome-offset);
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1040;
}

.cd-search-nav-card {
  width: min(400px, 92%);
  text-align: center;
  background: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  box-shadow: var(--cd-shadow-md);
  padding: 1.4rem 1.15rem;
}

.cd-search-nav-card .cd-search-nav-q {
  font-weight: 600;
  font-size: 0.95rem;
  margin: 0.55rem 0 0.15rem;
  overflow-wrap: anywhere;
}

.cd-search-nav-card .cd-search-nav-sub {
  font-size: 0.8rem;
  color: var(--cd-text-muted);
  margin: 0;
}

.cd-search-nav-card .cd-search-nav-time {
  margin-top: 0.85rem;
  font-size: 0.75rem;
  color: var(--cd-text-subtle);
  font-variant-numeric: tabular-nums;
}

/* Exempt from the 1s delayed-spinner rule above, on the same grounds as button
   spinners: this card is painted at the instant the agent presses Enter and
   exists only to confirm that the search started. A spinner that stays invisible
   for its first second is exactly the "did anything happen?" gap being fixed. */
.cd-search-nav-card .spinner-border {
  opacity: 1;
}

/* Past SLOW_AFTER_MS the wait stops being routine — say so, so an agent doesn't
   assume it hung and press Enter again. */
.cd-search-nav-card.is-slow .cd-search-nav-sub { color: var(--cd-warning-fg); }

.cd-search-nav-card.is-slow .cd-search-nav-time {
  color: var(--cd-warning-fg);
  font-weight: 600;
}

/* The /search/results shell: the region as it is BEFORE the results fetch lands
   (CD-2085). Positioned because .cd-search-nav is absolute over it — putting
   the containing block on this wrapper rather than on #tickets-results keeps
   the results swap total (the whole wrapper goes) and leaves the results
   region's own positioning alone. Only the BODY is covered: the shell's tab bar
   sits outside it so the tabs hold their place and stay usable during the wait. */
.search-shell-body { position: relative; }

/* The skeleton exists to give the region a plausible height behind an opaque
   Loader, not to be read. Dimmed so it never competes with the card on top. */
.search-shell-skeleton { opacity: 0.5; }

/* The one thing the shell cannot know is the counts. Hold their width with a
   shimmer rather than a guessed number, so the badge doesn't resize when the
   real one lands.

   Deliberately NOT `.badge.bg-secondary`: Bootstrap sets that background-color
   with `!important`, so a rule here is simply discarded and only the transparent
   text survives — a flat grey chip that looks like a rendering fault. The shell's
   badges carry no bg-* class at all, and the shimmer is defined here instead. */
.search-shell-count {
  background: linear-gradient(90deg, var(--cd-warm-200) 25%, var(--cd-warm-100) 50%, var(--cd-warm-200) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s infinite;
  color: transparent;
}



/* ─── Skeleton frames (CdSkeleton — /js/skeletons.js) ─── */
/* Frame layouts for the shared skeleton builders; the shimmering primitives
   (.skeleton-line / .skeleton-card, incl. dark theme) are defined above. */
.skeleton-card--sm { height: 3rem; }
.skeleton-card--lg { height: 8rem; }

/* Ticket list: table-ish rows */
.skeleton-list { padding: 0.25rem 0; }

.skeleton-list-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.85rem 1rem;
  border-bottom: 1px solid var(--cd-line);
}

.skeleton-list-row:last-child { border-bottom: none; }
.skeleton-list-row .skeleton-line { margin-bottom: 0; }
.skeleton-line--chip { width: 4.5rem; flex: none; }
.skeleton-line--grow { flex: 1 1 auto; }
.skeleton-line--w20 { width: 20%; flex: none; }
.skeleton-list-row:nth-child(even) .skeleton-line--grow { max-width: 60%; }

/* Global-search dropdown rows (CdSkeleton.searchRows — globalSearch.js).
   Box model mirrors .global-search-item (0.5rem 0.75rem padding, 1px bottom
   rule) and the line heights mirror its .search-title / .search-meta font
   metrics, so the ranked results land in the same footprint. Change a metric on
   .global-search-item → change it here too. */
.skeleton-search-row {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--cd-line);
}

.skeleton-search-row:last-child { border-bottom: none; }
.skeleton-line--gs-title { height: 0.95rem; margin-bottom: 0.3rem; }
.skeleton-line--gs-meta { height: 0.75rem; margin-bottom: 0; opacity: 0.65; }

/* Ticket card grid: mode-aware card view (mirrors .ticket-card structure).
   The wrapper reuses .ticket-cards-container so it inherits the real grid +
   gap, so real cards land in the same footprint without reflow. */
.skeleton-card-grid { padding: 0; }

/* Box model + line heights below mirror .ticket-card / .ticket-card-top /
   .ticket-card-fields (and the font metrics of the text inside them), so a
   skeleton card is the same height as the real card that replaces it. Change a
   card metric there → change it here (and CARD_H in skeletons.js). */
.skeleton-scard {
  background-color: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  padding: 0;
  overflow: hidden;
}

.skeleton-scard .skeleton-line { margin-bottom: 0; }

.skeleton-scard-top {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.4rem 0.75rem;
}

/* Bulk-select checkbox beside the number/badge stack (.ticket-card-id-status) */
.skeleton-scard-idstatus {
  flex: none;
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
}

.skeleton-line--check {
  height: 1rem;
  width: 1rem;
  flex: none;
}

/* Ticket number stacked above the status badge (.ticket-card-id-inner). */
.skeleton-scard-id {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 64px;
}

.skeleton-line--num { height: 1.1rem; width: 3.5rem; }
.skeleton-line--badge { height: 1.15rem; border-radius: var(--cd-radius-pill); }

/* Title + description (.ticket-card-body) */
.skeleton-scard-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.skeleton-line--title { height: 1.25rem; width: 45%; }
.skeleton-line--desc { height: 1.05rem; width: 80%; }

/* Created / updated relative ages (.ticket-card-ages) */
.skeleton-scard-ages {
  flex: none;
  display: flex;
  gap: 0.5rem;
  padding-top: 1px;
}

.skeleton-line--age { height: 1.05rem; width: 4rem; }

/* Inline-edit field row (.ticket-card-fields): star cell + label/value cells */
.skeleton-scard-fields {
  display: flex;
  align-items: stretch;
  border-top: 1px solid var(--cd-line);
  overflow: hidden;
}

.skeleton-scard-field {
  flex: 0 1 auto;
  min-width: 150px;
  padding: 0.25rem 1.25rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.125rem;
}

/* The real star cell is sized by its icon button (64px wide); pin the same
   width so the label/value cells after it line up with the real card's. */
.skeleton-scard-field--star {
  flex: 0 0 64px;
  min-width: 0;
  align-items: center;
  justify-content: center;
  padding: 0 0.25rem;
  border-right: 1px solid var(--cd-line);
}

.skeleton-line--star { height: 1rem; width: 1rem; border-radius: 50%; }
.skeleton-line--flabel { height: 1.2rem; width: 3.5rem; }
.skeleton-line--fvalue { height: 1.8rem; width: 6rem; }

/* Full ticket frame: left thread + right sidebar */
.skeleton-ticket { padding: 1rem 1.5rem; }

.skeleton-ticket-body {
  display: flex;
  gap: 1.5rem;
  margin-top: 0.75rem;
}

.skeleton-ticket-left {
  flex: 1 1 auto;
  min-width: 0;
}

.skeleton-ticket-sidebar { flex: 0 0 300px; }

@media (max-width: 991px) {
  .skeleton-ticket-sidebar { display: none; }
}

/* Preview sidebar / widget bodies */
.skeleton-preview { padding: 1rem; }

.skeleton-widget-row {
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--cd-line);
}

.skeleton-widget-row:last-child { border-bottom: none; }
/* "Source" (raw HTML) toolbar toggle + the textarea it reveals (CdEditor source feature) */
.ql-snow .ql-toolbar button.ql-source,
button.ql-source {
  display: inline-flex !important;
  align-items: center;
  width: auto !important;
  padding: 0 6px !important;
  font-size: 0.8125rem;
  white-space: nowrap;
  /* Same token as the shared .ql-snow glyph rules, so the text toggle and the
     icon buttons beside it are one weight. */
  color: var(--cd-text-muted);
}

.ql-snow .ql-toolbar button.ql-source:hover,
button.ql-source:hover,
button.ql-source.ql-active {
  color: var(--cd-brand);
}

.signature-source-area {
  width: 100%;
  min-height: 160px;
  box-sizing: border-box;
  padding: 8px;
  font-family: monospace;
  font-size: 13px;
  border: 1px solid var(--cd-line);
  border-top: none;
  border-radius: 0 0 var(--cd-radius-md) var(--cd-radius-md);
  background-color: var(--cd-surface-raised);
  color: var(--cd-text);
}

/* All three [data-theme="dark"] rules that stood here are gone. Two restated
   the muted colour and the textarea's surface/border/text with cool-grey
   literals. The third re-declared the hover colour as #E07075 — a hand-copy of
   what --cd-brand ALREADY resolves to in dark (#E07A7F), so it had been a no-op
   with a stale value in it. */

/* {{ placeholder autocomplete popup (CdEditor features.placeholders) */
.cd-ph-menu {
  position: absolute;
  z-index: 1080; /* above the editor content, below Bootstrap modals (1055+backdrop) */
  min-width: 260px;
  max-width: 420px;
  max-height: 240px;
  overflow-y: auto;
  padding: 4px 0;
  background-color: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
}

.cd-ph-item {
  display: block;
  width: 100%;
  padding: 5px 10px;
  border: none;
  background: transparent;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
}

.cd-ph-item:hover,
.cd-ph-item.active {
  background-color: var(--cd-brand-soft);
}

.cd-ph-token {
  display: inline-block;
  margin-right: 8px;
  font-size: 12px;
  color: var(--cd-brand);
}

.cd-ph-label {
  font-size: 12px;
  color: var(--cd-text-muted);
}

/* Profile settings explainer panel: fixed width to the right on desktop, full
   width (stacked) on mobile where the parent isn't a flex container. */
@media (min-width: 992px) {
  .settings-explain {
    flex: 0 0 360px;
  }
}

/* Quill focus ring. The global `:focus-visible` outline (tokens.css) lands on the
   inner `.ql-editor`, which sits inside the container's padding — so it draws a
   red box partly around the text instead of framing the editor. Suppress it and
   turn the whole box (toolbar + container) red on focus, mirroring the
   `.form-control:focus` border treatment. `:has()` reaches the toolbar (a prior
   sibling of the focused container); placed late so it wins per-editor borders. */
.ql-editor:focus,
.ql-editor:focus-visible {
  outline: none;
}
.ql-container.ql-snow:focus-within,
.ql-toolbar.ql-snow:has(+ .ql-container.ql-snow:focus-within) {
  border-color: var(--cd-focus);
}

/* ── Animated Tabs (opt-in personal setting; `tab-fx` class on <html>) ─────────
   A cursor-tracked gradient plus a slow dwell ramp on hover: 30s to build up,
   350ms to restore. The asymmetry is pure CSS — the 30s duration lives on the
   :hover state (entering ramps slowly) and the 350ms on the base state (leaving
   restores quickly). tabFx.js feeds --tab-mx/--tab-my; registering the custom
   props lets them (and the intensity) interpolate. */
@property --tab-mx { syntax: '<length-percentage>'; inherits: false; initial-value: 50%; }
@property --tab-my { syntax: '<length-percentage>'; inherits: false; initial-value: 50%; }
@property --tab-fx-i { syntax: '<number>'; inherits: false; initial-value: 1; }

.tab-fx .thread-tab-bar,
.tab-fx .nav-tabs,
.tab-fx .nav-pills {
  gap: 4px; /* ≥4px so a tab never touches its left/right neighbour */
}

/* .ticket-tabs / .ticket-tab are deliberately absent from the gap rule above and
   this margin-top: the ticket strip's gap and top offset are owned by
   --tab-spacing, a user preference, and the bar's padding supplies the top gap.
   Pinning 4px here would freeze the Tab Spacing slider for everyone now that the
   fx defaults on, and would make the gap above a tab disagree with the gap beside
   it. The fx keeps its sheen; the user keeps their spacing. */
.tab-fx .thread-tab,
.tab-fx .nav-tabs .nav-link,
.tab-fx .nav-pills .nav-link {
  margin-top: 4px; /* ≥4px clear of the area above */
}

.tab-fx .ticket-tab,
.tab-fx .new-ticket-tab-btn,
.tab-fx .thread-tab,
.tab-fx .nav-tabs .nav-link,
.tab-fx .nav-pills .nav-link {
  background-image:
    radial-gradient(circle at var(--tab-mx) var(--tab-my),
      rgba(157, 46, 52, calc(0.11 * var(--tab-fx-i))), transparent 62%),
    linear-gradient(115deg, transparent 35%, rgba(157, 46, 52, 0.05) 50%, transparent 65%);
  background-size: 100% 100%, 250% 100%;
  background-repeat: no-repeat;
  animation: tabFxSheen 7s linear infinite;
  transition:
    --tab-mx 140ms linear, --tab-my 140ms linear,
    --tab-fx-i 350ms ease, filter 350ms ease,
    color var(--transition-speed) ease, border-color var(--transition-speed) ease,
    background-color var(--transition-speed) ease;
}

.tab-fx .ticket-tab:hover,
.tab-fx .new-ticket-tab-btn:hover,
.tab-fx .thread-tab:hover,
.tab-fx .nav-tabs .nav-link:hover,
.tab-fx .nav-pills .nav-link:hover {
  --tab-fx-i: 2.2;
  filter: contrast(1.14) saturate(1.12);
  /* Slow, deliberate build-up while the cursor rests; the glide stays snappy. */
  transition:
    --tab-mx 140ms linear, --tab-my 140ms linear,
    --tab-fx-i 30s linear, filter 30s linear,
    color var(--transition-speed) ease, border-color var(--transition-speed) ease,
    background-color var(--transition-speed) ease;
}

/* Second layer is `background-size: 250% 100%` — wider than the box — so this
   counts DOWN for the same reason `skeleton-shimmer` does: see the note there.
   It ran right-to-left, opposite the skeleton shimmer once that was corrected,
   and two shimmers travelling different ways on one page is worse than one
   travelling the wrong way (CD-2046). Layer 1 is the cursor glow and holds. */
@keyframes tabFxSheen {
  from { background-position: 0 0, 160% 0; }
  to   { background-position: 0 0, -60% 0; }
}

/* Respect reduced-motion: keep the (static, centred) gradient, drop the sheen,
   the glide, and the 30s ramp so hover intensity applies instantly. */
@media (prefers-reduced-motion: reduce) {
  .tab-fx .ticket-tab,
  .tab-fx .new-ticket-tab-btn,
  .tab-fx .thread-tab,
  .tab-fx .nav-tabs .nav-link,
  .tab-fx .nav-pills .nav-link,
  .tab-fx .ticket-tab:hover,
  .tab-fx .new-ticket-tab-btn:hover,
  .tab-fx .thread-tab:hover,
  .tab-fx .nav-tabs .nav-link:hover,
  .tab-fx .nav-pills .nav-link:hover {
    animation: none;
    transition: none;
  }
}

/* CD-716 — link editor popover (insert/edit) + suppress Quill's stock tooltip.
   There is exactly one link UI: our popover. Hiding .ql-tooltip is defensive —
   overriding the toolbar handler already stops the button from opening it. */
.ql-snow .ql-tooltip {
  display: none !important;
}

/* CD-1962 — a popover mounted INSIDE a Bootstrap modal (which is where the KB
   article editor lives) switches to fixed positioning: `.modal` scrolls
   independently of the page, so page coordinates would drift as the dialog
   scrolls. mountPopover in editorShared.js sets this class; placePopover reads it
   to decide whether to add the scroll offsets. See that comment for why the
   popover has to be in the modal at all — the focus trap. */
.cd-pop-fixed {
  position: fixed;
}

.cd-link-pop {
  position: absolute;
  z-index: 2000; /* above Bootstrap modals (~1060) for reply-in-modal cases */
  display: none;
  min-width: 240px;
  padding: 10px;
  background: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);
  font-size: 0.8125rem;
}

.cd-link-field {
  display: block;
  margin-bottom: 6px;
  color: var(--cd-text);
}

.cd-link-field input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin-top: 2px;
  padding: 4px 6px;
  font-size: 0.8125rem;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-sm);
}

.cd-link-field input:focus {
  outline: none;
  border-color: var(--cd-brand);
}

.cd-link-err {
  min-height: 0;
  margin-bottom: 4px;
  color: var(--cd-brand);
}

.cd-link-err:empty {
  display: none;
}

.cd-link-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}

.cd-link-actions button {
  padding: 3px 10px;
  font-size: 0.8125rem;
  line-height: 1.2;
  background: var(--cd-surface-sunken);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-pill);
  cursor: pointer;
}

.cd-link-actions .cd-link-apply {
  background: var(--cd-brand-fill);
  border-color: var(--cd-brand);
  color: var(--cd-text-on-brand);
}

.cd-link-actions .cd-link-remove {
  margin-right: auto; /* push Remove to the left, Apply/Cancel to the right */
  color: var(--cd-brand);
}

.cd-link-actions button:hover {
  filter: brightness(0.96);
}

/* CD-714 — inline image resize popover (Gmail-style presets) */
.ql-editor img {
  cursor: pointer;
}

.cd-img-resize-pop {
  position: absolute;
  z-index: 2000; /* above Bootstrap modals (~1060) for reply-in-modal cases */
  display: none;
  gap: 2px;
  padding: 3px;
  background: var(--cd-surface-raised);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

.cd-img-resize-btn {
  padding: 3px 8px;
  font-size: 0.8125rem;
  line-height: 1.2;
  color: var(--cd-text);
  background: transparent;
  border: none;
  border-radius: var(--cd-radius-pill);
  cursor: pointer;
  white-space: nowrap;
}

.cd-img-resize-btn:hover {
  background: var(--cd-brand-soft);
  color: var(--cd-brand);
}

.cd-img-resize-btn.is-active {
  background: var(--cd-brand-fill);
  color: var(--cd-text-on-brand);
}

/* ---------------------------------------------------------------------------
   Bulk edit (CD-231): row-select checkbox column + floating action bar.
   --------------------------------------------------------------------------- */
.bulk-select-col {
  width: 1%;
  text-align: center;
  white-space: nowrap;
}

.ticket-card .bulk-select {
  margin: 0;
  flex-shrink: 0;
  /* Align the checkbox to the ticket-number line it sits beside (CD-859). */
  margin-top: 1px;
}

/* Row highlight while selected (JS toggles .bulk-row-selected on the tr/card).
   Animate the background in/out; duration scales with the user's animation-speed
   preference (--cd-anim-scale: 0 = off … 1 = full, default 1). */
.ticket-row {
  transition: background-color calc(0.28s * var(--cd-anim-scale, 1)) ease;
}

.ticket-row.bulk-row-selected,
tr.bulk-row-selected {
  background: var(--cd-brand-soft);
}

/* OS "reduce motion" wins over the preference — never animate rows then. */
@media (prefers-reduced-motion: reduce) {
  .ticket-row { transition: none; }
}

/* Shared floating action bar — used as-is by the Trash "Restore selected" bar
   (trash-bulk-bar.hbs), which sits at the bottom of the page. The editable
   list's bulk bar (#ticketBulkBar) overrides this into a sticky top band below
   (CD-859). */
.ticket-bulk-bar {
  position: fixed;
  z-index: 1085;
  left: 50%;
  bottom: 1.5rem;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 1rem;
  max-width: calc(100vw - 2rem);
  flex-wrap: wrap;
  padding: 0.6rem 1rem;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--cd-radius-md);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
}

/* CD-859: the editable list's bulk bar becomes a sticky top band that replaces
   the page-header. Its .ticket-bulk-bar-row / -drawer own the inner layout, so
   it fully overrides the shared floating base above. Scoped to the id so the
   trash restore bar keeps floating at the bottom. */
#ticketBulkBar {
  position: sticky;
  top: 0;
  left: auto;
  right: auto;
  bottom: auto;
  transform: none;
  z-index: 30;
  display: block;
  flex-wrap: initial;
  gap: 0;
  max-width: none;
  margin: 0 -2rem 1rem;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: var(--cd-surface-raised);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}

.ticket-bulk-bar.d-none {
  display: none !important;
}

/* Floating (trash) bar: full-width on very small screens. #ticketBulkBar's
   id-scoped rules above keep it unaffected. */
@media (max-width: 575.98px) {
  .ticket-bulk-bar {
    left: 1rem;
    right: 1rem;
    transform: none;
  }
}

/* Single control row: never wraps — overflows horizontally on narrow screens.
   min-height matches the default .tickets-page-header band (1.5rem + 0.75rem
   padding + mb-2 around its ~31px control row = 4.6875rem, see ~line 1761), so
   this bar occupies the same height as the header it replaces on selection. That
   keeps the takeover height-neutral — otherwise the list, and the checkbox just
   clicked, shift ~11px under the cursor and a second click opens the ticket.
   Content stays vertically centered via align-items. Keep in sync with
   .tickets-page-header. */
.ticket-bulk-bar-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 4.6875rem;
  padding: 0.75rem 2rem;
  flex-wrap: nowrap;
  overflow-x: auto;
}

/* Expand-below drawer: mixed-dept hint + custom-fields panel. */
.ticket-bulk-bar-drawer {
  padding: 0 2rem;
}

.ticket-bulk-bar-count {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  white-space: nowrap;
}

.ticket-bulk-bar-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: nowrap;
}

/* Keep controls at full width and let the row scroll, rather than letting flex
   squeeze a control until its label wraps. */
.ticket-bulk-bar-count,
.ticket-bulk-bar-actions {
  flex-shrink: 0;
}

.ticket-bulk-bar-actions > * {
  flex-shrink: 0;
  white-space: nowrap;
}

/* While a selection is active the bulk bar takes over the header band: hide the
   title/controls row and collapse the header's own spacing so no empty strip
   remains. Targets a dedicated hook (.tickets-page-header-main), not the header's
   internal .d-flex shape, so header markup can change without silently breaking
   the takeover. */
body.is-bulk-active .tickets-page-header-main {
  display: none !important; /* beats Bootstrap's .d-flex { display: flex !important } */
}

/* Drop the header out of the sticky layer while it's replaced. Otherwise an
   expanded #ticketFilters panel (sticky top:0) would collide with the sticky
   bulk bar (also top:0); static lets the filter panel sit below the bar and
   scroll away normally instead of hiding behind it. */
body.is-bulk-active .tickets-page-header {
  position: static;
  padding-top: 0;
  padding-bottom: 0;
  margin-bottom: 0;
  box-shadow: none;
}

/* Same remedy, second reason (AF-11/AF-12). The advanced filter panel renders
   INSIDE this sticky header too — ticket-filters.hbs puts it in #ticketFilters
   — and a sticky box never gets a scrollbar of its own. So once a few
   conditions made the header taller than the viewport, everything below the
   fold (Apply and Close included) was reachable only by scrolling the ticket
   LIST underneath almost to its end, because the list is the only thing making
   the page long. On a short list it was unreachable at all.

   Leaving the sticky layer for as long as the panel is open lets the panel
   scroll with the page like ordinary content. Cost, accepted: the title and the
   sort control scroll away while composing — which is what any non-sticky
   header does — and the panel's own action row stays pinned instead
   (.cd-adv-actions). advancedFilterBuilder.js's paintPanelVisibility owns the
   class and re-applies it on every list swap; its toggleOpen compensates the
   scroll position so the header does not visibly jump as it goes static. */
body.is-adv-open .tickets-page-header {
  position: static;
}

.ticket-bulk-bar .bulk-action-input {
  width: auto;
  min-width: 9rem;
}

/* Note in the drawer (e.g. mixed-department hint). */
.ticket-bulk-bar-hint {
  padding-bottom: 0.75rem;
  font-size: 0.8125rem;
  color: var(--cd-text-muted);
}

.ticket-bulk-bar-hint.d-none {
  display: none !important;
}

/* Custom-fields sub-panel (PR3): drawer block under the action row. */
.ticket-bulk-cf {
  border-top: 1px solid var(--bs-border-color);
  padding-top: 0.5rem;
  padding-bottom: 0.75rem;
  max-height: 40vh;
  overflow-y: auto;
}

.ticket-bulk-cf.d-none {
  display: none !important;
}

.ticket-bulk-cf-head {
  font-weight: 600;
  font-size: 0.8125rem;
  margin-bottom: 0.4rem;
}

.ticket-bulk-cf-note {
  font-weight: 400;
  color: var(--cd-text-muted);
}

.ticket-bulk-cf-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}

.ticket-bulk-cf-row .bulk-cf-label {
  min-width: 8rem;
  font-size: 0.875rem;
}

.ticket-bulk-cf-row .bulk-cf-input {
  flex: 1;
  min-width: 10rem;
}

/* A field is greyed until its "set" checkbox is ticked. */
.ticket-bulk-cf-row:not(.is-active) .bulk-cf-input {
  opacity: 0.5;
  pointer-events: none;
}

/* Over-long names (e.g. a spam string jammed into the name field) are clipped
   to a reasonable width and scroll on hover/focus so they can't blow out the
   table column. Short names never get this wrapper (see helpers/nameMarquee).
   28ch mirrors NAME_DISPLAY_LIMIT — keep them in sync. */
.name-marquee {
  display: inline-block;
  max-width: 28ch;
  overflow: hidden;
  white-space: nowrap;
  vertical-align: bottom;
  cursor: default;
  -webkit-mask-image: linear-gradient(90deg, #000 88%, transparent); /* tokens-ok: mask opacity, see .tabs-fade-right */
          mask-image: linear-gradient(90deg, #000 88%, transparent); /* tokens-ok: mask opacity */
}
.name-marquee__inner {
  display: inline-block;
  will-change: transform;
}
.name-marquee:hover .name-marquee__inner,
.name-marquee:focus-within .name-marquee__inner {
  animation: name-marquee-scroll 5s linear infinite alternate;
}
@keyframes name-marquee-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(28ch - 100%)); }
}
@media (prefers-reduced-motion: reduce) {
  .name-marquee:hover .name-marquee__inner,
  .name-marquee:focus-within .name-marquee__inner { animation: none; }
}

/* ─── Release Notes / What's New (CD-846) ───────────────────────────────── */
/* Bespoke blog styling for /releases and /releases/:slug — deliberately
   distinct from the standard KB category/article reader. */

.releases-page,
.release-single__body {
  max-width: 820px;
  margin: 0 auto;
}

.releases-header { margin-bottom: 1.5rem; }
.releases-title { font-weight: 700; margin-bottom: 0.25rem; }
.releases-subtitle { color: var(--cd-text-muted); margin-bottom: 0; }

/* Known Bugs callout — a distinct, warmer block above the feed. */
.releases-known-bugs {
  background-color: var(--cd-brand-soft);
  border: 1px solid var(--cd-brand-soft);
  border-radius: var(--cd-radius-md);
  padding: 1rem 1.25rem;
}
.releases-known-bugs__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}
.releases-known-bugs__title {
  font-size: 1.1rem;
  font-weight: 700;
  margin: 0;
  color: var(--cd-brand-hover);
}

/* Blog post row: meta rail on the left, full content on the right. */
.release-post {
  display: grid;
  grid-template-columns: 150px minmax(0, 1fr);
  gap: 1.75rem;
  align-items: start;
}
.release-post__meta {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding-top: 0.35rem;
}
.release-post__meta--single { flex-direction: row; flex-wrap: wrap; align-items: center; margin-bottom: 1.25rem; }
.release-post__date {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--cd-text-muted);
}
.release-tag {
  display: inline-block;
  align-self: flex-start;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.4;
  padding: 0.1rem 0.5rem;
  border-radius: var(--cd-radius-pill);
  background-color: var(--cd-brand-soft);
  color: var(--cd-brand-hover);
  border: 1px solid var(--cd-brand-soft);
}
.release-post__title {
  font-size: 1.5rem;
  font-weight: 700;
  margin: 0 0 0.75rem;
  line-height: 1.2;
}
.release-post__title a { color: inherit; text-decoration: none; }
.release-post__title a:hover { color: var(--cd-brand); text-decoration: underline; }

/* Shared prose styling for release + KB bodies. */
.article-content img { max-width: 100%; height: auto; border-radius: var(--cd-radius-md); }
.article-content > :first-child { margin-top: 0; }
.article-content > :last-child { margin-bottom: 0; }
.article-content p { margin-bottom: 1rem; }

/* Decorative wavy separator between posts. */
.release-divider {
  height: 12px;
  width: 200px;
  margin: 2.25rem auto;
  background-repeat: repeat-x;
  background-position: center;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='12'%20viewBox='0%200%2024%2012'%20fill='none'%3E%3Cpath%20d='M0%206%20Q6%200%2012%206%20T24%206'%20stroke='%239D2E34'%20stroke-width='2'/%3E%3C/svg%3E");
  opacity: 0.5;
}

/* Individual post hero band. */
.release-hero {
  background-color: var(--cd-brand-fill);
  border-radius: var(--cd-radius-lg);
  padding: 2.5rem 1.5rem;
  margin-bottom: 2rem;
  text-align: center;
}
.release-hero__inner { max-width: 720px; margin: 0 auto; }
.release-hero__title { color: var(--cd-text-on-brand); font-weight: 700; margin: 0; }

.release-back {
  display: inline-flex;
  align-items: center;
  font-weight: 600;
  text-decoration: none;
  color: var(--cd-brand);
}
.release-back:hover { color: var(--cd-brand-hover); text-decoration: underline; }

.releases-pagination { margin-top: 2.5rem; }
.releases-pagination .page-item.active .page-link {
  background-color: var(--cd-brand-fill);
  border-color: var(--cd-brand);
}
.releases-pagination .page-link { color: var(--cd-brand); }
.releases-pagination .page-item.active .page-link { color: var(--cd-text-on-brand); }

/* Stack the meta above the content on small screens. */
@media (max-width: 640px) {
  .release-post { grid-template-columns: 1fr; gap: 0.5rem; }
  .release-post__meta { flex-direction: row; flex-wrap: wrap; align-items: center; padding-top: 0; }
  .release-post__title { font-size: 1.25rem; }
}

/* ── Drag-and-drop file affordance (CD-918) ────────────────────────────────
   Overlay painted over a rich-text editor while a file is dragged onto it.
   Appended to the Quill container (position:relative) by quillEditor.js
   wireDropOverlay and toggled via `.cd-drop-active`. It is pointer-events:none
   so the drop falls through to the contenteditable and Quill's uploader takes
   it — the overlay only paints, it never handles the drop. */
.cd-drop-overlay {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  background: rgba(157, 46, 52, 0.08); /* --apu-red wash; reads on both themes */
  border: 2px dashed var(--cd-brand);
  border-radius: var(--cd-radius-md);
  transition: opacity var(--transition-speed) ease;
}
.cd-drop-active > .cd-drop-overlay { opacity: 1; }
.cd-drop-overlay-label {
  padding: 0.375rem 0.75rem;
  font-weight: 600;
  font-size: 0.875rem;
  color: var(--cd-brand);
  background: var(--cd-surface-raised); /* flips to a dark surface under [data-theme="dark"] */
  border-radius: var(--cd-radius-sm);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
}
@media (prefers-reduced-motion: reduce) {
  .cd-drop-overlay { transition: none; }
}

/* ── Workflow builder drag-to-reorder (CD-923) ── */
/* Handle appears on row hover; class-based indicators keep the container's
   children == its rows (serialize reads DOM order). See dragReorder.js. */
.cd-reorder-row {
  position: relative;
}

.cd-drag-handle {
  display: inline-flex;
  align-items: center;
  align-self: center;
  cursor: grab;
  color: var(--cd-text-subtle);
  opacity: 0;
  transition: opacity 0.12s ease;
  user-select: none;
}

.cd-reorder-row:hover .cd-drag-handle {
  opacity: 0.55;
}

.cd-drag-handle:hover {
  opacity: 1;
  color: var(--cd-text-muted);
}

.cd-drag-handle:active {
  cursor: grabbing;
}

.cd-drag-handle i {
  pointer-events: none;
}

.cd-reorder-row.cd-dragging {
  opacity: 0.4;
}

/* Bold insertion line where the row will land. */
.cd-reorder-row.cd-drop-before {
  box-shadow: inset 0 3px 0 0 var(--cd-brand);
}

.cd-reorder-row.cd-drop-after {
  box-shadow: inset 0 -3px 0 0 var(--cd-brand);
}

/* Mouse-slowed: separate the rows and show a faint placeholder for the slot. */
.cd-reorder-row.cd-drop-gap-before {
  margin-top: 2.75rem;
}

.cd-reorder-row.cd-drop-gap-after {
  margin-bottom: 2.75rem;
}

.cd-reorder-row.cd-drop-gap-before::before,
.cd-reorder-row.cd-drop-gap-after::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 2.25rem;
  border: 1px dashed var(--cd-brand);
  border-radius: var(--cd-radius-sm);
  background: var(--cd-brand-soft);
  pointer-events: none;
}

.cd-reorder-row.cd-drop-gap-before::before {
  top: -2.5rem;
}

.cd-reorder-row.cd-drop-gap-after::after {
  bottom: -2.5rem;
}

/* ── Report screenshot + circle annotation (CD-1822) ─────────────────────────
   The capture is shown full width (it was a 120px thumbnail before, too small
   to review before filing) with the annotation canvas laid over it. The canvas
   is sized in CSS pixels to match the rendered image; circle coordinates are
   kept in the image's NATURAL space so a resize can't displace them. */
.bug-shot {
  position: relative;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  overflow: hidden;
  background: var(--cd-surface-sunken);
}

.bug-shot img {
  display: block;
  width: 100%;
  height: auto;
}

/* Report overlay: attachments drop zone (CD-1939). The zone is here and NOT on
   the screenshot preview because that preview's canvas consumes pointer drags
   for annotation — a drop target would sit on top of the drawing surface. Paste
   targets the screenshot slot; a drop targets attachments. */
.bug-attach-drop {
  padding: 0.5rem;
  border: 1px dashed var(--cd-line);
  border-radius: var(--cd-radius-md);
  /* --cd-transition carries duration + easing, and --cd-duration is already
     zeroed under prefers-reduced-motion in tokens.css — so no local media
     query is needed here. */
  transition: background-color var(--cd-transition),
    border-color var(--cd-transition);
}

.bug-attach-drop.bug-attach-over {
  background-color: var(--cd-brand-soft);
  border-color: var(--cd-brand);
}

.bug-shot canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  cursor: crosshair;
  /* Never let the browser claim the drag as a scroll/pan gesture. */
  touch-action: none;
}

/* "Also Appears In" secondary-placement picker (CD-1919). Lives in main.css
   rather than kb.css because both editors use it and they render on different
   layouts (the admin form on main.hbs, the in-context panel on kb.hbs); both
   load main.css. Capped so a long category tree can't push the editor's Body
   field off screen. */
.cd-placement-list {
  max-height: 12rem;
  overflow-y: auto;
}

/* ---------------------------------------------------------------------------
   Explore tour (/explore): unlisted guided walkthrough. Everything rides the
   Warm Desk tokens so dark mode follows for free; motion is decorative only
   and fully guarded by prefers-reduced-motion.
   --------------------------------------------------------------------------- */
.explore-page {
  max-width: 760px;
  margin: 0 auto;
  padding: 2rem 1rem 4rem;
}

/* Product Analytics Field Guide: the existing unlisted Explore surface grows
   into a reference page without making every shorter tour chapter too wide. */
.explore-page--field-guide { max-width: 1120px; }
.analytics-guide-hero {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(220px, .45fr);
  gap: var(--cd-space-6);
  overflow: hidden;
  padding: clamp(1.5rem, 4vw, 3rem);
  border-radius: var(--cd-radius-lg);
  color: var(--cd-text-on-brand);
  background:
    radial-gradient(circle at 92% 8%, color-mix(in srgb, var(--cd-white) 18%, transparent), transparent 30%),
    linear-gradient(135deg, var(--cd-brick-900), var(--cd-brick-700) 58%, var(--cd-plum));
  box-shadow: var(--cd-shadow-lg);
}
.analytics-guide-hero h1 {
  max-width: 14ch;
  margin: 0 0 var(--cd-space-3);
  color: var(--cd-text-on-brand);
  font-size: clamp(2rem, 5vw, 3.7rem);
  line-height: .98;
  letter-spacing: -.035em;
}
.analytics-guide-hero__copy > p:not(.analytics-guide-kicker) {
  max-width: 64ch;
  margin-bottom: var(--cd-space-5);
  color: color-mix(in srgb, var(--cd-text-on-brand) 88%, transparent);
  font-size: var(--cd-text-lg);
}
.analytics-guide-kicker {
  margin: 0 0 var(--cd-space-2);
  color: color-mix(in srgb, var(--cd-text-on-brand) 78%, transparent);
  font: var(--cd-type-eyebrow);
  letter-spacing: var(--cd-tracking-caps);
  text-transform: uppercase;
}
.analytics-guide-actions { display: flex; flex-wrap: wrap; gap: var(--cd-space-2); }
.analytics-guide-loop {
  align-self: center;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--cd-space-2) var(--cd-space-3);
  align-items: center;
  padding: var(--cd-space-5);
  border: 1px solid color-mix(in srgb, var(--cd-white) 25%, transparent);
  border-radius: var(--cd-radius-lg);
  background: color-mix(in srgb, var(--cd-surface-inverse) 30%, transparent);
  backdrop-filter: blur(10px);
}
.analytics-guide-loop span { font-weight: var(--cd-weight-bold); }
.analytics-guide-loop i:last-of-type { display: none; }

.analytics-guide-toc {
  position: sticky;
  top: var(--cd-space-3);
  z-index: 4;
  display: flex;
  flex-wrap: wrap;
  gap: var(--cd-space-2) var(--cd-space-4);
  align-items: center;
  margin: var(--cd-space-6) 0;
  padding: var(--cd-space-3) var(--cd-space-4);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-pill);
  background: color-mix(in srgb, var(--cd-surface-raised) 92%, transparent);
  box-shadow: var(--cd-shadow-sm);
  backdrop-filter: blur(12px);
  font-size: var(--cd-text-sm);
}
.analytics-guide-toc a { color: var(--cd-link); text-decoration: none; }
.analytics-guide-toc a:hover { color: var(--cd-link-hover); text-decoration: underline; }
.analytics-guide-section { scroll-margin-top: 5rem; padding-top: var(--cd-space-4); }

.analytics-guide-reading-order {
  display: grid;
  gap: var(--cd-space-2);
  margin: 0 0 var(--cd-space-6);
  padding: 0;
  list-style: none;
  counter-reset: guide-order;
}
.analytics-guide-reading-order li {
  counter-increment: guide-order;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--cd-space-3);
  align-items: center;
  padding: var(--cd-space-3) var(--cd-space-4);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  background: var(--cd-surface-raised);
}
.analytics-guide-reading-order li::before {
  content: counter(guide-order, decimal-leading-zero);
  color: var(--cd-brick-700);
  font-family: var(--cd-font-mono);
  font-weight: var(--cd-weight-bold);
}
.analytics-guide-reading-order a { white-space: nowrap; }
.analytics-guide-table-card {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-lg);
  background: var(--cd-surface-raised);
  box-shadow: var(--cd-shadow-sm);
}
.analytics-guide-table-card thead th { background: var(--cd-surface-sunken); }
.analytics-guide-table-card code { white-space: nowrap; }

.analytics-guide-surface-grid,
.analytics-guide-play-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--cd-space-4);
}
.analytics-guide-card,
.analytics-guide-play {
  position: relative;
  padding: var(--cd-space-5);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-lg);
  background: var(--cd-surface-raised);
  box-shadow: var(--cd-shadow-sm);
}
.analytics-guide-card { padding-left: 5rem; }
.analytics-guide-card h3,
.analytics-guide-play h3 { margin: 0 0 var(--cd-space-2); font-size: var(--cd-text-lg); }
.analytics-guide-card p,
.analytics-guide-play p { color: var(--cd-text-muted); }
.analytics-guide-card__icon {
  position: absolute;
  top: var(--cd-space-5);
  left: var(--cd-space-5);
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--cd-radius-md);
  color: var(--cd-white);
}
.analytics-guide-card__icon--brick { background: var(--cd-brick-700); }
.analytics-guide-card__icon--blue { background: var(--cd-blue); }
.analytics-guide-card__icon--green { background: var(--cd-green); }
.analytics-guide-card__icon--amber { background: var(--cd-amber); }
.analytics-guide-card__icon--plum { background: var(--cd-plum); }
.analytics-guide-card__icon--red { background: var(--cd-danger); }

.analytics-guide-callout {
  display: flex;
  gap: var(--cd-space-4);
  margin: var(--cd-space-6) 0;
  padding: var(--cd-space-4) var(--cd-space-5);
  border: 1px solid var(--cd-info);
  border-radius: var(--cd-radius-lg);
  background: var(--cd-info-bg);
  color: var(--cd-info-fg);
}
.analytics-guide-callout > i { flex: 0 0 auto; font-size: var(--cd-text-xl); }
.analytics-guide-callout--privacy { border-color: var(--cd-success); background: var(--cd-success-bg); color: var(--cd-success-fg); }
.analytics-guide-callout--warning { border-color: var(--cd-warning-border); background: var(--cd-warning-bg); color: var(--cd-warning-fg); }

.analytics-guide-subhead { margin: var(--cd-space-7) 0 var(--cd-space-3); font-size: var(--cd-text-xl); }
.analytics-guide-limit-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--cd-space-3);
}
.analytics-guide-limit-grid > div {
  display: flex;
  flex-direction: column;
  gap: var(--cd-space-1);
  padding: var(--cd-space-4);
  border-radius: var(--cd-radius-md);
  background: var(--cd-surface-sunken);
}
.analytics-guide-limit-grid span { color: var(--cd-text-muted); font-size: var(--cd-text-sm); }

.analytics-guide-play { padding-top: var(--cd-space-7); }
.analytics-guide-play__num {
  position: absolute;
  top: var(--cd-space-3);
  right: var(--cd-space-4);
  color: var(--cd-brick-700);
  font-family: var(--cd-font-mono);
  font-size: var(--cd-text-xl);
  font-weight: var(--cd-weight-black);
  opacity: .55;
}
.analytics-guide-play p:last-child { margin-bottom: 0; }
.analytics-guide-evidence-template {
  margin-top: var(--cd-space-6);
  padding: var(--cd-space-5);
  border-radius: var(--cd-radius-lg);
  background: var(--cd-surface-inverse);
  color: var(--cd-text-on-inverse);
}
.analytics-guide-evidence-template h3 { color: var(--cd-text-on-inverse); }
.analytics-guide-evidence-template dl {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--cd-space-4);
  margin: 0;
}
.analytics-guide-evidence-template dt { color: var(--cd-warm-300); }
.analytics-guide-evidence-template dd { margin: var(--cd-space-1) 0 0; color: color-mix(in srgb, var(--cd-text-on-inverse) 78%, transparent); }

.analytics-guide-setup {
  display: grid;
  gap: var(--cd-space-4);
  padding-left: 1.5rem;
}
.analytics-guide-setup li { padding-left: var(--cd-space-2); }
.analytics-guide-setup pre,
.analytics-guide-prompt {
  overflow: auto;
  margin: var(--cd-space-2) 0;
  padding: var(--cd-space-4);
  border: 1px solid color-mix(in srgb, var(--cd-white) 18%, transparent);
  border-radius: var(--cd-radius-md);
  background: var(--cd-surface-inverse);
  color: var(--cd-text-on-inverse);
  font-family: var(--cd-font-mono);
  font-size: var(--cd-text-sm);
  line-height: var(--cd-leading-normal);
  white-space: pre-wrap;
  word-break: break-word;
}
.analytics-guide-copy { float: right; margin-bottom: var(--cd-space-2); }
.analytics-guide-copy--copied { border-color: var(--cd-success); color: var(--cd-success-fg); }
.analytics-guide-prompt-wrap {
  overflow: hidden;
  padding: var(--cd-space-3);
  border-radius: var(--cd-radius-lg);
  background: var(--cd-surface-inverse);
  box-shadow: var(--cd-shadow-md);
}
.analytics-guide-prompt-wrap .analytics-guide-prompt { clear: both; max-height: 52rem; margin: 0; border: 0; }
.analytics-guide-examples .accordion-button { font-weight: var(--cd-weight-semibold); }
.analytics-guide-footer {
  margin-top: var(--cd-space-8);
  padding: var(--cd-space-6);
  border-top: 4px solid var(--cd-brick-700);
  border-radius: var(--cd-radius-lg);
  background: var(--cd-surface-sunken);
  text-align: center;
}

@media (max-width: 767.98px) {
  .analytics-guide-hero { grid-template-columns: 1fr; }
  .analytics-guide-loop { grid-template-columns: 1fr auto 1fr auto; }
  .analytics-guide-loop i:last-of-type { display: inline-block; }
  .analytics-guide-reading-order li { grid-template-columns: auto 1fr; }
  .analytics-guide-reading-order a { grid-column: 2; }
  .analytics-guide-surface-grid,
  .analytics-guide-play-grid,
  .analytics-guide-limit-grid,
  .analytics-guide-evidence-template dl { grid-template-columns: 1fr; }
  .analytics-guide-toc { position: static; border-radius: var(--cd-radius-lg); }
}

@media (max-width: 479.98px) {
  .explore-page--field-guide { padding-inline: 0; }
  .analytics-guide-hero { border-radius: var(--cd-radius-md); }
  .analytics-guide-loop { grid-template-columns: 1fr auto; }
  .analytics-guide-loop i:last-of-type { display: none; }
  .analytics-guide-card { padding: var(--cd-space-5); padding-top: 5rem; }
  .analytics-guide-card__icon { left: var(--cd-space-5); }
}

/* Hero — brick gradient panel with a drifting "topo" glow and paw-print
   watermark built from layered radial gradients (no image asset). */
.explore-hero {
  position: relative;
  overflow: hidden;
  border-radius: var(--cd-radius-lg);
  padding: 2.5rem 2rem;
  color: var(--cd-text-on-brand);
  background:
    radial-gradient(1200px 400px at 110% -20%, rgba(255,255,255,.14), transparent 60%),
    linear-gradient(135deg, var(--cd-brick-800), var(--cd-brick-700) 55%, var(--cd-brick-600));
  box-shadow: 0 12px 32px rgba(110, 34, 36, .25);
}
.explore-hero > * {
  position: relative;
  z-index: 2;
}
.explore-hero__gl {
  /* WebGL backdrop painted by exploreHero.js; absent (CSS gradient shows) on
     reduced motion, no WebGL, or hardware the adaptive loop gave up on */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
}
.explore-hero::after {
  /* paw print: one pad + four toes, top-right, barely-there */
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  opacity: .35;
  background:
    radial-gradient(46px 40px at 84% 66%, rgba(255,255,255,.16) 98%, transparent),
    radial-gradient(15px 19px at 72% 30%, rgba(255,255,255,.16) 98%, transparent),
    radial-gradient(15px 19px at 82% 22%, rgba(255,255,255,.16) 98%, transparent),
    radial-gradient(15px 19px at 92% 28%, rgba(255,255,255,.16) 98%, transparent),
    radial-gradient(13px 16px at 98% 42%, rgba(255,255,255,.14) 98%, transparent);
}
.explore-hero__kicker {
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  opacity: .85;
  margin-bottom: .5rem;
}
.explore-hero__title {
  font-family: "proxima-nova", "Mulish", sans-serif;
  font-weight: 800;
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  margin-bottom: .5rem;
  /* tokens.css h1 rule loads after this file and wins over inheritance */
  color: var(--cd-text-on-brand);
}
.explore-hero__lede {
  max-width: 46ch;
  opacity: .92;
  margin-bottom: 1rem;
}
.explore-hero__meta {
  display: inline-flex;
  gap: 1rem;
  font-size: .85rem;
  opacity: .85;
}

/* Track cards — the entry points. 2-up grid, icon tile, lift on hover. */
.explore-tracks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: .75rem;
  margin: 1.5rem 0 2.25rem;
}
.explore-track {
  display: flex;
  gap: .85rem;
  align-items: flex-start;
  padding: 1rem;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  background: var(--cd-surface-raised);
  text-decoration: none;
  color: var(--cd-text);
  transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.explore-track:hover {
  color: var(--cd-text);
  transform: translateY(-2px);
  box-shadow: 0 8px 20px rgba(37, 36, 37, .12);
  border-color: var(--cd-brick-500);
}
.explore-track__title { font-weight: 700; margin-bottom: .15rem; }
.explore-track__desc { font-size: .85rem; color: var(--cd-text-muted); margin: 0; }

/* Icon tiles — shared by tracks and chapter rows; accent per chapter. */
.explore-tile {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--cd-radius-md);
  font-size: 1.15rem;
  color: var(--cd-white);
}
.explore-tile--brick { background: linear-gradient(135deg, var(--cd-brick-700), var(--cd-brick-500)); }
.explore-tile--blue  { background: linear-gradient(135deg, color-mix(in srgb, var(--cd-blue) 72%, black), var(--cd-blue)); }
.explore-tile--green { background: linear-gradient(135deg, color-mix(in srgb, var(--cd-green) 72%, black), var(--cd-green)); }
.explore-tile--amber { background: linear-gradient(135deg, color-mix(in srgb, var(--cd-amber) 72%, black), var(--cd-amber)); }
.explore-tile--plum  { background: linear-gradient(135deg, color-mix(in srgb, var(--cd-plum) 72%, black), var(--cd-plum)); }

/* Chapter list — numbered rows, staggered entrance, arrow slides on hover. */
.explore-chapters {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  overflow: hidden;
  background: var(--cd-surface-raised);
}
.explore-chapters > li + li { border-top: 1px solid var(--cd-line); }
.explore-chapter-row {
  display: flex;
  align-items: center;
  gap: .9rem;
  padding: .9rem 1rem;
  text-decoration: none;
  color: var(--cd-text);
  transition: background-color .15s ease;
}
.explore-chapter-row:hover { background: var(--cd-surface-hover); color: var(--cd-text); }
.explore-chapter-row__num {
  font-family: var(--bs-font-monospace, monospace);
  font-size: .75rem;
  color: var(--cd-text-subtle);
  width: 1.5rem;
}
.explore-chapter-row__title { font-weight: 600; }
.explore-chapter-row__tagline { font-size: .85rem; color: var(--cd-text-muted); }
.explore-chapter-row__mins {
  margin-left: auto;
  font-size: .78rem;
  color: var(--cd-text-subtle);
  white-space: nowrap;
}
.explore-chapter-row__arrow {
  color: var(--cd-text-subtle);
  transition: transform .15s ease;
}
.explore-chapter-row:hover .explore-chapter-row__arrow { transform: translateX(3px); color: var(--cd-brick-700); }

/* Chapter page */
.explore-crumb { font-size: .82rem; color: var(--cd-text-muted); margin-bottom: 1rem; }
.explore-crumb a { color: var(--cd-brick-700); text-decoration: none; }
.explore-crumb a:hover { text-decoration: underline; }

.explore-progress {
  height: 4px;
  border-radius: var(--cd-radius-pill);
  background: var(--cd-line);
  overflow: hidden;
  margin-bottom: 1.5rem;
}
.explore-progress__bar {
  height: 100%;
  border-radius: var(--cd-radius-pill);
  background: linear-gradient(90deg, var(--cd-brick-700), var(--cd-brick-500));
  transition: width .4s ease;
}

.explore-chapter-head { display: flex; gap: 1rem; align-items: center; margin-bottom: 1.25rem; }
.explore-chapter-head .explore-tile { width: 3rem; height: 3rem; font-size: 1.4rem; }
.explore-chapter-head h1 {
  font-family: "proxima-nova", "Mulish", sans-serif;
  font-weight: 800;
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  margin: 0;
}
.explore-chapter-head p { margin: 0; color: var(--cd-text-muted); }

.explore-shot { margin: 1.5rem 0; }
.explore-shot__frame {
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  overflow: hidden;
  background: var(--cd-surface-sunken);
  box-shadow: 0 10px 30px rgba(37, 36, 37, .12);
}
.explore-shot__frame img { display: block; width: 100%; height: auto; }
.explore-shot figcaption {
  margin-top: .5rem;
  text-align: center;
  font-size: .82rem;
  color: var(--cd-text-muted);
}

.explore-role-note {
  display: flex;
  gap: .75rem;
  align-items: flex-start;
  border: 1px solid var(--cd-line);
  border-left: 3px solid var(--cd-brick-700);
  border-radius: var(--cd-radius-sm);
  background: var(--cd-surface-sunken);
  padding: .85rem 1rem;
  margin: 1.25rem 0;
  font-size: .92rem;
}
.explore-role-note i { color: var(--cd-brick-700); margin-top: .1rem; }

.explore-steps-heading {
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--cd-brick-700);
  margin: 1.75rem 0 .75rem;
}
.explore-steps { list-style: none; margin: 0 0 2rem; padding: 0; counter-reset: explore-step; }
.explore-steps > li {
  counter-increment: explore-step;
  display: flex;
  gap: .85rem;
  align-items: flex-start;
  padding: .8rem 1rem;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  background: var(--cd-surface-raised);
}
.explore-steps > li + li { margin-top: .5rem; }
.explore-steps > li::before {
  content: counter(explore-step);
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  background: var(--cd-warm-150);
  color: var(--cd-brick-700);
  font-weight: 700;
  font-size: .8rem;
}
[data-theme="dark"] .explore-steps > li::before { background: var(--cd-surface-sunken); }
.explore-steps__text { flex: 1 1 auto; align-self: center; }
.explore-steps .btn { white-space: nowrap; }

.explore-pager { display: flex; gap: .75rem; margin-top: 2rem; }
.explore-pager a {
  flex: 1 1 0;
  padding: .85rem 1rem;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  background: var(--cd-surface-raised);
  text-decoration: none;
  color: var(--cd-text);
  transition: border-color .15s ease, transform .15s ease;
}
.explore-pager a:hover { border-color: var(--cd-brick-500); transform: translateY(-1px); color: var(--cd-text); }
.explore-pager a.explore-pager--next { text-align: right; }
.explore-pager__dir {
  display: block;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--cd-text-subtle);
  margin-bottom: .15rem;
}
.explore-pager__title { font-weight: 600; }

/* Entrance animation — staggered fade-up on index rows and chapter blocks. */
@media (prefers-reduced-motion: no-preference) {
  .explore-fade-up { animation: explore-fade-up .45s ease both; }
  .explore-chapters > li:nth-child(n)  { animation-delay: calc(var(--explore-i, 0) * 60ms); }
  .explore-hero::after { animation: explore-drift 14s ease-in-out infinite alternate; }
}
@keyframes explore-fade-up {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
@keyframes explore-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-12px, 6px, 0); }
}

/* Reporting Dashboards: Reach-scoped, curated operating views. */
.report-dashboard-entry,
.report-cockpit-hero,
.report-dashboard-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--cd-space-4);
}

.report-dashboard-entry,
.report-cockpit-hero {
  padding: var(--cd-space-5);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-lg);
  background: linear-gradient(
    125deg,
    color-mix(in srgb, var(--cd-brand-soft) 72%, var(--cd-surface-raised)),
    var(--cd-surface-raised)
  );
  box-shadow: var(--cd-shadow-sm);
}

.report-cockpit-card,
.report-starter-card,
.report-composer-widget > .card {
  border-color: var(--cd-line);
  border-radius: var(--cd-radius-lg);
  box-shadow: var(--cd-shadow-sm);
}

.report-cockpit-card,
.report-starter-card {
  transition: transform var(--cd-transition), box-shadow var(--cd-transition), border-color var(--cd-transition);
}

.report-cockpit-card:hover,
.report-starter-card:hover {
  transform: translateY(-2px);
  border-color: var(--cd-brand);
  box-shadow: var(--cd-shadow-md);
}

.report-cockpit-count {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  min-width: 2.5rem;
  height: 2.5rem;
  padding-inline: var(--cd-space-2);
  border-radius: var(--cd-radius-pill);
  background: var(--cd-brand-soft);
  color: var(--cd-brand);
  font-weight: var(--cd-weight-bold);
}

.report-composer-widget {
  transition: opacity var(--cd-transition), transform var(--cd-transition);
}

.report-composer-widget.is-dragging {
  opacity: .5;
  transform: scale(.99);
}

.report-widget-drag {
  cursor: grab;
  touch-action: none;
}

.report-widget-drag:active { cursor: grabbing; }

.report-dashboard-page,
.report-dashboard-presentation {
  position: relative;
  isolation: isolate;
  color: var(--cd-text);
}

.report-dashboard-page {
  overflow: hidden;
  padding: var(--cd-space-5);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-lg);
  background: var(--cd-surface-sunken);
}

.report-dashboard-presentation {
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  padding: var(--cd-space-6);
  background: var(--cd-surface-sunken);
}

.report-dashboard-page::before,
.report-dashboard-presentation::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -15%;
  pointer-events: none;
  opacity: .72;
}

.report-dashboard-theme-aurora::before {
  background:
    radial-gradient(circle at 18% 18%, color-mix(in srgb, var(--cd-brand) 24%, transparent) 0, transparent 34%),
    radial-gradient(circle at 82% 26%, color-mix(in srgb, var(--cd-amber) 22%, transparent) 0, transparent 30%),
    radial-gradient(circle at 58% 86%, color-mix(in srgb, var(--cd-blue) 16%, transparent) 0, transparent 35%);
}

.report-dashboard-theme-constellation::before {
  background-image: radial-gradient(circle, color-mix(in srgb, var(--cd-text-subtle) 52%, transparent) 1px, transparent 1px);
  background-size: 2.25rem 2.25rem;
}

.report-dashboard-theme-topography::before {
  background: repeating-radial-gradient(
    ellipse at 70% 32%,
    transparent 0 1.65rem,
    color-mix(in srgb, var(--cd-brand) 16%, transparent) 1.7rem 1.78rem
  );
}

.report-dashboard-theme-still::before { display: none; }

.report-dashboard-header {
  position: relative;
  z-index: 1;
  margin-bottom: var(--cd-space-4);
}

.report-dashboard-presentation .report-dashboard-header {
  position: sticky;
  z-index: 3;
  top: 0;
  padding: var(--cd-space-3) var(--cd-space-4);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-lg);
  background: color-mix(in srgb, var(--cd-surface-raised) 92%, transparent);
  box-shadow: var(--cd-shadow-md);
  backdrop-filter: blur(14px);
}

.report-dashboard-presentation .report-dashboard-actions {
  flex: 1 1 32rem;
  justify-content: flex-end;
}

.report-dashboard-rotation-controls {
  display: flex;
  flex: 1 1 100%;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--cd-space-2);
  padding: var(--cd-space-2);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  background: color-mix(in srgb, var(--cd-surface-sunken) 82%, transparent);
}

.report-dashboard-rotation-progress {
  display: inline-flex;
  align-items: center;
  gap: var(--cd-space-2);
  white-space: nowrap;
}

.report-dashboard-rotation-progress progress {
  width: clamp(5rem, 10vw, 8rem);
  height: .55rem;
  accent-color: var(--cd-brand);
}

.report-dashboard-rotation-controls [data-presentation-rotation-status] {
  flex: 1 1 14rem;
  text-align: right;
}

.report-dashboard-freshness {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cd-space-2) var(--cd-space-4);
  margin-bottom: var(--cd-space-4);
  padding: var(--cd-space-3) var(--cd-space-4);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  background: color-mix(in srgb, var(--cd-surface-raised) 90%, transparent);
  font-size: var(--cd-text-sm);
}

.report-dashboard-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--cd-space-4);
}

.report-dashboard-scene {
  min-height: var(--report-scene-height, auto);
  scroll-margin-top: var(--cd-space-4);
}

.report-dashboard-scene[hidden] { display: none !important; }

.report-dashboard-scene-grid {
  align-content: start;
  min-height: inherit;
}

.report-widget-width-4 { grid-column: span 4; }
.report-widget-width-6 { grid-column: span 6; }
.report-widget-width-8 { grid-column: span 8; }
.report-widget-width-12 { grid-column: span 12; }

.report-dashboard-widget {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-lg);
  background: color-mix(in srgb, var(--cd-surface-raised) 94%, transparent);
  box-shadow: var(--cd-shadow-sm);
  backdrop-filter: blur(10px);
}

.report-widget-height-compact { min-height: 14rem; }
.report-widget-height-medium { min-height: 24rem; }
.report-widget-height-tall { min-height: 34rem; }

.report-dashboard-widget-header,
.report-dashboard-widget-footer {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--cd-space-3);
  padding: var(--cd-space-3) var(--cd-space-4);
}

.report-dashboard-widget-header { border-bottom: 1px solid var(--cd-line); }

.report-dashboard-widget-footer {
  display: block;
  margin-top: auto;
  border-top: 1px solid var(--cd-line);
  background: color-mix(in srgb, var(--cd-surface-sunken) 70%, transparent);
  font-size: var(--cd-text-xs);
}

.report-dashboard-widget-body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
  padding: var(--cd-space-4);
}

.report-dashboard-chart {
  flex: 1 1 14rem;
  min-height: 14rem;
  position: relative;
}

.report-dashboard-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: var(--cd-space-3);
}

.report-dashboard-metric {
  display: flex;
  flex-direction: column;
  gap: var(--cd-space-1);
  padding: var(--cd-space-3);
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  background: var(--cd-surface-sunken);
}

.report-dashboard-metric-value {
  font-size: clamp(var(--cd-text-xl), 4vw, var(--cd-text-3xl));
  font-weight: var(--cd-weight-bold);
  line-height: var(--cd-leading-tight);
  color: var(--cd-brand);
}

.report-dashboard-metric-label { font-weight: var(--cd-weight-semibold); }

.report-dashboard-widget-state {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: center;
  gap: var(--cd-space-3);
  min-height: 10rem;
  padding: var(--cd-space-4);
  border: 1px dashed var(--cd-line-strong);
  border-radius: var(--cd-radius-md);
  background: var(--cd-surface-sunken);
}

.report-dashboard-widget-state > i {
  font-size: var(--cd-text-2xl);
  color: var(--cd-text-subtle);
}

.report-dashboard-evidence {
  margin-top: var(--cd-space-3);
  border-top: 1px solid var(--cd-line);
  padding-top: var(--cd-space-2);
}

.report-dashboard-evidence summary,
.report-dashboard-delete summary {
  color: var(--cd-link);
  cursor: pointer;
  font-weight: var(--cd-weight-semibold);
}

/* Per-user report disclosures remain plain native controls so keyboard and
   assistive-technology behavior comes from the browser. */
.report-auto-collapse {
  margin-top: var(--cd-space-3);
}

.report-auto-collapse > summary {
  color: var(--cd-link);
  cursor: pointer;
  font-weight: var(--cd-weight-semibold);
}

.report-auto-collapse[open] > summary {
  margin-bottom: var(--cd-space-2);
}

.report-dashboard-heatmap {
  display: grid;
  grid-template-columns: minmax(5.5rem, auto) repeat(var(--report-heat-columns), minmax(2.25rem, 1fr));
  gap: var(--cd-space-1);
  width: 100%;
  min-width: 0;
  padding-block: var(--cd-space-2);
  overflow-x: auto;
}

.report-dashboard-heatmap-corner,
.report-dashboard-heatmap-hour,
.report-dashboard-heatmap-day {
  align-self: center;
  color: var(--cd-text-muted);
  font-size: var(--cd-text-xs);
  font-weight: var(--cd-weight-semibold);
}

.report-dashboard-heatmap-hour { text-align: center; }

.report-dashboard-heatmap-cell {
  display: grid;
  place-items: center;
  min-height: 2.25rem;
  border: 1px solid var(--cd-line);
  border-radius: var(--cd-radius-sm);
  background: var(--cd-surface-sunken);
  color: var(--cd-text-muted);
  font-size: var(--cd-text-xs);
}

.report-dashboard-heatmap-cell.has-value {
  background: color-mix(
    in srgb,
    var(--cd-brand) calc(18% + (var(--report-heat) * 62%)),
    var(--cd-surface-raised)
  );
  color: var(--cd-text);
  font-weight: var(--cd-weight-semibold);
}

@media (prefers-reduced-motion: no-preference) {
  .report-dashboard-theme-aurora::before,
  .report-dashboard-theme-constellation::before,
  .report-dashboard-theme-topography::before {
    animation: report-dashboard-drift 24s ease-in-out infinite alternate;
  }

  .report-dashboard-scene:not([hidden]) {
    animation: report-dashboard-scene-in .24s ease-out both;
  }
}

@keyframes report-dashboard-drift {
  from { transform: translate3d(-1.5%, -1%, 0) scale(1); }
  to { transform: translate3d(1.5%, 1%, 0) scale(1.03); }
}

@keyframes report-dashboard-scene-in {
  from { opacity: .72; transform: translateY(.35rem); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .report-cockpit-card,
  .report-starter-card,
  .report-composer-widget,
  .report-dashboard-page::before,
  .report-dashboard-presentation::before {
    animation: none !important;
    transition: none !important;
  }

  .report-dashboard-scene { animation: none !important; }

  .report-cockpit-card:hover,
  .report-starter-card:hover { transform: none; }
}

@media (max-width: 991.98px) {
  .report-widget-width-4,
  .report-widget-width-8 { grid-column: span 6; }
}

@media (max-width: 767.98px) {
  .report-dashboard-entry,
  .report-cockpit-hero,
  .report-dashboard-header,
  .report-dashboard-widget-header {
    flex-direction: column;
  }

  .report-dashboard-page,
  .report-dashboard-presentation { padding: var(--cd-space-3); }

  .report-dashboard-rotation-controls {
    justify-content: flex-start;
  }

  .report-dashboard-rotation-controls [data-presentation-rotation-status] {
    flex-basis: 100%;
    text-align: left;
  }

  .report-widget-width-4,
  .report-widget-width-6,
  .report-widget-width-8,
  .report-widget-width-12 { grid-column: 1 / -1; }

  .report-widget-height-medium,
  .report-widget-height-tall { min-height: 22rem; }
}

/* CD-2160 (Subject), CD-2161 (Owner): two API-key columns hold values long
   enough to eat the rest of the table — an opaque 255-char identifier, and 255
   characters of free text. Narrow them with CSS, never by slicing the string
   server-side: the full value has to stay in the DOM for browser find, which is
   the one list-scanning workflow that genuinely needs these columns. The
   truncation sits on the inner element because max-width on a <td> is ignored
   under the default auto table layout. Sized once, so the two cannot drift to
   different widths. */
.cd-key-subject code,
.cd-key-owner span {
  display: inline-block;
  max-width: 14rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* Advanced filter builder panel (CD-2236, CD-2249 AF-11). Bucket accents reuse
   existing brand tokens rather than inventing new colors — blue for ALL (a
   plain conjunction), plum for ANY (the alternative), matching the two
   non-status/priority hues already in tokens.css. */
.cd-adv-panel {
  border-top: 1px solid var(--bs-border-color);
  padding: 0.75rem 1rem;
}
.cd-adv-section .cd-adv-rows {
  border-left: 2px solid var(--bs-border-color);
  padding-left: 0.75rem;
}
.cd-adv-all .cd-adv-rows { border-left-color: var(--cd-blue); }
.cd-adv-any .cd-adv-rows { border-left-color: var(--cd-plum); }
.cd-adv-bucket-badge {
  font-family: var(--bs-font-monospace, monospace);
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  font-weight: 700;
}
.cd-adv-bucket-all { color: var(--cd-blue); background: color-mix(in srgb, var(--cd-blue) 15%, transparent); }
.cd-adv-bucket-any { color: var(--cd-plum); background: color-mix(in srgb, var(--cd-plum) 15%, transparent); }
.cd-adv-row { padding: 0.25rem 0; }
.cd-adv-novalue { font-size: 0.8rem; color: var(--bs-secondary-color); font-style: italic; }
.cd-adv-tokenwrap {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem;
  border: 1px solid var(--bs-border-color); border-radius: var(--cd-radius-sm);
  padding: 0.15rem 0.35rem; position: relative;
}
.cd-adv-token {
  display: inline-flex; align-items: center; gap: 0.25rem;
  background: var(--cd-surface-hover); border-radius: var(--cd-radius-pill);
  padding: 0.05rem 0.3rem 0.05rem 0.55rem; font-size: 0.75rem; white-space: nowrap;
}
.cd-adv-token i { font-style: normal; cursor: pointer; opacity: 0.6; }
.cd-adv-token i:hover { opacity: 1; }
/* AF-21: a chip whose label is in flight, or could not be resolved — muted,
   same as .cd-adv-novalue, rather than reading as a normal chosen value. */
.cd-adv-token--pending, .cd-adv-token--unavailable { color: var(--bs-secondary-color); font-style: italic; }
/* AF-22b — the chip strip is a flex item alongside the `dd` search trigger
   (`.cd-adv-tokendd`, below) inside `.cd-adv-tokenwrap`; it needs its own
   wrap behaviour so a long chip run doesn't force the trigger off the row. */
.cd-adv-token-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem; }
/* AF-22b — the token picker's search box is now the same `dd` component
   every other picker in this panel uses (invariants/lists-and-tables.md,
   "No native select in the advanced-filter panel"); this just gives it the
   flexible width `.cd-adv-tokeninput-wrap` used to have, since `.dd`/
   `.dd-trigger` carry no width rule of their own for the plain multi-select
   trigger variant (the single-select variant, `.dd-trigger--select`,
   already sets width:100% via its form-select mimicry).  */
.cd-adv-tokendd { flex: 1 1 140px; min-width: 140px; }
.cd-adv-tokendd .dd-trigger { width: 100%; }
.cd-adv-date { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }
.cd-adv-date-range { display: flex; align-items: center; gap: 0.4rem; }

/* The Apply/Close row, pinned to the bottom of the scrollport for as long as
   the panel spans it — same problem and same remedy as .macro-preview-actions
   (CD-1715): a long condition list otherwise puts Apply a full scroll away.
   That scrollport is #main-content, not the window: `body` is `height: 100vh;
   overflow: hidden` and #main-content carries `overflow-y: auto`. Sticky binds
   to the nearest such ancestor, so `bottom: 0` holds against the bottom of
   #main-content and then settles the moment the panel's real bottom edge
   scrolls into view. Opaque, or the condition rows show through it. A dedicated
   hook rather than the row's .d-flex shape, so panelBodyHtml's markup can
   change without silently unpinning this. */
.cd-adv-actions {
  position: sticky;
  bottom: 0;
  background: var(--cd-surface-raised);
  padding-bottom: 0.5rem;
}
#advToggle.cd-adv-on { background: var(--cd-brick-700); border-color: var(--cd-brick-700); color: var(--cd-white); }
#advToggle .cd-adv-badge {
  display: inline-block; margin-left: 0.35rem; padding: 0 0.4rem; border-radius: var(--cd-radius-pill);
  background: rgba(255, 255, 255, 0.28); font-size: 0.7rem; font-weight: 600;
}
#advToggle .cd-adv-clear { margin-left: 0.2rem; opacity: 0.8; }
#advToggle .cd-adv-clear:hover { opacity: 1; }

/* AF-16/AF-20 (CD-2309) gave a set operator (in/not_in) on a base enum or a
   custom dropdown/multiselect field its own checkbox list, with a scroll cap
   past ten options. AF-22a (CD-2335) replaced it with a `dd` multi-select —
   `.dd`/`.dd-overlay` in this file already carry that control's styling, so
   there is nothing closed-set-specific left to keep here. */

/* AF-12 — the bar/panel three-state model. `cd-filters-frozen` is toggled by
   advancedFilterBuilder.js for exactly as long as the panel sits open on top
   of the LIVE bar (adv empty); `cd-adv-bar-inert` is the server-rendered
   stand-in shown instead of controls once adv is non-empty — see
   ticket-filters.hbs.

   This class is the LOOK of the freeze, never the whole of it: `pointer-events`
   stops the mouse and nothing else, so the same element also gets the `inert`
   ATTRIBUTE, which is what removes it from the tab order and the a11y tree.
   Keeping the dimming here rather than on `[inert]` means an engine without
   `inert` support still shows the state and still refuses the mouse. */
.cd-filters-frozen {
  opacity: 0.55;
  pointer-events: none;
}
.cd-adv-bar-inert {
  padding: 0.35rem 0;
}
