/* ================================
   THEME VARIABLES — single source of truth
   4 themes: light (default), dark, grayscale-light, grayscale-dark.
   Selected/persisted by js/theme.js (window.SiteSettings), which sets
   data-theme="…" on <html> (light = no attribute, matching CSS default).
   Ported from the reader app's variables.css pattern.
   ================================ */

/* App-wide: NOT reserving a root-level scrollbar gutter here (see
   below for why) — loaded on every page (login.html, home.html,
   user-certification.html, etc.) since this stylesheet is the one
   thing all of them include.

   Previously set to `stable`, to keep content width from shifting
   when a scrollbar appears/disappears. That reasoning doesn't apply
   at the root level in this app though: body has overflow:hidden
   (see styles.css) — the document itself never scrolls, so there is
   no root-level scrollbar to ever appear or disappear in the first
   place. Reserving gutter space for one anyway just permanently ate a
   strip of width down the whole page (header included), reported as
   "the header and everything isn't going all the way to the right".
   Individual scrollable panels (sidebars, tab content, etc. — each
   with their own overflow-y:auto) are exactly where this technique
   still makes sense, and can opt in with their own scrollbar-gutter:
   stable rule if a specific one is ever seen to jump width on its own
   content change — just not applied blanket at the root. */

/* Tried a blanket cross-document View Transition opt-in
   (@view-transition { navigation: auto; }) here for the login/
   certification page → home.html swap after certifyUser() approves.
   Reverted: that hard navigation is triggered by Datastar's location()
   action (vendor/starfederation/datastar-php/src/events/Location.php),
   which fires via `setTimeout(() => window.location = uri)` — a
   script-triggered navigation with no live user-activation signal by
   the time it runs, well outside the click that started it. Per the
   @view-transition spec, push/replace navigations only get a transition
   when "initiated by a user interacting with the page content", which
   this doesn't reliably satisfy — combined with the certify dialog being
   a native <dialog> (top layer) still open at the moment of navigation,
   the result was the dialog visibly flickering (disappearing/
   reappearing) mid-transition instead of a clean cross-fade. Not worth
   fighting for one navigation; the emergeClose()-avoidance +
   showCertifyingProgress() spinner (login.html, UserCertification_
   Controller.php) already handle the jank this was meant to polish. */

/* ── Post-certification entrance animation (replaces the above) ───────
   home.html only, and only on the render immediately after certifyUser()
   navigates here (see Home_Controller::index()'s entranceAnimClass, set
   from dbMotionPreference — same one-time SESSION.motionNeedsSync flag).
   Same-document, so none of the cross-document problems above apply: no
   top-layer conflicts, no user-activation requirement, no browser support
   gap.

   Pure CSS @keyframes, not a JS-toggled class pair — the class and its
   animation are both already present in the very first bytes of the
   response, so playback starts the instant the browser paints
   .top-panel/#app-panels. An earlier version of this held content at
   opacity:0 via a class that JS only removed/swapped once
   datastar/traverse.js/motion.js had all loaded and run — real, visible
   time on a cold hard navigation, showing as a blank-but-themed gap
   between the certify dialog closing and this content appearing. Nothing
   here waits on script load.

   Matches the app-wide Motion/Fade/No Fade convention (Motion = the real,
   unrestrained experience; Fade = the deliberately toned-down one; No
   Fade = zero animation everywhere):
     - 'no-fade', or a DB preference that was never explicitly set: no
       class at all, content just renders normally — the safest default
       when the user's opted out, or when we can't confirm a preference
       (no server-side fallback to localStorage/OS the way js/motion.js's
       own client-side resolution chain has).
     - 'fade': .entrance-anim-fade — a plain opacity crossfade, no
       movement.
     - 'motion': .entrance-anim-motion — opacity + a translateY slide, an
       actual motion effect rather than just "fade but not restrained". */
body.entrance-anim-fade > .top-panel,
body.entrance-anim-fade > #app-panels {
  animation: entranceFadeIn 250ms ease both;
}

body.entrance-anim-motion > .top-panel,
body.entrance-anim-motion > #app-panels {
  animation: entranceMotionIn 300ms ease both;
}

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

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

/* ── Design tokens (radius / spacing) — not theme-dependent, same across
   all 4 themes, so declared once here rather than repeated per theme.
   Values chosen from what was already the dominant, de facto standard
   across the app's CSS (e.g. border-radius: 4px alone already accounted
   for 31 of ~50 uses) rather than invented fresh — naming an existing
   pattern, not creating a new one. Deliberately NOT extending this to
   height/width: those are mostly either 100% (fill-parent, not a design
   token) or genuinely one-off context-specific sizes (icon dimensions,
   panel widths) that don't actually share intent just because two
   numbers coincide — forcing them into shared variables would risk
   accidentally coupling unrelated elements together. */
:root {
  --radius-sm: 4px;
  --radius-md: 8px;

  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 12px;
  --space-lg: 16px;
  --space-xl: 20px;
}

/* ── Button tier tokens (header/side/content tab buttons) ──────────
   Introduced for a consistent, purpose-agnostic naming scheme — see
   .btn-primary / .btn-secondary / .btn-tertiary in css/styles.css.
   Values below are aliases to the exact same underlying tokens each
   tier already used (--btn-bg / --secondaryColor / --panelBgColor
   etc.), so nothing currently looks any different — this just gives
   each tier its own dedicated set of tokens so it *can* be recolored
   independently later without touching the others. These are live
   var() references, so they resolve correctly per-theme automatically;
   no need to repeat this block inside each [data-theme="…"] rule below.
   The dark/grayscale-dark border-only active treatment (a different
   property shape, not just a color swap — transparent bg + thicker
   border) stays as its own CSS rule in styles.css rather than being
   tokenized here. ────────────────────────────────────────────── */
:root {
  --btn-primary-bg: var(--btn-bg);
  --btn-primary-bg-hover: var(--btn-bg-hover);
  --btn-primary-color: var(--textDarkColor);
  --btn-primary-border: var(--borderColor);
  --btn-primary-active-bg: var(--secondaryColor);
  --btn-primary-active-border: var(--secondaryColor);
  --btn-primary-active-color: var(--btn-active-color);

  --btn-secondary-bg: var(--btn-bg);
  --btn-secondary-bg-hover: var(--btn-bg-hover);
  --btn-secondary-color: var(--textDarkColor);
  --btn-secondary-border: var(--borderColor);
  --btn-secondary-active-bg: var(--secondaryColor);
  --btn-secondary-active-border: var(--secondaryColor);
  --btn-secondary-active-color: var(--btn-active-color);

  --btn-tertiary-bg: var(--panelBgColor);
  --btn-tertiary-bg-hover: var(--tab-hover-bg);
  --btn-tertiary-color: var(--textDarkColor);
  --btn-tertiary-active-border: var(--secondaryColor);
  --btn-tertiary-active-color: var(--secondaryColor);
}

/* App-wide bold weight — browsers default <b>/<strong> to font-weight:
   700, which reads heavier than the rest of the app's type (buttons,
   active nav items, etc. all sit at 600 at most). Low specificity by
   design so any element-specific font-weight elsewhere in the app still
   wins without needing !important. */
b,
strong {
  font-weight: 500;
}

:root {
  color-scheme: light;

  --primaryColor: #333;
  --secondaryColor: #4682b4;
  --panelBgColor: #dddddd;
  --border-color: #dedede;
  --shadowColor: rgba(0, 0, 0, 0.1);
  --sectionBg: #f8f9fa;
  --headingColor: #2c3e50;
  --textDarkColor: #334155;
  --textLightColor: #64748b;
  --text-primary: #212529;
  --text-secondary: #333;
  --text-tertiary: #141414;
  --bg-primary: #f5f5f5;
  --bg-secondary: #e6e6e6;
  --bg-tertiary: #cfcfcf;
  --btn-active-color: #ffffff;
  --scrollbar-thumb: #ccc;
  --scrollbar-track: rgba(0, 0, 0, 0.05);
  --btn-bg: #dde1e2;
  --btn-bg-hover: #cecece;
  --icon-filter: none;
  --icon-button-svg-color: #333;
  --input-border-color: #2c2c2c;
  --input-bg-color: #ffffff;
  --placeholder-color: #999;
  --sanctions-tabs-bg: white;
  --tab-hover-bg: rgba(0, 0, 0, 0.05);
  --item-category-bg: #eee;
  --modal-backdrop: rgba(0, 0, 0, 0.5);
  --logout-color: hsl(348, 50%, 43%);
  --find-cell-hover-bg: rgba(70, 130, 180, 0.14);
  --charcount-warning-color: #ffc107;
  --logo-bg-color: #2c3e50;
  --bg-no-record-found: #333;
  --text-no-record-found: white;
  --searchPanelStatusActive: #86b99d;
  --searchPanelStatusInactive: #d9534f;
  --searchPanel: var(--bg-secondary);
  --info-bg-color: var(--bg-tertiary);

  /* ── Status/Uptime page severity colors ──────────────────────── */
  --uptime-up-color: #8fce9e;
  --uptime-degraded-color: #e8a39c;
  --uptime-partial-color: #e2564a;
  --uptime-full-color: #c62828;
}

[data-theme="dark"] {
  color-scheme: dark;

  --primaryColor: #f0f0f0;
  --secondaryColor: #66b3ff;
  --panelBgColor: #1a1a1a;
  --sectionBg: #2a2a2a;
  --border-color: #434343;
  --headingColor: #f0f0f0;
  --textDarkColor: #e0e0e0;
  --textLightColor: #aaaaaa;
  --text-primary: #ffffff;
  --text-secondary: #b0b0b0;
  --text-tertiary: #dddddd;
  --shadowColor: rgba(0, 0, 0, 0.4);
  --bg-primary: #1e1e1e;
  --bg-secondary: #2a2a2a;
  --bg-tertiary: #333333;
  --btn-active-color: #1e1e1e;
  --scrollbar-thumb: #555;
  --scrollbar-track: rgba(255, 255, 255, 0.05);
  --btn-bg: #444;
  --btn-bg-hover: #555;
  --icon-filter: brightness(0) invert(1);
  --icon-button-svg-color: #e0e0e0;
  --input-border-color: #555;
  --input-bg-color: #1e1e1e;
  --placeholder-color: #999;
  --sanctions-tabs-bg: #2a2a2a;
  --tab-hover-bg: rgba(255, 255, 255, 0.05);
  --item-category-bg: #333;
  --modal-backdrop: rgba(0, 0, 0, 0.65);
  --logout-color: hsl(348, 54%, 54%);
  --find-cell-hover-bg: rgba(102, 179, 255, 0.16);
  --charcount-warning-color: #ffc107;
  --logo-bg-color: #2c3e50;
  --bg-no-record-found: #333;
  --text-no-record-found: white;
  --searchPanelStatusInactive: #b95754;
  --searchPanelStatusActive: #2d5a2d;
  --searchPanel: var(--bg-primary);
  --info-bg-color: var(--bg-tertiary);
  --uptime-up-color: #6fae7f;
  --uptime-degraded-color: #c98a83;
  --uptime-partial-color: #c94f45;
  --uptime-full-color: #a81f1f;
}

[data-theme="grayscale-light"] {
  color-scheme: light;

  --primaryColor: #1a1a1a;
  --secondaryColor: #555555;
  --panelBgColor: #e4e4e4;
  --sectionBg: #eeeeee;
  --border-color: #cccccc;
  --headingColor: #1a1a1a;
  --textDarkColor: #333333;
  --textLightColor: #666666;
  --text-primary: #212529;
  --text-secondary: #6c757d;
  --text-tertiary: #dddddd;
  --shadowColor: rgba(0, 0, 0, 0.1);
  --bg-primary: #f1f1f1;
  --bg-secondary: #efefef;
  --bg-tertiary: #e4e4e4;
  --btn-active-color: #ffffff;
  --scrollbar-thumb: #bbbbbb;
  --scrollbar-track: rgba(0, 0, 0, 0.05);
  --btn-bg: #dcdcdc;
  --btn-bg-hover: #c8c8c8;
  --icon-filter: grayscale(1);
  --icon-button-svg-color: #444;
  --input-border-color: #646464;
  --input-bg-color: #eeeeee;
  --placeholder-color: #888888;
  --sanctions-tabs-bg: #f2f2f2;
  --tab-hover-bg: rgba(0, 0, 0, 0.05);
  --item-category-bg: #e0e0e0;
  --modal-backdrop: rgba(0, 0, 0, 0.5);
  --logout-color: hsl(0, 0%, 40%);
  --find-cell-hover-bg: var(--btn-bg-hover);
  --charcount-warning-color: #8f8f8f;
  --logo-bg-color: #1a1a1a;
  --bg-no-record-found: #333;
  --text-no-record-found: white;
  --searchPanelStatusActive: #a7a7a7;
  --searchPanelStatusInactive: #7b7b7b;
  --searchPanel: var(--bg-primary);
  --info-bg-color: var(--bg-tertiary);
  --uptime-up-color: #cfcfcf;
  --uptime-degraded-color: #aaaaaa;
  --uptime-partial-color: #808080;
  --uptime-full-color: #4d4d4d;
}

[data-theme="grayscale-dark"] {
  color-scheme: dark;

  --primaryColor: #e8e8e8;
  --secondaryColor: #999999;
  --panelBgColor: #1a1a1a;
  --sectionBg: #242424;
  --border-color: #434343;
  --headingColor: #e8e8e8;
  --textDarkColor: #d8d8d8;
  --textLightColor: #999999;
  --text-primary: #ffffff;
  --text-secondary: #b0b0b0;
  --text-tertiary: #dddddd;
  --shadowColor: rgba(0, 0, 0, 0.4);
  --bg-primary: #212121;
  --bg-secondary: #242424;
  --bg-tertiary: #2e2e2e;
  --btn-active-color: #1e1e1e;
  --scrollbar-thumb: #555555;
  --scrollbar-track: rgba(255, 255, 255, 0.05);
  --btn-bg: #3a3a3a;
  --btn-bg-hover: #4a4a4a;
  --icon-filter: brightness(0) invert(1) grayscale(1);
  --icon-button-svg-color: #ccc;
  --input-border-color: #4a4a4a;
  --input-bg-color: #fff;
  --placeholder-color: #888888;
  --sanctions-tabs-bg: #242424;
  --tab-hover-bg: rgba(255, 255, 255, 0.05);
  --item-category-bg: #333333;
  --modal-backdrop: rgba(0, 0, 0, 0.65);
  --logout-color: hsl(0, 0%, 22%);
  --find-cell-hover-bg: var(--btn-bg-hover);
  --charcount-warning-color: #838383;
  --logo-bg-color: #3a3a3a;
  --bg-no-record-found: #333;
  --text-no-record-found: white;
  --searchPanelStatusActive: #9a9a9a;
  --searchPanelStatusInactive: #6e6e6e;
  --searchPanel: var(--bg-primary);
  --info-bg-color: var(--bg-tertiary);
  --uptime-up-color: #9a9a9a;
  --uptime-degraded-color: #7f7f7f;
  --uptime-partial-color: #666666;
  --uptime-full-color: #4a4a4a;
}

[data-theme="neutral"] {
  color-scheme: dark;

  --primaryColor: #dcdde0;
  --secondaryColor: #6b8fad;
  --panelBgColor: #2c2f34;
  --sectionBg: #3a3d42;
  --border-color: #434343;
  --headingColor: #e4e5e7;
  --textDarkColor: #d3d4d6;
  --textLightColor: #9a9ca0;
  --text-primary: #ffffff;
  --text-secondary: #b0b0b0;
  --text-tertiary: #dddddd;
  --shadowColor: rgba(0, 0, 0, 0.3);
  --bg-primary: #3a3d42;
  --bg-secondary: #34373c;
  --bg-tertiary: #2e3034;
  --btn-active-color: #d3d4d6;
  --scrollbar-thumb: #55585d;
  --scrollbar-track: rgba(255, 255, 255, 0.05);
  --btn-bg: #45484d;
  --btn-bg-hover: #50535a;
  --icon-filter: brightness(0) invert(0.9);
  --icon-button-svg-color: #aaa;
  --input-border-color: #585858;
  --input-bg-color: #313131;
  --placeholder-color: #8b8d91;
  --sanctions-tabs-bg: #34373c;
  --tab-hover-bg: rgba(255, 255, 255, 0.04);
  --item-category-bg: #45484d;
  --modal-backdrop: rgba(0, 0, 0, 0.55);
  --logout-color: hsl(350, 25%, 48%);
  --find-cell-hover-bg: rgba(107, 143, 173, 0.18);
  --charcount-warning-color: #ffc107;
  --logo-bg-color: #273441;
  --bg-no-record-found: #333;
  --text-no-record-found: white;
  --searchPanelStatusActive: #9a9a9a;
  --searchPanelStatusInactive: #6e6e6e;
  --searchPanel: var(--bg-primary);
  --info-bg-color: var(--bg-tertiary);
  --uptime-up-color: #7fae8a;
  --uptime-degraded-color: #c99a8a;
  --uptime-partial-color: #c2665a;
  --uptime-full-color: #9c3f38;
}

/* ── Theme popover menu (built by js/theme.js) ───────────────────── */

.theme-menu {
  position: fixed;
  inset: unset;
  margin: 0;
  padding: 6px;
  border: 1px solid var(--borderColor);
  border-radius: 8px;
  background: var(--bg-primary);
  box-shadow: 0 8px 24px var(--shadowColor);
  max-width: 280px;
}

.theme-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--textDarkColor);
  font-size: 0.9rem;
  text-align: left;
  cursor: pointer;
  height: auto;
}

.theme-menu-item:hover {
  background-color: var(--bg-secondary);
}

.theme-menu-item svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Theme menu items actually render their icon as a CSS-mask <span
   class="icon-theme-*"> (see js/theme.js), not an <svg> — the rule
   above never matched them. Masked icons take their color from
   background-color (see css/svg.css's shared .icon-theme-* base
   rule), not stroke, and need their size pinned here since the
   18px sizing above only ever applied to the (unused) svg selector. */
.theme-menu-item [class*="icon-theme-"] {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  background-color: currentColor;
}

.theme-menu-item span:not([class*="icon-theme-"]):not(.theme-menu-item-check) {
  flex: 1;
}

.theme-menu-item-check {
  width: 16px !important;
  height: 16px !important;
  flex-shrink: 0;
  visibility: hidden;
}

.theme-menu-item[data-active="true"] {
  font-weight: 600;
  color: var(--secondaryColor);
}

.theme-menu-item[data-active="true"] .theme-menu-item-check {
  visibility: visible;
}

/* ── Theme icon row (built by js/theme.js's SiteSettings.initThemeIcons)
   Used on the login page (see login.html) inline in the Login/Plans tab
   row, before the Motion/Fade controls — a compact row of icon-only
   buttons instead of a popover trigger, since there's no header chrome
   to anchor a popover to on that page and every theme should be visible
   and reachable in one glance before the person has even logged in.
   Each button carries a native title="" tooltip with the theme name as
   a hint, since there's no room for a visible text label per icon here.
   ────────────────────────────────────────────────────── */

.theme-icon-row {
  display: flex;
  align-items: center;
  gap: 4px;
}

.theme-icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--textDarkColor);
  cursor: pointer;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease,
    color 0.15s ease;
}

.theme-icon-btn [class*="icon-theme-"] {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  background-color: currentColor;
}

.theme-icon-btn:hover {
  background-color: var(--btn-bg);
}

.theme-icon-btn[data-active="true"] {
  border-color: var(--secondaryColor);
  color: var(--secondaryColor);
  background-color: hsl(from var(--secondaryColor) h s l / 12%);
}

/* ── Motion popover menu (built by js/theme.js-style pattern; now also
   reused inline — see .motion-panel below) ── reuses .theme-menu /
   .theme-menu-item above for visual consistency. Structure: a top-level
   Slide item, a non-interactive "Reduced Motion" header, then indented
   Fade / No Fade items under it, plus an always-visible explainer of
   what these actually control. ────────────────────────────────── */

.motion-menu-header {
  padding: 8px 10px 2px;
  margin-top: 2px;
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--textLightColor);
}

.motion-menu-subitem {
  padding-left: 22px;
}

.motion-menu-note {
  padding: 8px 10px;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--textLightColor);
}

.motion-menu-explainer {
  padding-top: 10px;
  border-top: 1px solid var(--borderColor);
  margin-top: 4px;
}

/* ── Inline Motion settings panel (built by js/motion.js's
   SiteMotion.initMotionPanel, used in User Manager — see
   user-manager-user-vue.html). Plain block container, unlike .theme-menu
   which is a positioned popover — the same .theme-menu-item /
   .motion-menu-* classes above render correctly inside it since none of
   their rules are popover-positioning specific. ────────────────── */

/* ── Edit User / Add User / Password dialog forms (shared by
   user-manager-user-edit.html and user-manager-user-password.html —
   both render inside an Emerge modal via showUserPopup(), which already
   loads theme-variables.css since it's just DOM appended to the same
   document). Previously each partial carried its own near-duplicate
   hardcoded-color <style> block; consolidated here, themed, so both
   stay in sync and adapt across all 5 palettes automatically. ──────── */

:root {
  --actionPanelStatusEnabledColor: #7fad7b;
  --actionPanelStatusDisabledColor: var(--bg-secondary);
  --validation-success-color: #2ecc71;
  --validation-error-color: #e0524a;
}

/* Grayscale must stay hue-free even for these — X/check marks and the
   Save button's "enabled" tint in Edit User / Add User / Password
   would otherwise be the only green/red left in an already fully
   desaturated theme. Reuses the exact grays already established for
   active/inactive status in search-sanctions-lists.html's
   --searchPanelStatusActive/Inactive (lighter = valid/enabled, darker
   = invalid), rather than inventing new ones. */
[data-theme="grayscale-light"] {
  --actionPanelStatusEnabledColor: #a7a7a7;
  --validation-success-color: #a7a7a7;
  --validation-error-color: #7b7b7b;
}

[data-theme="grayscale-dark"] {
  --actionPanelStatusEnabledColor: #9a9a9a;
  --validation-success-color: #9a9a9a;
  --validation-error-color: #6e6e6e;
}

.layout_container {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr;
  height: 100%;
  gap: 12px;
  overflow: auto;
}

.layout_panel_left {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  background-color: var(--bg-secondary);
  color: var(--textDarkColor);
  padding: 12px;
  overflow-y: auto;
  min-height: 0;
}

.layout_panel_right {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  overflow: hidden;
  background: var(--bg-tertiary);
  padding: 12px;
  min-height: 0;
}

.layout_panel_right_top_panel {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
}

.layout_panel_right_bottom_panel {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  background-color: var(--actionPanelStatusDisabledColor);
  padding: 20px 0 10px 0;
  margin-top: 5px;
  flex-shrink: 0;
}

.layout_panel_right_bottom_panel_info {
  margin: 10px 5px 5px 5px;
}

.strikethrough {
  text-decoration: line-through;
}

.layout_info_row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 10px;
}

.text {
  flex-grow: 1;
  font-size: 1rem;
  color: var(--textDarkColor);
}

.small-text-01 {
  font-size: 0.8rem;
  color: var(--textLightColor);
}

#btnAction {
  height: 50px;
  width: 140px;
  font-size: 1rem;
  border: 0;
}

#btnAction:disabled {
  background-color: var(--btn-bg);
  color: var(--textLightColor);
  cursor: not-allowed;
  text-decoration: line-through;
}

#btnClear {
  height: 36px;
  width: 100px;
  font-size: 1rem;
  margin: 5px 0px 5px 0px;
}

.table_layout_details {
  font-size: 1rem;
  width: 100%;
  min-width: 400px;
  color: var(--textDarkColor);
  border-collapse: separate;
  border-spacing: 0 var(--space-xs);
}

.table_layout_details > tbody > tr > th:first-child,
.user_layout > tbody > tr > td:first-child {
  width: 180px;
}

.layout_info {
  overflow-y: auto;
  height: auto;
}

/* Always present from page load (not display:none-until-populated like
   before) so its layout space never shifts when an error appears/clears
   - see js/notification-panel.js, which drives visibility via opacity +
   the notification-panel-visible/notification-panel-truncated classes
   below rather than display/:empty. Fixed single line, ellipsis-
   truncated; overflowing text is reachable via the expand-icon popover
   instead of growing this box. Kept the original red-fill/white-text
   treatment (background-color/color/padding unchanged from before). */
#layout_notification {
  position: relative;
  background-color: var(--validation-error-color);
  color: white;
  padding: 4px 26px 4px 4px;
  margin-bottom: var(--space-sm);
  min-height: 1.6em;
  max-height: 1.6em;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  opacity: 0;
  transition: opacity 200ms ease;
}

#layout_notification.notification-panel-visible {
  opacity: 1;
  animation: notificationPanelFadeIn 200ms ease;
}

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

/* Expand affordance - only inserted by JS once the text is actually
   measured as overflowing (see js/notification-panel.js); never shown
   for a short message with nothing extra to reveal. */
.notification-panel-expand-icon {
  display: none;
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  cursor: pointer;
  background-color: currentColor;
}

#layout_notification.notification-panel-truncated
  .notification-panel-expand-icon {
  display: inline-block;
}

/* Shared popover bubble (one instance, repositioned per click - see
   js/notification-panel.js). Native Popover API [popover] already gets
   top-layer stacking/backdrop-dismiss for free from the browser. */
.notification-panel-popover {
  max-width: 360px;
  padding: 10px 12px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  color: var(--textDarkColor);
  box-shadow: 0 4px 12px var(--shadowColor);
  font-size: 0.9rem;
  line-height: 1.4;
}

/* Feature Preview (shared) - "available in Desktop only" placeholder for
   panels that have a shell but no feature behind them in this web app
   (currently: Monitor's Ongoing Monitoring / Watchlist, and Batch Run,
   all of which live in the CheckComplianceInfo Desktop app instead).
   Two columns: .feature-preview-copy (icon/eyebrow/title/text/note) on
   the left, .feature-preview-mockup (a small illustration of the actual
   Desktop app's own chrome, branded with a placeholder company name) on
   the right. The whole row is top-anchored and horizontally centered
   within its parent .content-box via align-self, so it works as a
   drop-in replacement for the plain h2/p placeholders used before a
   panel is confirmed to be a deliberate "not here" rather than an
   in-progress stub. */
.feature-preview {
  flex: 1;
  align-self: center;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  align-content: flex-start;
  justify-content: center;
  gap: var(--space-xl);
  max-width: 1040px;
  padding: var(--space-lg) 0 0 0;
}

.feature-preview-copy {
  flex: 1 1 260px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: left;
  max-width: 320px;
}

.feature-preview-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  margin-bottom: var(--space-lg);
  border-radius: 50%;
  background: color-mix(in srgb, var(--secondaryColor) 12%, transparent);
}

.feature-preview-icon [class*="icon-"] {
  width: 28px;
  height: 28px;
  background-color: var(--secondaryColor);
}

.feature-preview-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: var(--space-sm);
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--secondaryColor);
  background: color-mix(in srgb, var(--secondaryColor) 12%, transparent);
}

.feature-preview-title {
  margin: 0 0 var(--space-sm) 0;
}

.feature-preview-text {
  color: var(--textLightColor);
  line-height: 1.4;
  text-align: center;
}

/* Standout, themeable panel — replaces the earlier plain hairline-divider
   footnote treatment. Deliberately more visually weighted than
   .feature-preview-text above it: this is the privacy/security rationale
   (telemetry, encryption, obfuscation, no storage), which needs to read
   as a genuine reassurance, not a small-print afterthought. Colors route
   through --secondaryColor (tinted background + left accent bar) and
   --border-color/--bg-secondary, same tokens used for every other
   themed accent panel in this app, so it re-themes automatically across
   all 5 palettes with no per-theme overrides needed. */
.feature-preview-note {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  margin-top: var(--space-md);
  padding: var(--space-md);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-color);
  border-left: 4px solid var(--secondaryColor);
  background: color-mix(in srgb, var(--secondaryColor) 8%, var(--bg-secondary));
  text-align: left;
}

.feature-preview-note-icon {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  background-color: var(--secondaryColor);
}

.feature-preview-note-text {
  margin: 0;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--textDarkColor);
}

/* ── Feature Preview mockup ──────────────────────────────────────────
   A small, static illustration of the Desktop app's own chrome — reuses
   this app's actual tokens (--panelBgColor, --bg-secondary, --bg-primary,
   --secondaryColor, --btn-active-color) so it looks like a shrunk-down
   screenshot of the real thing, not a generic mockup graphic. Two levels
   of nav mirror home.html's real structure: .feature-preview-mockup-tabs
   is the top-level app tabs row (Search / Monitor / etc.), and
   .feature-preview-mockup-sidebar is a sub-tab's own left sidebar (e.g.
   Monitor's Ongoing Monitoring / Watchlist) — omit the sidebar markup
   entirely for panels with no sidebar of their own (Batch Run). */
.feature-preview-mockup {
  flex-shrink: 0;
}

.feature-preview-mockup-bezel {
  background: var(--panelBgColor);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 16px;
}

.feature-preview-mockup-screen {
  width: 600px;
  height: 340px;
  background: var(--bg-primary);
  border-radius: var(--radius-sm);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.feature-preview-mockup-topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 52px;
  padding: 0 16px;
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-color);
  flex-shrink: 0;
}

.feature-preview-mockup-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 16px;
  font-weight: 600;
  color: var(--textDarkColor);
  white-space: nowrap;
}

.feature-preview-mockup-brand-dot {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  background: var(--secondaryColor);
  flex-shrink: 0;
}

.feature-preview-mockup-tabs {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  justify-content: center;
}

.feature-preview-mockup-tab {
  height: 26px;
  min-width: 40px;
  border-radius: 6px;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
}

.feature-preview-mockup-tab-active {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 12px;
  background: var(--secondaryColor);
  border: none;
  color: var(--btn-active-color);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  min-width: 0;
}

.feature-preview-mockup-body {
  flex: 1;
  display: flex;
  min-height: 0;
}

.feature-preview-mockup-sidebar {
  width: 160px;
  flex-shrink: 0;
  background: var(--bg-secondary);
  border-right: 1px solid var(--border-color);
  padding: 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.feature-preview-mockup-sidebar-item {
  height: 28px;
  border-radius: 6px;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
}

.feature-preview-mockup-sidebar-item-active {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 10px;
  background: var(--secondaryColor);
  border: none;
  color: var(--btn-active-color);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}

/* Labeled-but-not-active sidebar ghost item — same neutral bar as
   .feature-preview-mockup-sidebar-item, but with room for a short text
   label (unlike that base rule, which has no flex/font styling since it
   was only ever used blank). Added for Client Manager's mockup sidebar
   (Buy Seats / Add User / Edit User all need to read as real nav items,
   not just one active + blank bars like Monitor/Batch Run) — reusable
   by any future mockup sidebar that needs more than one labeled item. */
.feature-preview-mockup-sidebar-item-labeled {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 10px;
  font-size: 13px;
  color: var(--textDarkColor);
  white-space: nowrap;
}

.feature-preview-mockup-content {
  flex: 1;
  padding: 20px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 12px;
}

/* "Ghost" skeleton bars representing generic screen content — themable
   light-blue tint via --find-cell-hover-bg (the same token used for
   hover/skeleton-style tints elsewhere in this app), which correctly
   desaturates to gray in the grayscale themes rather than being a fixed
   hex blue. display:block is required here: these are <span> elements,
   and width/height on an inline element (a span's default display) are
   ignored by the box model — without this the bars silently collapse to
   zero-size, invisible. */
.feature-preview-mockup-line {
  display: block;
  height: 12px;
  width: 85%;
  border-radius: 4px;
  background: var(--find-cell-hover-bg);
}

.feature-preview-mockup-line-title {
  width: 55%;
  height: 18px;
}

.feature-preview-mockup-content .feature-preview-mockup-line:nth-child(3) {
  width: 92%;
}

.feature-preview-mockup-content .feature-preview-mockup-line:nth-child(4) {
  width: 78%;
}

.feature-preview-mockup-stand {
  width: 112px;
  height: 24px;
  margin: 0 auto;
  background: var(--panelBgColor);
  border: 1px solid var(--border-color);
  border-top: none;
  border-radius: 0 0 8px 8px;
}

.feature-preview-mockup-base {
  width: 192px;
  height: 10px;
  margin: 8px auto 0;
  background: var(--border-color);
  border-radius: 6px;
}

/* Scrollable variant of .feature-preview-mockup-content — used only by
   Monitor's Ongoing Monitoring panel (see home.html), whose #fp-grid now
   holds 12 rows and no longer fits the fixed 340px mockup screen.
   Applied as a second class alongside .feature-preview-mockup-content
   (not folded into that shared rule) so Watchlist/Batch Run/Client
   Manager's short static ghost content stays exactly as it was —
   min-height:0 is what actually lets the child below shrink and scroll
   instead of overflowing the fixed-height mockup screen. */
.feature-preview-mockup-content.fp-content-scroll {
  min-height: 0;
  overflow: hidden;
  width: 100%;
}

.fp-grid-scroll {
  width: 100%;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) transparent;
}

.fp-grid-scroll::-webkit-scrollbar {
  width: 6px;
}

.fp-grid-scroll::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: 3px;
}

/* ── Ongoing Monitoring live-grid mockup ────────────────────────
   Dynamic "spreadsheet" grid used in Monitor's Ongoing Monitoring
   feature-preview-mockup-content (see home.html #fp-grid, wired up by
   js/monitor-mockup.js). Purely decorative — this whole panel is a
   Desktop Application Only placeholder (see feature-preview-note.html),
   no real data or server calls involved.

   grid-auto-rows is fixed (not auto) so rows never resize as a cell's
   text appears/disappears — an empty inline element contributes no
   line box, so auto-sized rows would otherwise grow/shrink by a pixel
   or two every time a cell toggled between filled and empty. */
.fp-grid {
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) repeat(3, minmax(0, 1fr));
  grid-auto-rows: 28px;
  gap: 1px;
  background: var(--border-color);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.fp-grid-cell {
  height: 28px;
  display: flex;
  align-items: center;
  padding: 0 8px;
  background: var(--bg-primary);
  font-size: 11px;
  color: var(--textDarkColor);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background-color 0.5s ease;
}

.fp-grid-cell-name {
  font-weight: 600;
}

.fp-grid-cell.fp-flash {
  background: color-mix(
    in srgb,
    var(--validation-error-color) 22%,
    var(--bg-primary)
  );
}

.fp-grid-value {
  display: inline-block;
  color: var(--validation-error-color);
  opacity: 0;
  transition: opacity 0.5s ease;
}

.fp-grid-value.fp-show {
  opacity: 1;
}

/* Static checked entry for 4 randomly-chosen rows' second column (the
   first data column — see js/monitor-mockup.js) — a name plus a small
   green check icon that never changes; that column is excluded from
   the fill/remove loop for those rows specifically, while the row's
   other two data columns keep animating normally. Reuses the app's
   existing .icon-check CSS-mask class (see css/svg.css) rather than a
   one-off inline SVG, just resized/recolored down from its 28px
   default. Name text uses the normal themed color (--textDarkColor via
   .fp-grid-cell), not the red used for the animated entries. */
.fp-grid-check {
  width: 12px;
  height: 12px;
  background-color: var(--validation-success-color);
  vertical-align: -1px;
  margin: 0 1px;
}

/* Live-activity header row — a blinking dot plus a short label ("Live
   Ongoing Monitoring" / "Live Watchlist monitoring"), sitting above the
   scrollable content in both Monitor sub-panels' mockups (see home.html).
   Fixed (flex-shrink:0) so it never gets squeezed by the scrolling
   content below it — that content lives in .fp-grid-scroll, a sibling
   flex item that takes the remaining space and scrolls on its own. */
.fp-live-header {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  margin-bottom: var(--space-sm);
}

.fp-live-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--textDarkColor);
}

/* Live-activity dot — a themeable blinking circle signaling background
   activity, used inside .fp-live-header. Green/success-toned (var(
   --validation-success-color), themed per-palette including grayscale).
   Plain inline flow now (previously absolutely positioned as a corner
   badge on the mockup sidebar's active item — moved here per the
   "live header row" redesign, and sized up slightly for its new,
   less-cramped spot). */
.fp-badge-dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--validation-success-color);
  animation: fpBadgeBlink 1.2s ease-in-out infinite;
  flex-shrink: 0;
}

@keyframes fpBadgeBlink {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.2;
  }
}

/* ── Watchlist live-source-list mockup ──────────────────────
   Bordered, scrollable list of sanctions/PEP source rows used in
   Monitor's Watchlist feature-preview-mockup-content (see home.html,
   wired up by js/watchlist-mockup.js). Purely decorative — same
   Desktop Application Only placeholder as Ongoing Monitoring's grid
   (see feature-preview-note.html), no real data or server calls
   involved. Sits inside the same .fp-grid-scroll wrapper Ongoing
   Monitoring uses, so it scrolls the same way. */
.fp-source-list {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.fp-source-row {
  padding: 10px 12px;
  border-bottom: 1px solid var(--border-color);
  background: var(--bg-primary);
}

.fp-source-row:last-child {
  border-bottom: none;
}

.fp-source-badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--secondaryColor) 14%, transparent);
  color: var(--secondaryColor);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}

/* Ticker line under each source badge — a looping horizontal marquee
   (2 copies of the same text back to back, animated -50% so the seam
   is invisible) showing a dummy status message for that source.
   js/watchlist-mockup.js swaps the message text and restarts the
   animation on a random interval, and adds one of the 3 flash-* classes
   below for a moment depending on the message's type. */
.fp-ticker-clip {
  overflow: hidden;
  border-radius: var(--radius-sm);
  padding: 2px 4px;
  margin: 6px -4px 0;
  transition: background-color 0.5s ease;
}

.fp-ticker-clip.fp-flash-green {
  background: color-mix(
    in srgb,
    var(--validation-success-color) 25%,
    var(--bg-primary)
  );
}

.fp-ticker-clip.fp-flash-red {
  background: color-mix(
    in srgb,
    var(--validation-error-color) 25%,
    var(--bg-primary)
  );
}

.fp-ticker-clip.fp-flash-yellow {
  background: color-mix(
    in srgb,
    var(--charcount-warning-color) 30%,
    var(--bg-primary)
  );
}

.fp-ticker-track {
  display: flex;
  width: max-content;
  animation: fpTickerScroll 12s linear infinite;
}

.fp-ticker-track span {
  padding-right: 32px;
  font-size: 12px;
  color: var(--textLightColor);
  white-space: nowrap;
}

@keyframes fpTickerScroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

/* Validation status icons: plain colored icons (theme red/green via
   currentColor, no filled badge background) — checkmark reuses the same
   polyline shape used in .theme-menu-item-check, plus a matching X built
   the same way. Toggled by SetValidDataCheckIcon() in each partial's own
   script. Sits in a flex row (.layout_info_row) to the left of .text,
   aligned to the top of the (often multi-line) message rather than
   centered on it. Namespaced .uv- (user-validation) deliberately —
   search-sanctions-lists.html already defines its own unrelated
   .validation-icon class (absolutely positioned, for an inline input
   icon), and since that panel stays in the DOM at all times (Traverse
   only hides it, doesn't remove it), a plain .validation-icon name here
   would collide with it and lose the cascade — its rule sits later in
   the document than this stylesheet, so it would win for every element
   sharing the name. */
.uv-validation-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  margin-top: 2px;
}

.uv-validation-icon.icon-x-mark,
.uv-validation-icon.icon-check {
  width: 18px;
  height: 18px;
}

.uv-validation-icon-valid {
  color: var(--validation-success-color);
}

.uv-validation-icon-invalid {
  color: var(--validation-error-color);
}

/* Radio groups replacing the old Status / Admin User / Timeout <select>
   dropdowns in user-manager-user-edit.html. .layout_container's own
   `input { width:100% }` rule above would otherwise stretch each radio
   button to fill its row, so it's explicitly overridden back to auto
   here. */
.uv-radio-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  align-items: center;
  padding: 6px 0;
}

.uv-radio-label {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: 1rem;
  color: var(--textDarkColor);
  cursor: pointer;
  user-select: none;
  width: auto;
}

.uv-radio-label input[type="radio"] {
  width: auto;
  margin: 0;
  cursor: pointer;
  accent-color: var(--secondaryColor);
}
