/* Enact Studio workspace: "Slate / Cool Data-First" dark theme + the Panel 1/2/3 layout that
   replaced the old sidebar. Loaded after dashboard.css in base.html, so it can override whatever
   light-theme bootstrap/select2 chrome it needs to. Palette validated against the wireframe at
   https://claude.ai/code/artifact/2ed06503-68f1-4d1c-a135-b468143c6700 (Direction A). */

/* theme-independent tokens derived from the per-theme ones below/after -- substituted at html's
   computed-value time, so each [data-theme] block's own --ws-accent/--ws-bg-card is what gets used */
:root {
  /* "currently selected" fill (navigator's active page, Raw/Percentile switch): the accent blended
     15% toward the card color -- softer than the raw accent, still dark enough for white text */
  --ws-accent-selected: color-mix(in srgb, var(--ws-accent) 85%, var(--ws-bg-card));
}

:root {
  --ws-bg: #1b1e22;
  --ws-bg-header: #16181b;
  --ws-bg-strip: #1e2226;
  --ws-bg-card: #22262b;
  /* Trailer Attributes chart series (categorical, fixed order: blue, orange, aqua, violet) --
     validated as a set per theme with the dataviz palette checker against each card surface */
  --ws-attr-series-1: #3987e5;
  --ws-attr-series-2: #d95926;
  --ws-attr-series-3: #199e70;
  --ws-attr-series-4: #9085e9;
  --ws-flag-low: #f2879a; /* Segment Ranking's low-sample flag -- 5.4:1 on --ws-bg-paper */
  --ws-bg-card-alt: #24282d;
  --ws-bg-paper: #2c3137; /* data-table body cells -- lighter than the card */
  --ws-bg-input: #1e2226;
  --ws-border: #33383e;
  --ws-border-soft: #2a2e33;
  --ws-border-input: #3a4046;
  --ws-text: #e8eaed;
  --ws-text-strong: #f2f3f5;
  --ws-text-muted: #9aa1a9;
  --ws-text-faint: #7a8188;
  --ws-text-fainter: #6b7177;
  --ws-text-disabled: #565c62;
  --ws-accent: #4f8ff7;
  --ws-accent-hover: #6aa1f8;
  --ws-accent-text: #ffffff;
  /* darker/more muted than the original #2f9e5c -- that read as too saturated/poppy next to the
     rest of this theme's generally desaturated palette (--ws-accent's blue included). Hover variant
     is a touch lighter than this new base, not darker -- signals "hoverable" the same direction
     --ws-bg-card -> --ws-bg-card-alt already does for the plain .ws-btn hover. */
  --ws-success: #2c7a4c;
  --ws-success-hover: #35915b;
  /* Was its own teal (Vision's nav-tabs + Glossary accordion getting a "tab, not a button" hue);
     dropped 2026-10-15 -- it didn't gel with Silver. Tabs are now folder-style (no fill, see
     .nav-tabs .nav-link.active), so this only colors the Glossary accordion's expanded row -- since
     2026-10-17 the soft-button tint (28% accent, strong text), not the solid selected blue. */
  --ws-tab-accent: color-mix(in srgb, var(--ws-accent) 28%, var(--ws-bg-card));
  --ws-tab-accent-text: var(--ws-text-strong);


  /* rail/stage cards (Rail + Stage layout) */
  --ws-card-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  --ws-nav-hover: rgba(79, 143, 247, 0.14);
}

/* Light theme prototype -- opt-in only (data-theme="light" on <html>, set by
   static/js/workspace_theme.js). Default with no attribute, or data-theme="dark", stays the
   original palette above untouched: nothing about the current dark experience changes unless a
   user explicitly flips the header toggle. Same variable names/roles as the dark :root block, so
   every rule already written against var(--ws-*) re-themes automatically -- only the handful of
   rules below that use hardcoded literals instead of a variable need their own explicit
   [data-theme="light"] override (see "light theme touch-ups" at the end of this file). */
[data-theme="light"] {
  --ws-bg: #f4f5f7;
  --ws-bg-header: #ffffff;
  --ws-bg-strip: #eceef1;
  --ws-bg-card: #ffffff;
  --ws-attr-series-1: #2a78d6;
  --ws-attr-series-2: #eb6834;
  --ws-attr-series-3: #1baf7a;
  --ws-attr-series-4: #6250d6;
  --ws-flag-low: #c42b48;
  --ws-bg-card-alt: #eef0f3;
  --ws-bg-paper: #ffffff; /* data-table body cells -- lighter than the card */
  --ws-bg-input: #ffffff;
  --ws-border: #d8dbe0;
  --ws-border-soft: #e6e8eb;
  --ws-border-input: #c7cbd1;
  --ws-text: #2b2f34;
  --ws-text-strong: #14171a;
  --ws-text-muted: #5c626a;
  --ws-text-faint: #7d838b;
  --ws-text-fainter: #90959c;
  --ws-text-disabled: #b3b8bd;
  --ws-accent: #2f6fe0;
  --ws-accent-hover: #2760c7;
  --ws-accent-text: #ffffff;
  --ws-success: #2f8f5c;
  --ws-success-hover: #278055;
  --ws-tab-accent: color-mix(in srgb, var(--ws-accent) 28%, var(--ws-bg-card));
  --ws-tab-accent-text: var(--ws-text-strong);


  --ws-card-shadow: 0 1px 2px rgba(20, 24, 30, 0.08), 0 2px 8px rgba(20, 24, 30, 0.06);
  --ws-nav-hover: rgba(47, 111, 224, 0.08);
}

/* Silver (experiment/ui-ux, 2026-10-14) -- a mid-tone skin between dark and light: dark text on a
   silver base, cards a step lighter than the page so the data tables read as the foreground. Same
   variable roles as the blocks above; shares light theme's literal touch-ups via
   :is([data-theme="light"], [data-theme="silver"]), plus its own short list at the end of this file.
   The default when nothing is stored (base.html's <head> script). */
[data-theme="silver"] {
  --ws-bg: #c2c7cd;
  --ws-bg-header: #aeb4bb;
  --ws-bg-strip: #b8bec5;
  --ws-bg-card: #d5d9de;
  --ws-attr-series-1: #2a78d6;
  --ws-attr-series-2: #eb6834;
  --ws-attr-series-3: #1baf7a;
  --ws-attr-series-4: #6250d6;
  --ws-flag-low: #a8213b;
  --ws-bg-card-alt: #c9ced4;
  --ws-bg-paper: #dfe3e8; /* data-table body cells -- lighter than the card */
  --ws-bg-input: #dde1e5;
  --ws-border: #9aa1a9;
  --ws-border-soft: #abb1b8;
  --ws-border-input: #8f969f;
  --ws-text: #1d2126;
  --ws-text-strong: #0b0d10;
  --ws-text-muted: #3c424a;
  --ws-text-faint: #535a62;
  --ws-text-fainter: #626970;
  --ws-text-disabled: #858c94;
  --ws-accent: #2a5fc0;
  --ws-accent-hover: #234fa3;
  --ws-accent-text: #ffffff;
  --ws-success: #2a7a50;
  --ws-success-hover: #236843;
  --ws-tab-accent: color-mix(in srgb, var(--ws-accent) 28%, var(--ws-bg-card));
  --ws-tab-accent-text: var(--ws-text-strong);


  --ws-card-shadow: 0 1px 2px rgba(20, 24, 30, 0.18), 0 4px 14px rgba(20, 24, 30, 0.10);
  --ws-nav-hover: rgba(42, 95, 192, 0.12);
}

/* "Ask Nexus" (header button) -- a single hue, blended into each theme's own
   card/border/text tokens so it adapts to Silver/Dark/Light without per-theme literals. Slate since
   2026-10-21: the chat panel's look was picked live (chatbot_widget.js, which reads --ws-chat-hue too),
   and the pill follows it, its fill matching the panel body (20%) and its border the panel's edge
   (55%). Was sage (2026-10-16), picked for the old placeholder modal. Kept clear of --ws-success's
   green, which already means Comps/"add". */
:root {
  --ws-chat-hue: #6f84a3; /* slate */
  --ws-chat-bg: color-mix(in oklab, var(--ws-chat-hue) 20%, var(--ws-bg-card));
  --ws-chat-bg-strip: color-mix(in oklab, var(--ws-chat-hue) 14%, var(--ws-bg-card));
  --ws-chat-bg-bubble: color-mix(in oklab, var(--ws-chat-hue) 30%, var(--ws-bg-card));
  --ws-chat-border: color-mix(in oklab, var(--ws-chat-hue) 55%, var(--ws-border));
  --ws-chat-border-soft: color-mix(in oklab, var(--ws-chat-hue) 25%, var(--ws-border-soft));
  --ws-chat-accent: color-mix(in oklab, var(--ws-chat-hue) 82%, var(--ws-text-strong));
  --ws-chat-accent-text: #ffffff;
  --ws-chat-text: color-mix(in oklab, var(--ws-chat-hue) 12%, var(--ws-text));
  --ws-chat-text-muted: color-mix(in oklab, var(--ws-chat-hue) 20%, var(--ws-text-muted));
}

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

a { color: #6ea8fe; }
a:hover { color: #8fbcff; }

/* ---------- header ---------- */

.ws-header {
  /* sticky so the rail below (also sticky, offset by this height) never slides under it */
  position: sticky;
  top: 0;
  z-index: 1020;
  display: flex;
  align-items: center;
  height: 42px; /* trimmed from 52px 2026-10-15 to match the Portal header (~42px: navbar-brand's .55rem padding + 1rem text) */
  padding: 0 24px;
  background: var(--ws-bg-header);
  border-bottom: 1px solid var(--ws-border-soft);
}

.ws-header-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  color: var(--ws-text-strong);
}

.ws-header-logo {
  height: 22px;
  width: auto;
}

/* app name -- Montserrat in widely tracked caps, ENACT bold / STUDIO regular (picked 2026-10-17 from
   a preview of 7 options; runner-up was Manrope 800/300 mixed case). Caps via text-transform so the
   markup and screen readers keep "Enact Studio". */
.ws-header-name {
  font-family: 'Montserrat', system-ui, sans-serif;
  font-size: 14px;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
}

.ws-header-name-strong {
  font-weight: 700;
}

/* hairline between the name and the beta tag */
.ws-header-divider {
  width: 1px;
  height: 18px;
  background: var(--ws-text-muted);
  opacity: 0.5;
}

/* "beta" -- quiet muted tracked caps beside the name, no fill (the old tinted pill read as a sticker).
   Vertically centered on the divider/name (per the user, 2026-10-17), not sharing the name's
   baseline: line-height 1 keeps its box to the glyphs, so the brand row's align-items centers them */
.ws-beta-badge {
  line-height: 1;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ws-text-muted);
}

/* Light/dark theme toggle -- lives as a plain .dropdown-item in the user dropdown
   (#themeToggleBtn/#themeToggleLabel, see static/js/workspace_theme.js), so it already inherits
   .ws-header-user .dropdown-item's color/layout/icon-sizing rules below; no styling of its own
   needed. */

.ws-header-user {
  position: relative;
}

.ws-header-user-toggle {
  color: var(--ws-text-muted);
  text-decoration: none;
  font-size: 13px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.ws-header-user-toggle:hover {
  color: var(--ws-text);
}

.ws-header-user .dropdown-menu {
  background: var(--ws-bg-card);
  border-color: var(--ws-border);
}

.ws-header-user .dropdown-item {
  color: var(--ws-text);
  font-size: 13px;
  display: flex;
  align-items: center;
}

/* fixed-width icon box, not just a fixed margin -- each fontawesome glyph (user-gear, book,
   arrow-left-from-arc) has a different intrinsic width, so a shared margin alone still leaves the
   following text starting at a different x per icon. Centering every icon inside the same-width
   box lines up the text that follows regardless of which icon it is.

   Targets both <i> and the <svg> FontAwesome's kit script swaps it for at render time (the "SVG
   with JS" framework replaces the original <i> element outright, so an i-only selector silently
   stops matching once the kit script runs -- confirmed via screenshot, margin/width weren't
   applying). */
.ws-header-user .dropdown-item i,
.ws-header-user .dropdown-item svg {
  /* !important on width/height: FontAwesome's kit script sets a per-glyph width via an inline
     style attribute (each icon's own viewBox aspect ratio scaled to font-size), which otherwise
     beats a plain external-stylesheet rule */
  width: 16px !important;
  height: 16px !important;
  font-size: 12px;
  text-align: center;
  margin-right: 10px;
  flex-shrink: 0;
}

/* sign-out's arrow-left-from-arc is a regular-weight line glyph that fills its whole box, so at the
   shared 16px it reads oversized next to the duotone icons -- shrink the glyph, keep the 16px-wide
   box so the "Sign Out" text still lines up with the items above */
.ws-header-user .dropdown-item .ws-menu-icon-sm {
  height: 12px !important;
}

.ws-header-user .dropdown-item:hover,
.ws-header-user .dropdown-item:focus {
  /* was --ws-bg-card-alt, which is only 2 shades off the dropdown's own background -- too subtle
     to read as a hover highlight; matching select2's already-correct hover treatment instead */
  background: var(--ws-accent);
  color: var(--ws-accent-text);
}

.ws-header-user .dropdown-divider {
  border-color: var(--ws-border-soft);
}

/* "Ask Nexus" (base.html) -- the app's one call-to-action, so a solid slate fill with white text
   (2026-10-23: the boss should notice it), not the soft tint used for toggles/action buttons
   ([[feedback_small_control_active_state]]). The fill is the chat hue deepened toward a fixed
   near-black, not --ws-chat-accent: that one blends toward each theme's text color and goes too light
   for white text in Dark. */
.ws-header-chat-btn {
  --ws-chat-pill: color-mix(in oklab, var(--ws-chat-hue) 78%, #1b2433);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-right: 16px;
  /* the original tinted pill's height (~26px), so it sits in the header like its neighbors */
  padding: 4px 13px 4px 11px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: var(--ws-chat-pill);
  color: #ffffff;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.3;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.16);
  transition: background 0.15s ease, box-shadow 0.15s ease;
}

.ws-header-chat-btn svg {
  flex-shrink: 0;
  color: #ffffff;
}

.ws-header-chat-btn:not(:disabled):hover,
.ws-header-chat-btn:not(:disabled):focus-visible {
  background: color-mix(in oklab, var(--ws-chat-hue) 64%, #1b2433);
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.24);
}

/* disabled while the chat server loads and on Tools pages (chatbot_widget.js) */
.ws-header-chat-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

/* chat open: the rail collapses (html.ws-rail-collapsed, chatbot_widget.js) and the header names
   what it was showing (.ws-header-context, base.html) */
.ws-rail-collapsed .ws-main:has(> .ws-rail) {
  grid-template-columns: minmax(0, 1fr);
}

.ws-rail-collapsed .ws-rail {
  display: none;
}

.ws-header-context {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  margin-left: 22px;
  padding-left: 14px;
  border-left: 1px solid var(--ws-border-soft);
}

.ws-header-context[hidden] {
  display: none;
}

.ws-header-rail-toggle {
  display: flex;
  padding: 4px;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--ws-text-muted);
}

.ws-header-rail-toggle:hover {
  background: var(--ws-nav-hover);
  color: var(--ws-text-strong);
}

.ws-header-context-names {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
  font-size: 13px;
  white-space: nowrap;
}

.ws-header-context-names[hidden] {
  display: none;
}

.ws-header-context-title {
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
  color: var(--ws-text-strong);
}

.ws-header-context-view {
  color: var(--ws-text-muted);
}

.ws-header-context-view:not(:empty)::before {
  content: "\00b7";
  margin-right: 8px;
}

/* settings gear + its panel (_user_settings_panel.html) -- sits just left of the user dropdown */
.ws-header-settings {
  margin-right: 18px;
}

.ws-header-settings-toggle {
  background: none;
  border: 0;
  padding: 4px;
  color: var(--ws-text-muted);
  font-size: 15px;
  line-height: 1;
}

.ws-header-settings-toggle:hover,
.ws-header-settings-toggle[aria-expanded="true"] {
  color: var(--ws-text);
}

.ws-header-settings .dropdown-menu {
  background: var(--ws-bg-card);
  border-color: var(--ws-border);
  padding: 0;
}

.ws-settings-panel {
  width: 300px;
  padding: 12px 16px 14px;
  color: var(--ws-text);
  font-size: 13px;
}

.ws-settings-heading {
  color: var(--ws-text-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: 10px;
}

.ws-settings-switch {
  margin-bottom: 4px;
}

.ws-settings-switch .form-check-input {
  background-color: var(--ws-bg-input);
  border-color: var(--ws-border-input);
  cursor: pointer;
}

.ws-settings-switch .form-check-input:checked {
  background-color: var(--ws-accent);
  border-color: var(--ws-accent);
}

.ws-settings-switch .form-check-label {
  cursor: pointer;
}

.ws-settings-hint {
  color: var(--ws-text-faint);
  font-size: 12px;
  line-height: 1.4;
}

.ws-settings-saved {
  margin-top: 10px;
  color: var(--ws-text-muted);
  font-size: 12px;
}

/* ---------- Panel 1: title search + target metadata (layout lives in "Rail + Stage" below) ---------- */

.ws-search-wrap {
  position: relative;
}

.ws-search-icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: #7891ac;
  pointer-events: none;
}

.ws-search-wrap input[name="title_search"] {
  width: 100%;
  height: 34px;
  /* deliberately blue-tinted rather than the neutral card background -- this is the primary
     entry point into the whole workspace, worth its own identity rather than blending in */
  border: 1px solid #3d5978;
  border-radius: 4px;
  padding: 0 30px 0 30px;
  font-size: 13.5px;
  background: #1c2733;
  color: var(--ws-text);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.ws-search-wrap input[name="title_search"]:focus {
  outline: none;
  border-color: var(--ws-accent);
  box-shadow: 0 0 0 3px rgba(79, 143, 247, 0.25);
}

.ws-search-wrap input[name="title_search"]::placeholder {
  color: #7891ac;
}

/* thin svg x in the same muted blue-gray as the magnifier + placeholder (was a bold 18px &times;
   glyph that read heavier than either, and competed with the target card's own corner x below) */
.ws-clear-search-btn {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  border: none;
  background: transparent;
  color: #7891ac;
  line-height: 1;
  cursor: pointer;
  padding: 4px 6px;
}

.ws-clear-search-btn:hover {
  color: var(--ws-text);
}

/* only there when there's something to clear -- an empty box shows its placeholder, so
   :placeholder-shown covers typing, clearing and JS-set values alike. visibility (not display)
   keeps it out of the tab order without shifting anything. */
.ws-search-wrap:has(input[name="title_search"]:placeholder-shown) .ws-clear-search-btn {
  visibility: hidden;
}

.ws-search-hint {
  font-size: 12px;
  color: var(--ws-text-faint);
}

.ws-message-box {
  /* lives in the header (base.html), centered on the page rather than between its neighbours --
     absolute + translate keeps it dead center however wide the brand or user menu get. The blue
     fill reuses the one already established for the metric-group table headers
     (.ws-metric-group-a), so it pops without adding another color family. */
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  max-width: min(640px, calc(100vw - 760px)); /* clears the brand + right cluster (chat button, gear, user) */
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 14px;
  border-radius: 6px;
  border: 1px solid #3d5673;
  background: #283847;
  animation: ws-message-in 0.25s ease-out;
}

.ws-message-icon,
.ws-message-box > svg {
  flex-shrink: 0;
  font-size: 12px;
  color: #8fb0d6;
}

.ws-message-placeholder {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 12.5px;
  font-weight: 500;
  color: #dce3ea;
}

@keyframes ws-message-in {
  from { opacity: 0; transform: translate(-50%, -50%) translateY(-4px); }
  to { opacity: 1; transform: translate(-50%, -50%); }
}

/* narrow windows: no room to float it over the header's middle, so it just takes the gap */
@media (max-width: 1100px) {
  .ws-message-box {
    position: static;
    transform: none;
    flex: 0 1 auto;
    min-width: 0;
    max-width: none;
    margin: 0 16px;
    animation: none;
  }
}

/* setWorkspaceMessage()'s auto-dismiss fade (comparison_dashboard.js, MESSAGE_FADE_MS) */
.ws-message-box.ws-message-leaving {
  opacity: 0;
  transition: opacity 0.4s ease;
}

@media (prefers-reduced-motion: reduce) {
  .ws-message-box { animation: none; }
  .ws-message-box.ws-message-leaving { transition: none; }
}

.ws-poster {
  width: 64px;
  height: 96px;
  flex-shrink: 0;
  background: #2a2f35;
  border: 1px solid var(--ws-border-input);
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  color: #5c636a;
}

.ws-poster img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ws-poster-zoom-btn {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: none;
  background: none;
  cursor: zoom-in;
}

.ws-poster-zoom-btn:hover img {
  opacity: 0.85;
}

/* poster zoom modal -- transparent/borderless dialog shell so it's just the poster, large, over a
   dimmed backdrop; the close button floats over the top-right corner of the image itself */
.ws-poster-modal .modal-dialog {
  max-width: none;
  width: fit-content;
  margin-left: auto;
  margin-right: auto;
}

.ws-poster-modal .modal-content {
  background: transparent;
  border: none;
  width: fit-content;
  margin: 0 auto;
}

.ws-poster-modal-close {
  /* a plain SVG icon under our own control, not Bootstrap's .btn-close (its background-image
     icon mechanism kept rendering as a blank circle here for reasons not worth chasing further) */
  position: absolute;
  top: -14px;
  right: -14px;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background-color: var(--ws-bg-card);
  border: 1px solid var(--ws-border);
  border-radius: 50%;
  color: var(--ws-text);
  cursor: pointer;
}

.ws-poster-modal-close:hover {
  background-color: var(--ws-bg-card-alt);
  color: var(--ws-text-strong);
}

.ws-poster-modal-img {
  display: block;
  max-height: 85vh;
  max-width: 85vw;
  border-radius: 6px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}

.ws-core-meta {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding-top: 2px;
}

.ws-change-title-form {
  margin: 0;
}

/* Clear Target: a small icon in the target card's top-right corner (picked over a labelled button
   2026-10-15 to save rail space). A plain x svg -- reset/circle-x/eject icons were tried as a way to
   tell it apart from the search box's own clear-x, but the plain x still read best. */
.ws-change-title-btn {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 24px;
  height: 24px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ws-text-faint);
  cursor: pointer;
}

.ws-change-title-btn:hover {
  background: var(--ws-bg-card-alt);
  color: var(--ws-text-strong);
}

.ws-meta-line {
  font-size: 13px;
  color: var(--ws-text-muted);
}

/* ---------- shared controls (percentile switch, tabs, accordion, htmx indicators) ---------- */

/* Raw/Percentile display-mode switch (Domestic Trailer only so far) -- pill-shaped (originally
   matching the since-removed Panel 2 Data/Tools toggle), per the user: an important, standalone control deserves its own
   visually separated space (centered in the controls row via .mx-auto, not squeezed in alongside
   Avg. Only) and a more prominent look than a plain checkbox switch. */
.ws-percentile-switch {
  display: inline-flex;
  flex-shrink: 0;
  background: var(--ws-bg-card);
  border: 1px solid var(--ws-border-input);
  border-radius: 999px;
  padding: 2px;
  gap: 2px;
}

.ws-percentile-switch-btn {
  border: none;
  background: transparent;
  color: var(--ws-text-muted);
  font-size: 12px;
  font-weight: 600;
  padding: 4px 12px;
  border-radius: 999px;
  cursor: pointer;
}

.ws-percentile-switch-btn:hover {
  color: var(--ws-text-strong);
}

.ws-percentile-switch-btn.active {
  background: var(--ws-accent-selected); /* same "selected" blue as the navigator's active page */
  color: var(--ws-accent-text);
}

/* Bootstrap nav-tabs/tab-content (Enact Vision's Visual/Content/Audio/... tabs) -- the first place
   this app uses them, so there's no prior dark-theme override to build on; Bootstrap's own defaults
   are a light-mode look (white active-tab background, light gray borders) that clashed hard against
   this theme's dark cards. The active tab is folder-style (see .nav-tabs .nav-link.active below).
   .tab-content shares --ws-bg-card with every other panel on this page (Panel 3's cards,
   .ws-percentile-switch, etc.) so it recedes into the rest of the UI instead of
   standing out as its own white block. */
.nav-tabs {
  border-bottom: 1px solid var(--ws-border);
  margin-bottom: 0;
}

.nav-tabs .nav-link {
  color: var(--ws-text-muted);
  background: transparent;
  border: 1px solid transparent;
  border-top-left-radius: 6px;
  border-top-right-radius: 6px;
  font-size: 13px;
  font-weight: 600;
  padding: 8px 16px;
}

.nav-tabs .nav-link:hover:not(.active):not(:disabled) {
  color: var(--ws-text-strong);
  border-color: var(--ws-border-soft) var(--ws-border-soft) var(--ws-border);
  background: var(--ws-bg-card-alt);
}

/* folder tab (picked 2026-10-15 over the original teal fill, the selected blue and a dark slate):
   the active tab takes the panel's own color and merges into .tab-content below, strong text and a
   3px blue top edge -- reads as a tab, not a button, without adding a hue to Silver. The edge was the
   full selected blue until 2026-10-17; now a softened accent closer to the tinted action buttons
   (.soft-blue-buttons-form / Update Filters' 28% fill + 40% border), a bit stronger than either since
   it's only 3px tall */
.nav-tabs .nav-link.active {
  background: var(--ws-bg-card);
  color: var(--ws-text-strong);
  border-color: var(--ws-border);
  border-bottom-color: var(--ws-bg-card); /* merge into .tab-content below */
  box-shadow: inset 0 3px 0 color-mix(in srgb, var(--ws-accent) 55%, var(--ws-bg-card));
  margin-bottom: -1px;
}

.nav-tabs .nav-link:disabled {
  color: var(--ws-text-disabled);
  background: transparent;
  border-color: transparent;
  cursor: not-allowed;
}

.tab-content {
  background: var(--ws-bg-card);
  border: 1px solid var(--ws-border);
  border-top: none;
  border-radius: 0 0 6px 6px;
  padding: 16px;
}

/* Bootstrap accordion (Glossary tab, _vision_glossary.html) -- same story as nav-tabs just above:
   first use of this component in the app, defaults to a light-mode look (white button/body,
   Bootstrap's own blue focus ring) that clashes against this theme's dark cards. --bs-accordion-*
   are the component's own CSS variables (Bootstrap 5.2+); overriding those, not fighting individual
   rules, is what actually reaches every accordion-button/body state (collapsed, expanded, hover,
   focus) in one place. Expanded-button fill reuses --ws-tab-accent, not --ws-accent, matching the
   nav-tabs' own "this affordance gets its own hue" reasoning above -- both shipped together as part
   of Vision, the first place either component appears in this app. */
.accordion {
  --bs-accordion-color: var(--ws-text);
  --bs-accordion-bg: var(--ws-bg-card);
  --bs-accordion-border-color: var(--ws-border);
  --bs-accordion-btn-color: var(--ws-text);
  --bs-accordion-btn-bg: var(--ws-bg-card);
  --bs-accordion-active-color: var(--ws-tab-accent-text);
  --bs-accordion-active-bg: var(--ws-tab-accent);
  --bs-accordion-btn-focus-border-color: var(--ws-tab-accent);
  --bs-accordion-btn-focus-box-shadow: none;
}

.accordion-button::after {
  /* Bootstrap's default chevron SVG is baked-in dark-on-transparent -- invisible against a dark
     button background. This is the same chevron path, just recolored via a plain CSS filter
     (brightness/invert) rather than re-encoding a new data URI, since the two accordion-button
     states (collapsed/expanded) already swap between --ws-text and --ws-tab-accent-text above, both
     light enough that inverting the original dark-line icon reads correctly against either. */
  filter: invert(1) grayscale(1);
}

.ws-vision-glossary-list,
.ws-vision-glossary-list ul {
  list-style-type: none;
  padding-left: 0;
}

.ws-vision-glossary-list ul li {
  margin: 2px 4px;
}

/* htmx's own indicator convention (not htmx-bundled CSS -- the page has to define this itself):
   .htmx-indicator elements are hidden by default and only shown for the duration of a request, on
   whichever element(s) carry the .htmx-request class htmx toggles automatically. Generic/reusable
   by any future hx-get/hx-post element in the app, not specific to Panel 2's view-switcher below. */
.htmx-indicator {
  opacity: 0;
  transition: opacity 120ms ease;
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
  opacity: 1;
}

/* display: inline-flex above beats the browser's [hidden] rule (see .ws-view-page-row's comment) --
   needed for features the REGION strip hides when COM is selected (_workspace_view_switcher.html) */
.ws-view-btn[hidden] {
  display: none;
}

.ws-view-btn.htmx-request {
  pointer-events: none; /* guards against a second click starting an overlapping request */
}

.ws-view-btn.htmx-request .ws-view-btn-label {
  opacity: 0.25;
}

.ws-view-btn-disabled,
.ws-view-btn-disabled:hover {
  border-color: var(--ws-border-soft);
  background: #1c1f22;
  color: var(--ws-text-disabled);
  cursor: not-allowed;
}

/* ---------- Rail + Stage layout ---------- */

/* experiment/ui-ux, 2026-10-14 (see [[project_layout_study_rail_stage]]): replaced the Panel 1/2/3
   vertical stack so Panel 3's tables are the front-and-center element. Every workspace page renders
   _workspace_rail.html (Panel 1 + Panel 2 as two floating cards in a left column) next to
   #workspace-panel3 (the stage card). The page itself scrolls, not the stage -- select2 dropdowns
   and Bootstrap popovers inside Panel 3 position against the page, and a scrolling stage container
   would knock them out of place -- so the rail is sticky instead, offset below the sticky header. */
.ws-main {
  width: 100%;
}

.ws-main:has(> .ws-rail) {
  display: grid;
  grid-template-columns: 276px minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  padding: 12px;
}

.ws-rail {
  position: sticky;
  top: 54px; /* .ws-header's 42px + the grid's 12px padding */
  max-height: calc(100vh - 66px);
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-height: 0;
}

.ws-panel-1,
.ws-panel-2,
#workspace-panel3 {
  background: var(--ws-bg-card);
  border: 1px solid var(--ws-border-soft);
  border-radius: 8px;
  box-shadow: var(--ws-card-shadow);
}

/* Panel 1: search, compact target card, message line */
.ws-panel-1 {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
}

.ws-metadata-2a {
  position: relative; /* anchors the corner Clear Target button */
  display: flex;
  gap: 10px;
  align-items: flex-start;
}

.ws-core-meta {
  min-width: 0;
}

.ws-title-name {
  padding-right: 20px; /* clear of the corner Clear Target button */
  font-size: 15px;
  font-weight: 700;
  line-height: 1.25;
  color: var(--ws-text-strong);
}

.ws-panel-1 .ws-meta-line {
  font-size: 12px;
}

/* opens #releaseDatesModal (base.html) -- quiet like the meta lines above it, hover tint only (no
   blue text, see [[feedback_no_blue_text]]) */
.ws-release-dates-btn {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  margin: 0 0 0 -5px; /* icon lines up with the meta text's left edge */
  padding: 0;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--ws-text-muted);
  cursor: pointer;
}

.ws-release-dates-btn:hover {
  background: var(--ws-bg-card-alt);
  color: var(--ws-text-strong);
}

.ws-release-dates-modal .modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.ws-release-dates-modal .modal-title {
  display: flex;
  align-items: center;
  font-size: 15px;
  font-weight: 700;
}

/* same calendar as the card's trigger button, in a soft neutral tile (no blue icons, see
   [[feedback_no_blue_text]]) */
.ws-release-dates-heading-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  margin-right: 11px;
  border-radius: 7px;
  background: var(--ws-bg-card-alt);
  border: 1px solid var(--ws-border-soft);
  color: var(--ws-text-muted);
}

.ws-release-dates-heading-sub {
  white-space: pre;
}

.ws-release-dates-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ws-text-faint);
}

.ws-release-dates-close:hover {
  background: var(--ws-bg-card-alt);
  color: var(--ws-text-strong);
}

.ws-release-dates-heading-sub {
  font-weight: 400;
  color: var(--ws-text-faint);
}

.ws-release-dates-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px 16px;
}

.ws-release-dates-cell {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 5px 8px;
  border-radius: 5px;
  font-size: 13px;
}

.ws-release-dates-cell:hover {
  background: var(--ws-bg-card-alt);
}

.ws-release-dates-code {
  flex: 0 0 22px;
  font-weight: 700;
  color: var(--ws-text-strong);
}

.ws-release-dates-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ws-text-faint);
}

/* fixed-width, left-aligned slot so every date in a column starts at the same x -- right-aligned
   (the old flex end) let a 2-digit day like "Jan 22" jut out left of "Jan 8" */
.ws-release-dates-date {
  flex: 0 0 7.5em;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--ws-text);
}

.ws-release-dates-date.is-tbd {
  color: var(--ws-text-disabled);
}

@media (max-width: 767px) {
  .ws-release-dates-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.ws-change-title-form {
  margin: 0;
}

/* no target yet: the card renders empty (_workspace_title_metadata.html) -- drop it from the
   panel's flex gap entirely rather than leave a blank row */
.ws-metadata:not(:has(*)) {
  display: none;
}

/* only shows when there's something to say -- confirmations and access notices
   (setWorkspaceMessage, comparison_dashboard.js) land here */
.ws-message-box:has(#ws-message-text:empty) {
  display: none;
}

/* Panel 2: the navigator */
/* the card itself doesn't scroll -- a scroller's own border-radius never clips its scrollbar, which
   left a square gray track jutting into the top-right corner. Instead the card clips (overflow:
   hidden, so the radius applies) and the inner .ws-panel-2-scroll scrolls, with a transparent track
   so only a slim rounded thumb ever shows. */
.ws-panel-2 {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  overflow: hidden;
}

.ws-panel-2-scroll {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  padding: 10px 8px 12px 6px;
  scrollbar-width: thin;
  scrollbar-color: var(--ws-border-input) transparent;
}

/* group buttons (experiment/nav-groups, 2026-10-21): an icon column on the card's left edge */
.ws-nav-groups {
  flex: none;
  width: 70px;
  box-sizing: border-box;
  padding: 10px 6px;
  border-right: 1px solid var(--ws-border-soft);
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ws-nav-group-btn {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 9px 2px 7px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ws-text-muted);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}

.ws-nav-group-btn .ws-nav-icon {
  width: 20px;
  height: 20px;
  color: currentColor;
}

.ws-nav-group-btn:hover {
  background: var(--ws-nav-hover);
  color: var(--ws-text-strong);
}

/* shown group: the small-control accent tint + dark text (same as the region toggle's active state) */
.ws-nav-group-btn.active,
.ws-nav-group-btn.active:hover {
  background: color-mix(in srgb, var(--ws-accent) 28%, var(--ws-bg-card));
  color: var(--ws-text-strong);
}

/* "the loaded page is in here" -- only while that group isn't the one shown */
.ws-nav-group-dot {
  display: none;
  position: absolute;
  top: 7px;
  right: 10px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ws-accent-selected);
}

.ws-nav-group-btn[data-has-active]:not(.active) .ws-nav-group-dot {
  display: block;
}

/* "Data" / "Tools" inside a group -- quiet, like the old DATA/TOOLS headings, with a faint hairline
   underneath to mark where each set starts */
.ws-nav-sublabel {
  margin: 0 9px 6px;
  padding: 10px 0 5px;
  border-bottom: 1px solid color-mix(in srgb, var(--ws-border-soft) 60%, transparent);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ws-text-faint);
}

.ws-nav-section-block:first-child .ws-nav-sublabel {
  padding-top: 0;
}

.ws-nav-section-block[hidden] {
  display: none;
}

.ws-region-seg {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border-radius: 6px;
  background: var(--ws-bg-card-alt);
  border: 1px solid var(--ws-border-soft);
  letter-spacing: 0.04em;
}

.ws-region-seg button {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 20px;
  padding: 0 7px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--ws-text-faint);
  cursor: pointer;
}

.ws-region-seg button .ws-nav-icon {
  width: 12px;
  height: 12px;
  color: currentColor;
}

.ws-region-seg button:hover {
  color: var(--ws-text-strong);
}

/* active region: an accent tint, dark text, no outline (2026-10-15, per the user: the old
   raised-card look was too subtle, the nav links' full accent fill too strong, and an accent
   outline read as a border inside the control's own border) */
.ws-region-seg button.active,
.ws-region-seg button.active:hover {
  background: color-mix(in srgb, var(--ws-accent) 28%, var(--ws-bg-card));
  color: var(--ws-text-strong);
}

.ws-view-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  /* min-height, not height: a name too long for one line (Audience Compfinder, since the group
     column) wraps to two and the row grows, instead of ellipsizing */
  min-height: 33px;
  padding: 6px 9px;
  margin-bottom: 2px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--ws-text-strong);
  font-size: 13.5px;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
}

.ws-nav-icon {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--ws-text-faint);
}

.ws-view-btn-label {
  flex: 1;
  min-width: 0;
  line-height: 1.25;
}

/* .htmx-indicator (opacity 0 until the request starts), sat at the row's right edge */
.ws-view-btn-spinner {
  flex: none;
  width: 14px;
  height: 14px;
  border-width: 0.15em;
  color: currentColor;
}

.ws-view-btn:hover {
  background: var(--ws-nav-hover);
  border-color: var(--ws-border-input);
  color: var(--ws-text-strong);
  text-decoration: none;
}

/* no blue text or icons in the navigator (per the user) -- the hover tint + border carry it */
.ws-view-btn:hover .ws-nav-icon {
  color: var(--ws-text-strong);
}

/* current page: a solid --ws-accent-selected fill (so it stays clearly distinct from the light
   hover tint) -- the raw accent read as too intense (per the user, 2026-10-15); a pale tint looked
   too much like hover, and a 25% blend started costing the white text its definition. */
.ws-view-btn.active,
.ws-view-btn.active:hover {
  background: var(--ws-accent-selected);
  border-color: transparent;
  color: var(--ws-accent-text);
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
}

.ws-view-btn.active .ws-nav-icon {
  color: var(--ws-accent-text);
}

/* every view's Panel 3 shows the same "select a title" empty state until a target exists, so
   switching views first achieves nothing -- pointer-events, not just the dimmed look. Except a page
   that doesn't need one (data-target-optional: Latest Tracking, see access.TARGET_OPTIONAL_PAGES) */
.ws-panel-2[data-no-target="true"] .ws-view-btn:not([data-target-optional]) {
  opacity: 0.55;
  pointer-events: none;
}

/* groups inside a section: a faint hairline inset to the links' content edges, in a tighter gap
   than the plain 10px spacer it replaced (2026-10-15) -- the line carries the separation so the
   space doesn't have to. The region toggle's JS still hides a divider with no visible link on both
   sides (comparison_dashboard.js). */
.ws-view-divider {
  display: block;
  height: 10px;
}

/* scoped to the navigator: Competition/Compfinders' control rows reuse .ws-view-divider as a
   plain spacer and keep the rule above */
.ws-panel-2 .ws-view-divider {
  /* starts where the link text does (1px border + 9px padding + 16px icon + 10px gap = 36px), the
     macOS/iOS grouped-list convention -- reads as a lesser break than the headings' rules, which run
     from the left edge. Tried 2026-10-15 and passed over: short centered lines (40%, 66%), a full
     inset line, a dotted one. */
  height: 1px;
  margin: 3px 10px 5px 36px; /* + the link above's own 2px margin-bottom = 5px either side */
  background: var(--ws-border-soft);
  opacity: 0.7;
}

.ws-view-divider[hidden] {
  display: none;
}

/* Panel 3's page header (_page_header.html, 2026-10-21): "Group › Page", and the region -- the
   DOM/GLOBAL switch (the region toggle's look, as links) or a fixed chip, at the right end. A band
   across the card's top edge (bled out over #workspace-panel3's padding, its own tint + a bottom
   rule) so it reads as the display's title bar, apart from the page's own controls row below. */
/* three columns (2026-10-22): breadcrumb left, region switch/chip centered, the chip + Bookmarks +
   Share group right. Each child names its column, so a page with no region control keeps the group
   on the right and the middle just stays empty */
.ws-page-header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 14px;
  min-height: 48px;
  margin: -16px -20px 16px;
  padding: 8px 20px;
  border-bottom: 1px solid var(--ws-border-soft);
  border-radius: 8px 8px 0 0;
  background: var(--ws-bg-card-alt);
}

.ws-page-title {
  grid-column: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--ws-text-strong);
}

.ws-page-title-group {
  font-weight: 500;
  color: var(--ws-text-muted);
}

.ws-page-title-sep {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--ws-text-faint);
}

.ws-page-region-seg a {
  display: flex;
  align-items: center;
  gap: 5px;
  height: 24px;
  padding: 0 10px;
  border-radius: 4px;
  font-size: 11px;
  font-weight: 700;
  color: var(--ws-text-faint);
  text-decoration: none;
  cursor: pointer;
}

.ws-page-region-seg a .ws-nav-icon {
  width: 12px;
  height: 12px;
  color: currentColor;
}

.ws-page-region-seg a:hover {
  color: var(--ws-text-strong);
}

.ws-page-region-seg a.active {
  background: color-mix(in srgb, var(--ws-accent) 28%, var(--ws-bg-card));
  color: var(--ws-text-strong);
  cursor: default;
}

.ws-page-region-seg a .ws-page-region-spinner {
  display: none;
  width: 12px;
  height: 12px;
  border-width: 2px;
}

/* while the other region loads (GLOBAL can take a few seconds): the clicked option's icon becomes a
   spinner (the htmx-indicator inside it), and the option takes the hover color */
.ws-page-region-seg a.htmx-request {
  color: var(--ws-text-strong);
  pointer-events: none;
}

.ws-page-region-seg a.htmx-request .ws-nav-icon {
  display: none;
}

.ws-page-region-seg a.htmx-request .ws-page-region-spinner {
  display: inline-block;
}

.ws-page-header > .ws-page-region-seg,
.ws-page-header > .ws-page-region-chip {
  grid-column: 2;
}

.ws-page-header-actions {
  grid-column: 3;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.ws-page-region-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 24px;
  padding: 0 9px;
  border: 1px solid var(--ws-border-soft);
  border-radius: 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ws-text-muted);
}

.ws-page-region-chip .ws-nav-icon {
  width: 12px;
  height: 12px;
  color: currentColor;
}

/* the page header's Share button (_page_header.html): the region chip's outlined shape, neutral at
   rest, the hover tint other small controls use (no blue text); its icon turns into a spinner while
   the link is being made */
.ws-page-share-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding: 0 10px;
  border: 1px solid var(--ws-border-soft);
  border-radius: 6px;
  background: transparent;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ws-text-muted);
  cursor: pointer;
}

.ws-page-share-btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--ws-accent) 14%, var(--ws-bg-card-alt));
  color: var(--ws-text-strong);
}

.ws-page-share-btn:disabled {
  cursor: progress;
}

.ws-page-share-spinner {
  display: none;
  width: 11px;
  height: 11px;
  border-width: 2px;
}

.ws-page-share-btn:disabled .ws-page-share-spinner {
  display: inline-block;
}

.ws-page-share-btn:disabled .fa-link {
  display: none;
}

/* the page header's Bookmarks dropdown (_page_header.html, _bookmarks_menu.html): the toggle reuses
   .ws-page-share-btn's look; the menu is the settings dropdown's card. Row icons show on hover/focus,
   neutral text with the usual hover tint (no blue text) */
.ws-bookmark-menu {
  width: 320px;
  max-height: 60vh;
  overflow-y: auto;
  padding: 10px 0 8px;
  background: var(--ws-bg-card);
  border-color: var(--ws-border);
  color: var(--ws-text);
  font-size: 13px;
}

.ws-bookmark-save {
  display: flex;
  gap: 6px;
  padding: 0 12px 8px;
  border-bottom: 1px solid var(--ws-border-soft);
}

.ws-bookmark-input {
  flex: 1;
  min-width: 0;
  height: 28px;
  padding: 0 8px;
  border: 1px solid var(--ws-border-input);
  border-radius: 6px;
  background: var(--ws-bg-card-alt);
  color: var(--ws-text);
  font-size: 13px;
}

.ws-bookmark-save-btn {
  height: 28px;
  padding: 0 12px;
  border: 0;
  border-radius: 6px;
  background: color-mix(in srgb, var(--ws-accent) 28%, var(--ws-bg-card-alt));
  color: var(--ws-text-strong);
  font-size: 12px;
  font-weight: 600;
}

.ws-bookmark-heading {
  padding: 10px 14px 4px;
  color: var(--ws-text-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.ws-bookmark-row {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 8px 0 4px;
}

.ws-bookmark-row:hover,
.ws-bookmark-row:focus-within {
  background: color-mix(in srgb, var(--ws-accent) 14%, var(--ws-bg-card));
}

.ws-bookmark-link {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding: 5px 10px;
  color: var(--ws-text);
  text-decoration: none;
}

.ws-bookmark-link:hover {
  color: var(--ws-text-strong);
}

.ws-bookmark-name,
.ws-bookmark-page {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ws-bookmark-page {
  color: var(--ws-text-muted);
  font-size: 11px;
}

.ws-bookmark-actions {
  display: flex;
  gap: 2px;
  visibility: hidden;
}

.ws-bookmark-row:hover .ws-bookmark-actions,
.ws-bookmark-row:focus-within .ws-bookmark-actions,
.ws-bookmark-row.is-confirming .ws-bookmark-actions {
  visibility: visible;
}

.ws-bookmark-actions button {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 24px;
  padding: 0 6px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--ws-text-muted);
  font-size: 12px;
}

.ws-bookmark-actions button:hover {
  background: color-mix(in srgb, var(--ws-accent) 18%, var(--ws-bg-card));
  color: var(--ws-text-strong);
}

/* Delete's second click: the trash icon gives way to a "Delete?" confirm */
.ws-bookmark-confirm {
  display: none;
  font-weight: 600;
}

.ws-bookmark-row.is-confirming [data-bookmark-action="delete"] .ws-bookmark-confirm {
  display: inline;
}

.ws-bookmark-row.is-confirming [data-bookmark-action="delete"] i,
.ws-bookmark-row.is-confirming [data-bookmark-action]:not([data-bookmark-action="delete"]) {
  display: none;
}

.ws-bookmark-row .ws-bookmark-input {
  margin: 3px 0 3px 6px;
}

.ws-bookmark-empty {
  padding: 10px 14px 4px;
  color: var(--ws-text-muted);
  font-size: 12px;
}

/* the loaded bookmark chip: which bookmark this view came from, and Update to overwrite it */
.ws-loaded-bookmark {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 260px;
  height: 24px;
  padding: 0 4px 0 10px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--ws-accent) 14%, var(--ws-bg-card-alt));
  color: var(--ws-text-strong);
  font-size: 12px;
  font-weight: 600;
}

.ws-loaded-bookmark[hidden] {
  display: none;
}

.ws-loaded-bookmark > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ws-loaded-bookmark i {
  color: var(--ws-text-muted);
  font-size: 11px;
}

.ws-loaded-bookmark button {
  height: 18px;
  padding: 0 7px;
  border: 1px solid var(--ws-border-soft);
  border-radius: 4px;
  background: transparent;
  color: var(--ws-text-muted);
  font-size: 11px;
  font-weight: 600;
}

.ws-loaded-bookmark button:hover:not(:disabled) {
  background: color-mix(in srgb, var(--ws-accent) 18%, var(--ws-bg-card-alt));
  color: var(--ws-text-strong);
}

/* Panel 3: the stage */
#workspace-panel3 {
  min-width: 0;
  min-height: calc(100vh - 66px);
  padding: 16px 20px 20px;
}

/* below this the rail would squeeze wide tables too hard -- stack it above the stage instead */
@media (max-width: 900px) {
  .ws-main:has(> .ws-rail) {
    grid-template-columns: minmax(0, 1fr);
  }

  .ws-rail {
    position: static;
    max-height: none;
  }
}

/* ---------- Panel 3: controls bar + results ---------- */

.ws-controls-form {
  margin-bottom: 12px;
}

.ws-controls-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  /* same value .ws-controls-form already applies as its own margin-bottom on trailer/tracking
     pages (where this row sits inside a <form>) -- Posters has no <form> wrapper at all, so without
     this the row sat with no gap above its results at all. Harmless on pages that do have the form:
     just a few extra px above the (usually collapsed) filters panel, invisible in the common case. */
  margin-bottom: 12px;
}

.ws-status {
  font-size: 12.5px;
  color: var(--ws-text-faint);
}

/* the currently-selected Norm, shown to the left of the Filters button on every survey-data page --
   purely a reflection of the <select name="norm"> element inside the (collapsed) Filters panel, kept
   in sync live by syncNormDisplay() (comparison_dashboard.js), not server-rendered per filter change
   -- norm isn't wired into any page's actual query yet (placeholder field only), so there's nothing
   server-side to reflect beyond the field's own initial default. */
.ws-norm-display {
  font-size: 12.5px;
  color: var(--ws-text-faint);
  white-space: nowrap;
  flex-shrink: 0;
  margin-right: 14px;
}

.ws-norm-display [data-norm-display-value],
.ws-norm-display [data-intl-calendar-region-display] {
  color: var(--ws-text-muted);
  font-weight: 600;
}

/* Intl Calendar only -- the currently-selected region, shown next to Norm in the same display since
   the region strip's own highlighted pill wasn't enough of an indicator on its own (per the user
   2026-09-03). ws-norm-display-sep is the bullet between the two -- deliberately given its own
   margin rather than relying on literal whitespace in the template, so the gap is a precise, easy to
   adjust value instead of "however much space happens to be in the source". */
.ws-norm-display-sep {
  margin: 0 8px;
  color: var(--ws-text-faint);
  font-size: 16px;
}

.ws-btn {
  height: 32px;
  padding: 0 12px;
  border-radius: 4px;
  border: 1px solid var(--ws-border-input);
  background: var(--ws-bg-card);
  color: var(--ws-text);
  font-size: 13px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.ws-btn:hover:not(:disabled) {
  background: var(--ws-bg-card-alt);
}

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

/* controls-row action buttons -- eraser (Clear Comparison), +/- Comps and CSV export on every
   view (2026-10-15): outlined pills in the same shape/outline as the Raw/Percentile and
   Trailers/Spots switches they sit beside, muted icon/label at rest -- replaced solid green blocks
   that didn't gel with the rounded controls. .ws-pill-btn-go = the "do/add" actions (Comps, export):
   green arrives only on hover, as a light tint + green label. The eraser hovers neutral. Placed after
   .ws-btn's own rules so the equal-specificity base + hover rules win on cascade order. */
.ws-pill-btn {
  height: 34px;
  padding: 0 16px;
  border-radius: 999px;
  border-color: var(--ws-border-input);
  background: var(--ws-bg-card);
  color: var(--ws-text-muted);
  font-weight: 500;
}

.ws-pill-btn:hover:not(:disabled) {
  background: var(--ws-bg-card-alt);
  color: var(--ws-text-strong);
}

.ws-pill-btn-go:hover:not(:disabled) {
  background: color-mix(in srgb, var(--ws-success) 14%, var(--ws-bg-card));
  border-color: color-mix(in srgb, var(--ws-success) 45%, var(--ws-border-input));
  color: var(--ws-success);
}

[data-theme="dark"] .ws-pill-btn-go:hover:not(:disabled) {
  color: var(--ws-success-hover);
}

/* Update Filters (its only use) -- the same accent tint + dark text as the open Filters toggle
   (.ws-filter-toggle-btn below) rather than a full accent fill (2026-10-16) */
.ws-btn-primary {
  background: color-mix(in srgb, var(--ws-accent) 28%, var(--ws-bg-card));
  border-color: color-mix(in srgb, var(--ws-accent) 40%, var(--ws-border-input));
  color: var(--ws-text-strong);
}

/* must come after the generic .ws-btn:hover:not(:disabled) rule (equal specificity, cascade order
   breaks the tie) -- without it, hover fell through to the neutral --ws-bg-card-alt (Update
   Filters, 2026-10-05). Same for .ws-pill-btn's hovers. */
.ws-btn-primary:hover:not(:disabled) {
  background: color-mix(in srgb, var(--ws-accent) 36%, var(--ws-bg-card));
}

.ws-combined-toggle {
  color: var(--ws-text-muted);
  font-size: 13px;
}

/* Native checkbox/switch inputs (Release Formats, Hide Distributor on both Calendar pages;
   Competition's own filter checkboxes) don't get a hand cursor on hover by default the way every
   other clickable control in this app does (.ws-btn, .ws-view-btn, .ws-region-toggle, etc. all set
   this explicitly) -- per the user 2026-09-03, they should read as clickable the same way. Covers
   both the input itself and its <label> (Bootstrap's .form-check-label), since clicking either
   toggles the same control; :not(:disabled) guards against any future disabled checkbox reading as
   clickable when it isn't. */
.form-check-input:not(:disabled),
.form-check-label {
  cursor: pointer;
}

.ws-current-target {
  color: var(--ws-text-muted);
  font-size: 15px;
  margin: 0;
}

.ws-filter-toggle-btn {
  color: var(--ws-text-muted);
}

/* Bootstrap's collapse JS already keeps aria-expanded accurate on this button automatically (it's
   the data-bs-toggle="collapse" trigger for #advancedFiltersPanel) -- keying off that directly
   means the "open" state needs no JS of its own, just reusing the same active look .ws-view-btn/
   .ws-region-toggle already use elsewhere. */
/* open: the small-control tint recipe (same as the DOM/WW region toggle) rather than a full accent
   fill -- tint + dark text, the button's own border just picks up a little accent (2026-10-16) */
.ws-filter-toggle-btn[aria-expanded="true"] {
  background: color-mix(in srgb, var(--ws-accent) 28%, var(--ws-bg-card));
  border-color: color-mix(in srgb, var(--ws-accent) 40%, var(--ws-border-input));
  color: var(--ws-text-strong);
}

/* hover while open -- needs the :hover:not(:disabled) to out-specify .ws-btn's generic hover, which
   otherwise swapped in the plain card fill (2026-10-16) */
.ws-filter-toggle-btn[aria-expanded="true"]:hover:not(:disabled) {
  background: color-mix(in srgb, var(--ws-accent) 36%, var(--ws-bg-card));
}

.ws-filters-panel {
  margin-top: 10px;
  padding: 14px 16px;
  background: var(--ws-bg-card);
  border: 1px solid var(--ws-border);
  border-radius: 6px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Calendar's filters panel wraps .ws-filters-row/.ws-filters-actions in a real <form> (a native
   submit, unlike every other view's JS-driven button -- see _calendar_panel3.html), which makes
   the form the only direct child of .ws-filters-panel and so the gap above never reaches its own
   children. display: contents lets the row/actions divs participate in .ws-filters-panel's flex
   layout as if the <form> wrapper weren't there, while the form itself still submits normally. */
.ws-filters-panel > form {
  display: contents;
}

.ws-filters-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
  align-items: start;
}

.ws-filters-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.ws-filters-hint {
  font-size: 12px;
  color: var(--ws-text-faint);
}

/* Region toggle strip (Intl Trailer) -- pure client-side show/hide, see comparison_dashboard.js's
   refreshRowVisibility(). Lives inline inside .ws-controls-row (see _intl_trailer_panel3.html),
   pushed to the right of Avg. Only via its own ms-auto, same row as Clear/+ Comps/Filters/Export.
   Deliberately reuses .ws-btn's squared corners/border/height language (not a rounded pill) so it
   reads as part of the same control family instead of a bolted-on widget. */
.ws-region-toggle-strip {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  /* no margin-right of its own -- extra breathing room before whatever follows (a button on
     Posters, a divider on Competition) is that page's own concern, not baked into this shared
     class. It used to be a fixed 18px here, added for Posters' "Clear" button specifically; on
     Competition that stacked on top of .ws-controls-row's own flex gap AND .ws-view-divider's own
     margin, producing visibly uneven spacing on either side of the divider. Posters keeps its
     original spacing via ms-2 directly on its own Clear button (_posters_panel3.html) instead. */
}

.ws-region-toggle-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ws-text-fainter);
  margin-right: 2px;
}

.ws-region-toggle {
  position: relative; /* anchors ws-region-toggle-spinner on Intl Calendar's own region bar (a real
    htmx round trip per button, unlike every other page using this class) -- a no-op for those other
    pages, which never render a spinner child */
  height: 28px;
  min-width: 30px;
  padding: 0 8px;
  border-radius: 4px;
  border: 1px solid var(--ws-border-input);
  background: var(--ws-bg-card-alt);
  color: var(--ws-text-muted);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  /* a plain <button> centers its own text well enough by browser default, but this class is also
     applied to a <label> (Posters' region filter -- wraps a hidden checkbox + text, see
     _posters_panel3.html), which is inline by default and doesn't self-center at a fixed height.
     Explicit flex centering makes it reliable for both element types. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.ws-region-toggle:hover:not(.active):not(:has(input:checked)) {
  border-color: var(--ws-text-faint);
  color: var(--ws-text);
}

/* .active is set server-side (Posters/Intl) or client-side via JS (Intl's own toggle strip);
   :has(input:checked) is the same look driven purely by a real checkbox's native state instead --
   Competition's region strip has no server round-trip and no JS class-toggling to do this any other
   way (see refreshCompetitionFilters(), comparison_dashboard.js, which only ever reads .checked,
   never sets .active). */
.ws-region-toggle.active,
.ws-region-toggle:has(input:checked) {
  background: var(--ws-accent);
  border-color: var(--ws-accent);
  color: var(--ws-accent-text);
  font-weight: 700;
}

/* Loading feedback for Intl Calendar's region bar (_intl_calendar_panel3.html) -- each button is its
   own htmx element there (unlike every other .ws-region-toggle use, which is pure client-side), so
   htmx's own .htmx-request class lands directly on the clicked button, same as .ws-view-btn's own
   spinner-overlay pattern above -- see that rule's comment for the centering/animation reasoning,
   just retargeted at this class's smaller 28px size and .ws-region-toggle-code label span instead of
   .ws-view-btn-label. */
.ws-region-toggle-spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -8px;
  margin-left: -8px;
  color: currentColor;
  /* hidden by default -- Intl Calendar's own usage also carries .htmx-indicator (redundant with
     this but harmless) for its per-button request; Compfinders' pills below rely on this base rule
     directly instead, since their visibility toggle needs the ":has(input:checked)" scoping added
     below, not the generic "any .htmx-indicator inside an .htmx-request ancestor" rule. */
  opacity: 0;
  transition: opacity 120ms ease;
}

.ws-region-toggle.htmx-request {
  pointer-events: none; /* guards against a second click starting an overlapping request */
}

.ws-region-toggle.htmx-request .ws-region-toggle-code {
  opacity: 0.25;
}

/* Compfinders' region/# comps/format pills (_compfinders_panel3.html) -- unlike Intl Calendar's
   region bar above, every pill in a group shares ONE hx-get on the wrapping .ws-controls-row (see
   that file's own comment), so htmx's .htmx-request class lands on the row, not on whichever pill
   was clicked. Scoping down to ":has(input:checked)" lights up the currently-active pill in each of
   the three groups (not just whichever one was actually clicked) -- an accurate reflection of the
   request itself, which resends and re-evaluates all three current values together, not only the one
   that changed. Its radio reflects the click instantly (a plain client-side state change, no need to
   wait on the response), so the just-clicked pill's own spinner appears with no perceptible delay. */
.ws-controls-row.htmx-request {
  pointer-events: none; /* guards against a second control changing mid-request */
}

.ws-controls-row.htmx-request .ws-region-toggle:has(input:checked) .ws-region-toggle-code {
  opacity: 0.25;
}

.ws-controls-row.htmx-request .ws-region-toggle:has(input:checked) .ws-region-toggle-spinner {
  opacity: 1;
}

/* Reset Filters button (_compfinders_panel3.html) -- issues its own hx-get independent of the row
   above, so .htmx-request lands directly on the button, same as .ws-view-btn's spinner-overlay
   pattern (see that class's comment) -- reused here rather than duplicated. */
.ws-btn-with-spinner {
  position: relative;
}

.ws-btn-spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -8px;
  margin-left: -8px;
  color: currentColor;
}

.ws-btn.htmx-request {
  pointer-events: none; /* guards against a second click starting an overlapping request */
}

.ws-btn.htmx-request .ws-btn-icon {
  opacity: 0.25;
}

.ws-region-toggle-all {
  /* a plain "select every region" action, not a stateful toggle -- see
     comparison_dashboard.js's regionToggleAll click handler -- so it's never given the .active
     look, just a subtle outline treatment to read as an action rather than a pill */
  color: var(--ws-text-faint);
  border-style: dashed;
}

.ws-region-toggle-all:hover {
  color: var(--ws-text);
  border-color: var(--ws-text-faint);
}

/* One .ws-region-section per region (Intl Trailer) -- each wraps a full comparison table, hidden
   entirely when empty or toggled off (see comparison_dashboard.js's refreshRegionSectionVisibility()).
   ws-region-heading echoes the PowerBI reference's outer Territory column, just as a section heading
   here instead of a leftmost table column, since Region is the outer grouping but Title still owns
   the tbody -- see [[project_intl_trailer_panel]]. */
.ws-region-section {
  margin-top: 22px;
}

.ws-region-section:first-of-type {
  margin-top: 12px;
}

.ws-region-heading {
  font-size: 13px;
  margin: 0 0 6px;
  /* no border here -- the table immediately below already draws that line, a second one read as
     redundant clutter */
}

.ws-region-heading-code {
  /* "soft" -- the 2-char code that ties this heading back to its toggle-strip pill, kept muted so
     the full name (the actually-informative part) carries the visual weight */
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--ws-text-faint);
}

.ws-region-heading-sep {
  color: var(--ws-text-disabled);
  margin: 0 2px;
}

.ws-region-heading-name {
  font-weight: 700;
  color: var(--ws-text-strong);
}

/* Combined views' single grouped tables (combined_*_data_dash.html) -- Territory (or Metric)
   is a real leftmost column here, like PBI's. Each group's first visible tbody gets a heavier top
   border (refreshRowGroups(), comparison_dashboard.js) so groups read as blocks; tbodies
   within a group (target + comps) keep the normal thin divider. Theme tokens only, so both themes. */
.ws-grouped-table .ws-group-cell {
  vertical-align: middle;
  text-align: center;
  white-space: nowrap;
  width: 1%;
}

/* Territory Comparison's Metric label -- metric names are long ("Post-Trailer Urgency: TH Opg Wknd"),
   so this group cell wraps instead of forcing the column wide */
.ws-grouped-table .ws-group-cell.ws-group-cell-wrap {
  white-space: normal;
  text-align: left;
  min-width: 140px;
  max-width: 180px;
}

/* a territory that didn't test this trailer (or has no data for the title at all) -- greyed like
   PBI's empty cells, distinct from a tested-but-no-value cell's dash. A translucent fill rather than
   a theme token: the soft border token was nearly the row's own color in dark mode, so the cells
   didn't read as intentionally empty -- and a 7% fill was still too faint to notice (2026-09-28). */
.ws-grouped-table td.ws-cell-na {
  background: rgba(255, 255, 255, 0.15);
}

:is([data-theme="light"], [data-theme="silver"]) .ws-grouped-table td.ws-cell-na {
  background: rgba(0, 0, 0, 0.12);
}

.ws-grouped-table .ws-group-label {
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--ws-text-strong);
}

.ws-grouped-table tbody.ws-row-group-start > tr:first-child > td {
  border-top: 3px solid var(--ws-border-input);
}

#dash-error .alert {
  background: #3a2a1c;
  border-color: #6b4a24;
  color: #f0c896;
}

/* the alert's dismiss X -- Bootstrap's own .btn-close is a dark glyph, so lighten it for the dark
   theme the same way Bootstrap's .btn-close-white does (a filter, not a background override --
   background shorthand would wipe its icon, see the .btn-close note further up); light theme resets it */
#dash-error .alert .btn-close {
  filter: invert(1) grayscale(100%) brightness(200%);
}

/* Box Office (_box_office_panel3.html) -- informational, not an error/warning, so this gets its own
   quieter blue-toned treatment rather than reusing #dash-error's orange one. */
.ws-box-office-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  margin-bottom: 12px;
  border-radius: 6px;
  background: #1c2733;
  border: 1px solid #2d4258;
  color: var(--ws-text-muted);
  font-size: 13px;
}

.ws-box-office-banner i {
  color: var(--ws-accent);
}

/* Opening Wknd column is the headline number on this page (per the user's boss) and gets its own
   standing tint so it reads apart from the rest of the table -- blue (the app's own accent hue)
   rather than green, since green/red on this page already carries the % dev heatmap's meaning and
   reusing it here would read as a deviation signal instead of a "this column matters" one.
   !important needed: Bootstrap's own `.table > :not(caption) > * > *` cell rule sets
   background-color: var(--bs-table-bg) and outweighs a single class selector on specificity --
   same fix already used just below for tbody tr.combined-row. */
.ws-box-office-highlight-col {
  background: rgba(79, 143, 247, 0.08) !important;
}

thead .ws-box-office-highlight-col {
  background: rgba(79, 143, 247, 0.16) !important;
  color: var(--ws-text-strong);
}

/* Competition (_competition_panel3.html) -- one section per region, stacked (not tabbed, matching
   this app's established preference over the reference's Bootstrap tabs -- see
   [[project_intl_trailer_panel]] for the same call on Intl Trailer). */
.ws-competition-region {
  margin-bottom: 20px;
}

.ws-competition-region:last-child {
  margin-bottom: 0;
}

.ws-competition-region-header {
  font-size: 13px;
  font-weight: 600;
  color: var(--ws-text-strong);
  margin-bottom: 6px;
}

.ws-competition-region-header i,
.ws-competition-region-header svg {
  /* svg too, not just i -- FontAwesome's kit script replaces <i> with an <svg> at render time, so
     an i-only rule silently stops applying once that runs (same issue as the header dropdown
     icons) */
  color: var(--ws-text-faint);
  margin-right: 8px;
}

/* the target's own row -- a muted accent tint, not the reference's light-mode #eef1ff, so it still
   reads correctly against this theme's dark table rows */
tr.ws-competition-target-row {
  background: rgba(79, 143, 247, 0.22) !important;
  font-weight: 600;
}

/* :not(.ws-competition-target-row) -- the target row keeps its own distinct tint even on hover
   rather than getting replaced by this generic one; :hover's extra specificity would otherwise win
   out over tr.ws-competition-target-row's plain class selector. !important needed regardless --
   same Bootstrap .table > :not(caption) > * > * background-color specificity gotcha documented on
   .ws-box-office-highlight-col above. */
.ws-competition-region tbody tr:not(.ws-competition-target-row):hover {
  background: var(--ws-bg-card-alt) !important;
}

/* Compfinders (Audience) -- same tint/reasoning as .ws-competition-target-row above, own class
   rather than reusing that one since it's named for a different page and this table has no
   .ws-competition-region wrapper to scope a shared selector against. */
tr.ws-compfinders-target-row {
  background: rgba(79, 143, 247, 0.22) !important;
  font-weight: 600;
}

/* Top 5 ranked comps -- lighter tint than the target row's, just enough to help the eye find the
   best matches in a long table. Excluded from the generic hover rule below like the target row is,
   so its tint doesn't flicker off on hover. */
tr.ws-compfinders-top-row {
  background: rgba(255, 255, 255, 0.05) !important;
}

#dash-results .table tbody tr:not(.ws-compfinders-target-row):not(.ws-compfinders-top-row):hover {
  background: var(--ws-bg-card-alt) !important;
}

#dash-results .table tbody tr.ws-compfinders-top-row:hover {
  background: rgba(255, 255, 255, 0.09) !important;
}

/* Compfinders' Distributor filter -- first real <select> dropdown in the workspace dark theme (every
   other filter so far has been a pill row or checkbox); native selects otherwise render with the
   browser's default light-mode chrome, which clashes badly against this app's dark background. Sized
   to match .ws-btn/.ws-region-toggle's 32px/28px control heights already used in the same row. */
.ws-select {
  height: 32px;
  padding: 0 26px 0 8px;
  border-radius: 4px;
  border: 1px solid var(--ws-border-input);
  background: var(--ws-bg-card);
  color: var(--ws-text);
  font-size: 13px;
  max-width: 220px;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23888' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}

.ws-select:hover {
  border-color: var(--ws-text-faint);
}

.ws-competition-skip-note {
  font-size: 12px;
  color: var(--ws-text-faint);
  margin-bottom: 10px;
}

.ws-competition-skip-note i {
  color: var(--ws-text-faint);
  margin-right: 4px;
}

/* Calendar -- same target-row tint/hover treatment as Competition above, applied to the one shared
   table instead of one table per region. */
tr.ws-calendar-target-row {
  background: rgba(79, 143, 247, 0.22) !important;
  font-weight: 600;
}

/* Star Fanship -- row-hover treatment, no target-row exclusion needed here (no row is highlighted
   as special to begin with, roster or added). A plain background rule on tr:hover would get fully
   painted over by the heatmap cells' own inline background-color -- table cell backgrounds paint on
   top of their row's background regardless of !important, since they're a different element's
   background, not a specificity fight on the same one. An inset box-shadow on every <td> in the
   hovered row instead overlays a translucent tint on top of whatever's already there (heatmap color
   or plain), so the highlight actually reads across the whole row. */
.ws-star-fanship-table tbody tr:hover td {
  box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.08);
}

:is([data-theme="light"], [data-theme="silver"]) .ws-star-fanship-table tbody tr:hover td {
  box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.05);
}

/* Calendar -- same box-shadow technique as Star Fanship above (2026-09-14 fix; this table's hover
   used to be a plain tr:hover background, which the heatmap metric cells' own inline
   background-color silently painted over -- only the plain columns (Release Friday/Format/Title/
   Distributor) ever visibly highlighted). Keeps the target-row exclusion (unlike Star Fanship) since
   that row already has its own permanent tint -- see tr.ws-calendar-target-row above. */
.ws-calendar-table tbody tr:not(.ws-calendar-target-row):hover td {
  box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.08);
}

:is([data-theme="light"], [data-theme="silver"]) .ws-calendar-table tbody tr:not(.ws-calendar-target-row):hover td {
  box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.05);
}

/* Enact Vision -- same box-shadow technique as Star Fanship/Calendar above, for the same reason: a
   plain tr:hover background would get painted over by vision_cell/vision_bool_cell's own inline
   background-color (the heatmap/flag tint). No target-row exclusion needed, same as Star Fanship --
   no row on any of the six Vision tables is highlighted as special to begin with. One shared class
   (.ws-vision-table) on all six table partials rather than a per-tab class, since the same hover
   treatment applies identically everywhere. */
.ws-vision-table tbody tr:not(.ws-vision-norm-row):hover td {
  box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.08);
}

:is([data-theme="light"], [data-theme="silver"]) .ws-vision-table tbody tr:not(.ws-vision-norm-row):hover td {
  box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.05);
}

/* Trailer Norm / Spot Norm reference row (VisionNorms) at the bottom of the Visual/Content/Audio
   tables -- italic, muted, on the alt card tint, so it reads as a benchmark rather than one more
   trailer; no heatmap (vision_plain_cell) and no hover, it's not a data row. */
.ws-vision-table tbody tr.ws-vision-norm-row td {
  background-color: var(--ws-bg-card-alt);
  color: var(--ws-text-muted);
  font-style: italic;
}

.ws-vision-table tbody tr.ws-vision-norm-row td:first-child {
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Every comparison table (DOM/INTL/COM Trailer, Tracking, Territories -- all [data-comparison-table])
   -- same box-shadow technique as Star Fanship/Calendar/Vision above, for the same reason: the
   heatmap cells' inline background-color paints over a plain tr:hover background, so only the plain
   columns ever highlighted (2026-09-28). */
[data-comparison-table] tbody tr:hover td {
  box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.08);
}

:is([data-theme="light"], [data-theme="silver"]) [data-comparison-table] tbody tr:hover td {
  box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.05);
}

/* A title block's rowspan cells (role radio, title, region/date) belong to the block's FIRST <tr>
   only, so the rule above lit them up on that row and nowhere else -- hovering the 2nd+ trailer of a
   title highlighted just the data row (2026-10-15, per the user: felt inconsistent moving around the
   table). Each title block is its own <tbody>, so light every rowspan cell while ANY of its rows is
   hovered. Territories' segment cell spans only a sub-group of the tbody, so it's opted out with
   .ws-subgroup-cell and never highlights (lit-on-first-row-only would be the same inconsistency). */
[data-comparison-table] tbody:has(> tr:hover) > tr > td[rowspan]:not(.ws-subgroup-cell) {
  box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.08);
}

:is([data-theme="light"], [data-theme="silver"]) [data-comparison-table] tbody:has(> tr:hover) > tr > td[rowspan]:not(.ws-subgroup-cell) {
  box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.05);
}

[data-comparison-table] tbody tr:hover td.ws-subgroup-cell {
  box-shadow: none;
}

/* Trailer Comps' Comp/Title columns (vision_comps_cell, studio_extras.py) -- each cell can stack
   several comps, which read as a crowded wall of text with a bare <br> join. A little vertical
   breathing room plus a faint divider between lines (not on the last, so the cell doesn't end in a
   dangling border) makes each comp read as its own row without a real nested table. */
.ws-comp-line:not(:last-child) {
  padding-bottom: 0.35rem;
  margin-bottom: 0.35rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

:is([data-theme="light"], [data-theme="silver"]) .ws-comp-line:not(:last-child) {
  border-bottom-color: rgba(0, 0, 0, 0.08);
}

/* Trailer Comps -- a row's Comp/Title <td>s can stack several .ws-comp-line entries, so the shared
   .ws-vision-table row-hover above (fills every td in the whole tr) highlights the entire multi-line
   block as one slab instead of giving per-comp feedback. Cancel that shared fill on rows containing
   comp lines (:has(), already used the same way for Calendar's target-row exclusion), then hover the
   Trailer name cell on its own (it has no .ws-comp-line children) and each comp line independently. */
.ws-vision-table tbody tr:has(.ws-comp-line):hover td {
  box-shadow: none;
}

.ws-vision-table tbody tr:has(.ws-comp-line) > td:not(:has(.ws-comp-line)):hover {
  box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.08);
}

:is([data-theme="light"], [data-theme="silver"]) .ws-vision-table tbody tr:has(.ws-comp-line) > td:not(:has(.ws-comp-line)):hover {
  box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.05);
}

.ws-comp-line:hover {
  background: rgba(255, 255, 255, 0.08);
  border-radius: 4px;
}

:is([data-theme="light"], [data-theme="silver"]) .ws-comp-line:hover {
  background: rgba(0, 0, 0, 0.05);
}

/* "Hide Distributor" (_calendar_panel3.html) -- pure display filter via :has(), same technique
   Posters' "Hide un-tested" already uses (see that rule's comment above for the full reasoning).
   Scoped to #workspace-panel3 so it still reaches .calendar-dist-col cells after #calendar-results
   itself gets swapped independently by the Filters form -- the toggle checkbox lives outside that
   swap target and keeps its checked state either way. */
#workspace-panel3:has(#hideDistToggle:checked) .calendar-dist-col {
  display: none;
}

/* Weeks Before/After stepper -- click-only (+/- buttons), no text input: per the user, a value
   that's rarely more than 3 or 4 doesn't need a full-width box, and a native number input's spin
   arrows only show on hover, an unreliable click target. Sized to match .ws-region-toggle's 28px
   height so it sits flush in the same controls row. */
.ws-stepper-group {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.ws-stepper-label {
  font-size: 12px;
  color: var(--ws-text-muted);
}

.ws-stepper {
  display: inline-flex;
  align-items: center;
  height: 28px;
  border-radius: 4px;
  border: 1px solid var(--ws-border-input);
  background: var(--ws-bg-card-alt);
  overflow: hidden;
}

.ws-stepper-btn {
  width: 24px;
  height: 100%;
  border: none;
  background: transparent;
  color: var(--ws-text-muted);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}

.ws-stepper-btn:hover {
  background: var(--ws-bg-card);
  color: var(--ws-text);
}

.ws-stepper-value {
  min-width: 18px;
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  color: var(--ws-text);
}

/* Format / Movie·Series -- two checkbox pairs, no group heading (their own captions are self-
   explanatory), separated slightly from each other and laid out horizontally rather than stacked. */
.ws-competition-check-pair {
  display: inline-flex;
  align-items: center;
  gap: 14px;
}

/* Bootstrap's .form-check-inline margin-right (1rem) stacked on top of the pair gap + row gap left
   ~40px between checkboxes and pushed the export button onto its own line (2026-10-19) -- the
   pair's own gap is the only spacing now. */
.ws-competition-check-pair .form-check-inline {
  margin-right: 0;
}

.ws-competition-check-pair .form-check-label {
  color: var(--ws-text-muted);
  font-size: 13px;
}

#dash-results {
  min-height: 1px;
}

.ws-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 60px 0;
  color: var(--ws-border-input);
}

.ws-empty-text {
  font-size: 14px;
  color: var(--ws-text-disabled);
}

/* comparison table dark reskin -- heatmap cell colors are set inline by studio_extras.py and are
   unchanged here, they already read fine on dark */

.table.table-bordered {
  color: var(--ws-text);
  border-color: var(--ws-border);
}

.table.table-bordered thead th {
  border-color: var(--ws-border);
  color: var(--ws-text-muted);
  background: var(--ws-bg-strip);
}

/* metric-group header cells (e.g. "Post-Trailer Definite Interest") -- these used to be hardcoded
   light pastels (#cce7FF etc.) meant for dark text on a light page; against this theme's muted
   thead text color that read as washed-out and hard to read. Dark, hue-tinted backgrounds (same
   4-hue cycle, just darkened/desaturated to sit in this palette) with strong, bold text; the hue
   backgrounds themselves are set per metric just below. */
.table.table-bordered thead th.ws-metric-group {
  font-weight: 700;
  color: var(--ws-text-strong);
}

/* each metric's header band in its own hue (blue/green/amber/plum, cycling -- metric_group_letter in
   studio_extras.py), its segment row underneath a lighter tint of the same hue so a segment's metric
   reads at a glance. Every rule carries the full `.table.table-bordered thead th` prefix: bare
   .ws-metric-group-* classes lost to that rule's flat --ws-bg-strip and never showed at all from
   the 2026-09-01 overhaul until 2026-10-15 (picked over flat / one slate color / a dark band). */
.table.table-bordered thead th.ws-metric-group-a { background: #24344a; }
.table.table-bordered thead th.ws-metric-group-b { background: #1f3a2f; }
.table.table-bordered thead th.ws-metric-group-c { background: #3c2e1a; }
.table.table-bordered thead th.ws-metric-group-d { background: #362a3d; }
.table.table-bordered thead th.ws-seg-group-a { background: #1e2b3c; }
.table.table-bordered thead th.ws-seg-group-b { background: #1b3028; }
.table.table-bordered thead th.ws-seg-group-c { background: #30261a; }
.table.table-bordered thead th.ws-seg-group-d { background: #2c2432; }
.table.table-bordered thead th:is(.ws-metric-group, [class*="ws-seg-group-"]) { color: var(--ws-text-strong); }

/* the plain (non-metric) header cells -- Role / Title / Trailer / T-*, Calendar's and Fanship's
   equivalents -- on the card-alt gray, one step darker than the comparison tables' paper body rows
   (picked 2026-10-15 over the original strip gray, a cool slate, and a dark slate band) */
.table.table-bordered thead th:not(.ws-metric-group):not([class*="ws-seg-group-"]) {
  background: var(--ws-bg-card-alt);
  color: var(--ws-text-strong);
}

[data-theme="light"] .table.table-bordered thead th.ws-metric-group-a { background: #dce9fb; }
[data-theme="light"] .table.table-bordered thead th.ws-metric-group-b { background: #dcf3e4; }
[data-theme="light"] .table.table-bordered thead th.ws-metric-group-c { background: #faeccb; }
[data-theme="light"] .table.table-bordered thead th.ws-metric-group-d { background: #f1e2f7; }
[data-theme="light"] .table.table-bordered thead th.ws-seg-group-a { background: #edf3fd; }
[data-theme="light"] .table.table-bordered thead th.ws-seg-group-b { background: #edf9f1; }
[data-theme="light"] .table.table-bordered thead th.ws-seg-group-c { background: #fdf6e4; }
[data-theme="light"] .table.table-bordered thead th.ws-seg-group-d { background: #f8f0fb; }

[data-theme="silver"] .table.table-bordered thead th.ws-metric-group-a { background: #b7c9e3; }
[data-theme="silver"] .table.table-bordered thead th.ws-metric-group-b { background: #b8d6c3; }
[data-theme="silver"] .table.table-bordered thead th.ws-metric-group-c { background: #dfcfa6; }
[data-theme="silver"] .table.table-bordered thead th.ws-metric-group-d { background: #d2c2dc; }
[data-theme="silver"] .table.table-bordered thead th.ws-seg-group-a { background: #cdd9ea; }
[data-theme="silver"] .table.table-bordered thead th.ws-seg-group-b { background: #cde0d3; }
[data-theme="silver"] .table.table-bordered thead th.ws-seg-group-c { background: #e6dcc0; }
[data-theme="silver"] .table.table-bordered thead th.ws-seg-group-d { background: #dcd1e3; }

.table.table-bordered td,
.table.table-bordered th {
  border-color: var(--ws-border-soft);
}

.table.table-bordered tbody tr.combined-row {
  background: var(--ws-bg-card-alt) !important;
}

/* every data table's body on the light "paper" tone (--ws-bg-paper, lighter than the card) instead
   of the card gray -- per the user 2026-10-15, first for the comparison tables' title area, then any
   cell not showing heat-mapped data (Vision was full of drab ones). Set on the ROW, not the cell, so
   the deliberately tinted rows (target rows, Compfinders' top 5, hover -- all !important tr rules)
   still win, and heatmap cells' inline background still paints on top. */
.table.table-bordered > tbody > tr {
  background-color: var(--ws-bg-paper);
}

/* comparison tables: cell-level, since the Avg row's own darker shade (a hair under paper, so it
   still separates from trailer rows when expanded) has to beat the !important combined-row rule */
[data-comparison-table] tbody td { background: var(--ws-bg-paper); }
[data-comparison-table] tbody tr.combined-row > td { background: #292e33; }
[data-theme="light"] [data-comparison-table] tbody tr.combined-row > td { background: #f6f7f9; }
[data-theme="silver"] [data-comparison-table] tbody tr.combined-row > td { background: #d8dde2; }

/* Avg row: medium-weight numbers (500 -- 600 still read a touch heavy), not bold, and a quiet "Avg." label (2026-10-15, per the user, after
   the lighter PBI-matched heatmap -- full bold read heavy next to PBI's plain-weight cells, and the
   label says the same thing on every title so it doesn't need weight). The tinted row background
   already marks it as the summary row. */
[data-comparison-table] tbody tr.combined-row > td.ws-toggle-cell { font-weight: 500; }
[data-comparison-table] tbody tr.combined-row > td.ws-avg-label { font-weight: 400; color: var(--ws-text-muted); }

.title-label-cell {
  color: var(--ws-text-strong);
}

/* the title name stays on one line, like PBI -- the column widens to fit it instead of the table
   squeezing it into a 2-line wrap (2026-10-15); a wide table scrolls inside its own container */
.title-label-cell .ws-title-cell-name {
  white-space: nowrap;
}

/* ...except while its cell spans several rows (trailer/wave rows shown, i.e. not Avg. Only --
   refreshRowVisibility resets rowspan to 1 when collapsed): the tall cell has room for a 2-line name,
   and the trailer names beside it stay on one line instead of being squeezed (2026-10-21) */
.title-label-cell[rowspan]:not([rowspan="1"]) .ws-title-cell-name {
  white-space: normal;
}

/* rows with a remove x: reserve its slot after the name. Without this the column sized itself to the
   nowrap name alone and the floated x (20px + its 6px/-4px side margins = 22px) sat on top of the
   name's last characters. Padding counts toward the column width, so it now grows by the x's slot. */
.title-label-cell:has(.ws-row-remove) .ws-title-cell-name {
  padding-right: 22px;
}

/* remove-row x on a comp/extra title cell (and added stars) -- was Bootstrap's .btn-close-white,
   a white glyph that vanished on the Silver/Light themes and sat too large. Same plain svg x as
   Panel 1's Clear Target, muted until hovered. */
.ws-row-remove {
  float: right;
  width: 20px;
  height: 20px;
  margin: -2px -4px 0 6px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ws-text-faint);
  cursor: pointer;
}

.ws-row-remove:hover {
  background: var(--ws-bg-strip);
  color: var(--ws-text-strong);
}

/* role-cell: bullseye for the target's own row, a thin ring for comp rows, nothing for
   an arbitrary extra row the user searched in on their own (see comparison_dashboard.js's file
   header comment) -- replaces the old anchor-radio control (commit 1934841). */
/* target marker -- the strongest text color, not blue (per the user, 2026-10-15): hierarchy by
   weight against the faint comp ring, and can't be mistaken for a heatmap cell color (amber was
   tried and passed over) */
.ws-target-icon {
  color: var(--ws-text-strong);
  font-size: 15px;
}

/* comp marker (_comp_icon.html) -- a thin ring in the faintest gray, so the comps sit quietly
   under the target bullseye */
.ws-comp-icon {
  display: inline-flex;
  color: var(--ws-text-faint);
}

/* general-purpose: a muted rowspan cell grouping several rows under one region label, with its own
   right border so the grouping reads left-to-right without relying on indentation. Currently used
   by the Posters testing-history table (_posters_panel3.html). */
.region-cell {
  color: var(--ws-text-muted);
  font-size: 12.5px;
  border-right: 1px solid var(--ws-border) !important;
}

/* Posters (_posters_panel3.html) -- one card per poster, Bootstrap's own data-bs-toggle="collapse"
   for expand/collapse (same aria-expanded-driven chevron-flip trick as .ws-filter-toggle-btn),
   custom .ws-* classes rather than Bootstrap's raw .card/.card-header/.card-body since those ship
   styled for a light theme by default and this app has no light-theme fallback. */
.ws-poster-card {
  background: var(--ws-bg-card);
  border: 1px solid var(--ws-border);
  border-radius: 6px;
  margin-bottom: 14px;
  overflow: hidden;
}

.ws-poster-card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 14px;
  cursor: pointer;
  font-weight: 600;
  color: var(--ws-text-strong);
  background: var(--ws-bg-card-alt);
}

.ws-poster-card-header i {
  color: var(--ws-text-faint);
  transition: transform 150ms ease;
}

.ws-poster-card-header[aria-expanded="false"] i {
  transform: rotate(180deg);
}

.ws-poster-card-body {
  padding: 16px;
}

.ws-poster-image {
  max-width: 100%;
  border-radius: 4px;
  border: 1px solid var(--ws-border);
}

/* "Hide un-tested" (_posters_panel3.html) -- every poster card is always in the DOM (tested or
   not), so this is a pure display filter, same role Avg-Only plays on the comparison-table views.
   Implemented with :has() instead of a JS toggle-class binding so Posters stays the one workspace
   view with zero custom JS -- :has() lets #hideUntestedToggle's checked state reach the poster
   cards even though they're not adjacent siblings of it in the DOM (a plain ~ sibling combinator
   couldn't reach across #workspace-panel3's own structure the way this needs to). Scoped to
   #workspace-panel3 rather than body/:root -- survives every htmx swap of that element the same way
   (a fresh #hideUntestedToggle each time), just kept as specific as it can reasonably be. */
#workspace-panel3:has(#hideUntestedToggle:checked) .ws-poster-card--untested {
  display: none;
}

/* ---------- form controls / select2 / collapse panel dark pass ---------- */

.ws-filters-panel label {
  color: var(--ws-text-muted);
}

.ws-filters-panel .select2-container--default .select2-selection--multiple,
.ws-filters-panel .select2-container--default .select2-selection--single {
  background: var(--ws-bg-strip);
  border-color: var(--ws-border-input);
  color: var(--ws-text);
}

/* the container color above isn't enough for single-selects (Norm): select2.css sets
   color: #444 directly on the inner __rendered span, which beats anything inherited -- left alone,
   the selected norm renders dark grey on dark and reads as a disabled field */
.ws-filters-panel .select2-container--default .select2-selection--single .select2-selection__rendered {
  color: var(--ws-text);
}

.ws-filters-panel .select2-container--default .select2-selection--multiple .select2-selection__choice {
  background: var(--ws-bg-card-alt);
  border-color: var(--ws-border-input);
  color: var(--ws-text);
  /* select2's own base CSS gives every chip margin-top: 5px and no margin-bottom -- fine for its
     original float-based layout, but under the flex-wrap rule below it meant the top chip row
     always sat further from the box's top border than the bottom row sat from the bottom border,
     no matter how symmetric the container's own padding was made. Zeroed out here; the container's
     `gap` now does all the spacing, evenly, on every side. */
  margin: 0 !important;
}

/* select2's base CSS (not a dark-theme thing -- this is upstream) lays the chip row out with
   white-space: nowrap + overflow: hidden, single line only. With several chips selected by
   default (segments defaults to 5), that clips whatever doesn't fit -- including the inline
   type-ahead search field, which sits after all the chips in DOM order. It wasn't just hard to
   see, it was very likely being clipped out of the visible/clickable area entirely. Switching to
   flex-wrap lets the chips wrap across multiple lines instead of overflowing, and a min-width on
   the search field keeps it from collapsing to an invisible sliver when empty. */
.ws-filters-panel .select2-container--default .select2-selection--multiple .select2-selection__rendered {
  white-space: normal;
  overflow: visible;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* gap replaces select2's own per-chip margin-top for row spacing (see the __choice rule above) --
     symmetric container padding alone wasn't enough while chips still carried their own one-sided
     margin */
  gap: 6px;
  padding: 8px;
}

.ws-filters-panel .select2-search--inline .select2-search__field {
  min-width: 90px;
  /* select2's own CSS doesn't set a text color on this input at all -- it just inherits the
     browser default (near-black), unreadable against this theme's dark chip-row background.
     margin-top zeroed for the same reason as .select2-selection__choice above -- the container's
     own gap/padding handles spacing now. */
  color: var(--ws-text);
  background: transparent;
  margin-top: 0 !important;
}

/* !important here isn't a specificity shortcut -- it's necessary: select2.css's own
   .select2-dropdown / .select2-results__option rules have equal specificity to these (both single
   class selectors), and select2.css loads AFTER workspace-dark.css (its <link> in base.html sits
   right after this file's, on purpose) -- so on a plain specificity tie,
   select2's white background was winning the cascade by document order. Same fix needed for the
   jQuery UI autocomplete dropdown above didn't need !important only because dashboard.css (which
   defines .ui-autocomplete) loads before workspace-dark.css, not after. */
.select2-dropdown {
  background: var(--ws-bg-card) !important;
  border-color: var(--ws-border) !important;
  color: var(--ws-text) !important;
}

/* the flush border attaching the dropdown to the selection box is the correct look (reverted a
   margin-gap attempt here per the user -- detaching the border read as wrong, not an improvement).
   The actual crowding complaint was the *first option's* text sitting right against the selection
   box's own last chip row, with no breathing room -- fixed with padding-top on the options list
   itself, inside the still-attached border, instead of moving the border. */
.select2-container--default .select2-results__options {
  padding-top: 6px;
}

.select2-container--default .select2-results__option {
  color: var(--ws-text) !important;
}

.select2-container--default .select2-results__option[aria-selected="true"] {
  /* marks an already-picked option while scrolling, distinct from the (already-correct, bright
     blue) hover state right below -- a soft accent tint reads clearly without competing with it */
  background: rgba(79, 143, 247, 0.22) !important;
}

.select2-container--default .select2-results__option--highlighted[aria-selected] {
  background: var(--ws-accent) !important;
  color: var(--ws-accent-text) !important;
}

.select2-container--default .select2-results__group {
  color: var(--ws-text-muted) !important;
}

.select2-search--dropdown .select2-search__field {
  background: var(--ws-bg-strip) !important;
  border-color: var(--ws-border-input) !important;
  color: var(--ws-text) !important;
}

/* jQuery UI autocomplete dropdown (title search, Panel 1) -- dashboard.css hardcodes this to a
   white background (#ffffff) with no text color of its own, so it was inheriting body's light
   dark-theme text straight onto white: nearly unreadable. Overriding both here instead of touching
   dashboard.css itself. */

.ui-autocomplete {
  background-color: var(--ws-bg-card);
  border-color: var(--ws-border);
}

.ui-menu-item .ui-menu-item-wrapper {
  color: var(--ws-text);
}

.ui-menu-item .ui-menu-item-wrapper.ui-state-active {
  /* same fix as the header dropdown above -- --ws-bg-card-alt read as basically no highlight at
     all against this dropdown's own background */
  background: var(--ws-accent) !important;
  color: var(--ws-accent-text) !important;
}

/* modal dark pass -- shared across the app, not workspace-scoped, since the sign-out modal is the
   only one currently in play and it should match */

.modal-content {
  background: var(--ws-bg-card);
  color: var(--ws-text);
  border-color: var(--ws-border);
}

.modal-header,
.modal-footer {
  background: var(--ws-bg-card);
  border-color: var(--ws-border-soft);
}

/* Retention Chart (_retention_chart_panel3.html) -- fresh build off a PowerBI report view, see
   [[project_domestic_retention_chart_data_model]]. The bar list mirrors the PBI report's "By Weeks
   to Release" panel; bars are plain width:%% divs, same inline-style-driven approach the trailer/box
   office tables already use for their own heatmap coloring, just via a template-computed percent
   instead of a color. */

.ws-retention-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 1rem;
  margin-bottom: 1rem;
}

/* Intl only (_intl_retention_chart_panel3.html) -- grid, not flexbox space-between: the KPI/title
   block and the Wave Date block aren't the same width, so space-between's equal-gap-between-items
   wouldn't actually center the region bar in the middle column the way 1fr/auto/1fr does. Per the
   user 2026-09-08: the region bar used to sit in its own row above this one, wasting vertical space. */
.ws-retention-header-with-region {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}

.ws-retention-header-region-row {
  margin-bottom: 0; /* .ws-controls-row's own 12px margin-bottom doesn't apply inside this grid row */
  justify-content: center;
}

.ws-retention-header-label {
  display: block;
  font-size: 0.75rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--ws-text-muted);
}

.ws-retention-header-title {
  display: block;
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--ws-text-strong);
}

.ws-retention-header-wave {
  text-align: right;
}

/* scoped to the WTR column's own width now (moved inside .ws-retention-wtr-panel, above the "By
   Weeks to Release" heading), not the full page width -- per the user 2026-09-08, there was no
   reason for it to span wider than the data below it. */
.ws-retention-norm-table {
  margin-bottom: 1.25rem;
}

/* Details toggle -- centered inside .ws-retention-pie-panel (see .ws-retention-pie-toggle-row further
   down and _retention_chart_data_only.html's own comment for why there, not the header or a
   full-width row). Collapsing display:none's the WTR panel entirely (not just its contents) so the
   pie panel, the only flex item left in .ws-retention-body, can actually reflow into the freed space
   rather than sitting next to a merely-emptied column. */
.ws-retention-toggle-chevron {
  transition: transform 150ms ease;
}

.ws-retention-details-toggle[aria-expanded="false"] .ws-retention-toggle-chevron {
  transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
  .ws-retention-toggle-chevron { transition: none; }
}

.ws-retention-body {
  display: flex;
  gap: 1.5rem;
  align-items: flex-start;
  flex-wrap: wrap;
}

.ws-retention-body[data-collapsed="true"] {
  justify-content: center;
}

.ws-retention-body[data-collapsed="true"] .ws-retention-wtr-panel {
  display: none;
}

/* capped, not flex-grow-to-100% -- an edge-to-edge donut on a very wide monitor would just mean
   longer leader lines, not an easier-to-read chart. justify-content:center above centers this
   capped-width box in whatever space the WTR panel's absence freed up. */
.ws-retention-body[data-collapsed="true"] .ws-retention-pie-panel {
  flex: 0 1 900px;
  max-width: 900px;
  min-width: 0;
}

.ws-retention-body[data-collapsed="true"] .ws-retention-pie-canvas-wrap {
  height: 720px;
}

.ws-retention-wtr-panel {
  flex: 1 1 340px;
  min-width: 280px;
  max-width: 420px;
}

/* considerably wider than the WTR panel and given real height (ECharts renders into a plain sized
   div, not a canvas with an intrinsic size) -- the outside labels/leader lines for ~20 comparators,
   some with long titles, need real room on both axes to lay out without crowding. Widened further
   2026-09-08 (flex 2->3, min-width 420->560, wrap height 560->680) once the norm table moved out of
   the full-width header area, freeing up room for a bigger, less cramped donut -- per the user. */
.ws-retention-pie-panel {
  flex: 3 1 640px;
  min-width: 560px;
}

.ws-retention-pie-toggle-row {
  display: flex;
  justify-content: center;
  margin-top: 0.5rem;
}

.ws-retention-panel-heading {
  color: var(--ws-text-strong);
  margin-bottom: 0.75rem;
}

.ws-retention-wtr-group {
  margin-bottom: 0.85rem;
}

.ws-retention-wtr-bucket-label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--ws-text-faint);
  border-bottom: 1px dotted var(--ws-border);
  padding-bottom: 0.2rem;
  margin-bottom: 0.35rem;
}

.ws-retention-bar-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 90px 48px;
  align-items: center;
  gap: 0.5rem;
  padding: 0.15rem 0;
}

.ws-retention-bar-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.85rem;
  color: var(--ws-text);
}

.ws-retention-bar-row-target .ws-retention-bar-name {
  font-weight: 700;
  color: var(--ws-text-strong);
}

.ws-retention-bar-track {
  display: block;
  height: 0.6rem;
  border-radius: 3px;
  background: var(--ws-bg-input);
  overflow: hidden;
}

.ws-retention-bar-fill {
  display: block;
  height: 100%;
  background: var(--ws-accent);
  border-radius: 3px;
}

.ws-retention-bar-fill-target {
  background: var(--ws-text-strong);
}

.ws-retention-bar-value {
  font-size: 0.8rem;
  color: var(--ws-text-muted);
  text-align: right;
}

/* ECharts centers its drawing vertically within this box's full height by default (center:
   ['50%','50%']) -- a box taller than the chart + its outside labels actually need leaves dead space
   above and below rather than the chart "growing" into it. Dropped to 520px 2026-09-08 to kill a gap
   the user flagged, then brought back up to 640px the same day once they noticed that had also
   visibly shrunk the chart itself in the default view -- paired with the series' own center: ['50%',
   '46%'] nudge (comparison_dashboard.js) to bias the drawing toward the top of the box instead of
   relying on box height alone to control how much space sits above it. */
.ws-retention-pie-canvas-wrap {
  width: 100%;
  height: 640px;
}

/* ---------- light theme touch-ups ---------- */

/* Everything above already re-themes automatically via the [data-theme="light"] variable block
   near the top of this file, since it's written against var(--ws-*). The rules below are the
   exceptions -- spots that used a hardcoded literal instead of a variable (usually because they
   needed a color no existing --ws-* variable covered, e.g. a blue-tinted search box or a pastel
   metric-group header), so they need their own explicit light-mode value. Not covered here (out of
   scope for this prototype, see [[project_studio_light_theme_prototype]]): dashboard.css (no
   variables at all, still dark/light-inconsistent), and the heatmap/deviation cell colors
   studio_extras.py generates inline in Python -- both need their own follow-up pass. */

:where([data-theme="light"], [data-theme="silver"]) a { color: #2f6fe0; }
:where([data-theme="light"], [data-theme="silver"]) a:hover { color: #1d54b3; }

:is([data-theme="light"], [data-theme="silver"]) .ws-search-icon,
:is([data-theme="light"], [data-theme="silver"]) .ws-clear-search-btn:not(:hover),
:is([data-theme="light"], [data-theme="silver"]) .ws-search-wrap input[name="title_search"]::placeholder {
  color: #6b7d92;
}

:is([data-theme="light"], [data-theme="silver"]) .ws-search-wrap input[name="title_search"] {
  border-color: #b9c9dc;
  background: #eef3f9;
}

:is([data-theme="light"], [data-theme="silver"]) .ws-message-box {
  border-color: #b9cfe8;
  background: #e7f0fb;
}

:is([data-theme="light"], [data-theme="silver"]) :is(.ws-message-icon, .ws-message-box > svg) {
  color: #4a7bb8;
}

:is([data-theme="light"], [data-theme="silver"]) .ws-message-placeholder {
  color: #1f3a56;
}

:is([data-theme="light"], [data-theme="silver"]) .ws-poster {
  background: #eceef1;
  color: #b0b5bb;
}

:is([data-theme="light"], [data-theme="silver"]) .ws-view-btn-disabled,
:is([data-theme="light"], [data-theme="silver"]) .ws-view-btn-disabled:hover {
  background: #eceef1;
}

:is([data-theme="light"], [data-theme="silver"]) #dash-error .alert {
  background: #fdf1e0;
  border-color: #e8c894;
  color: #7a4e12;
}

:is([data-theme="light"], [data-theme="silver"]) #dash-error .alert .btn-close {
  filter: none;
}

:is([data-theme="light"], [data-theme="silver"]) .ws-box-office-banner {
  background: #eaf1fb;
  border-color: #bcd3ec;
}


:is([data-theme="light"], [data-theme="silver"]) tr.ws-compfinders-top-row {
  background: rgba(0, 0, 0, 0.035) !important;
}

:is([data-theme="light"], [data-theme="silver"]) #dash-results .table tbody tr.ws-compfinders-top-row:hover {
  background: rgba(0, 0, 0, 0.06) !important;
}

/* Star Fanship (_star_fanship_results.html) -- headshot avatar + name in the Star column. All
   var(--ws-*) tokens, so this re-themes automatically under [data-theme="light"] with no separate
   override block needed, same as every other rule already written this way in this file. */
.ws-star-cell {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.ws-star-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--ws-border);
  flex-shrink: 0;
}

.ws-star-avatar-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--ws-bg-card-alt);
  border: 1px solid var(--ws-border);
  color: var(--ws-text-muted);
  font-size: 0.75rem;
  font-weight: 600;
  flex-shrink: 0;
  text-transform: uppercase;
}

/* Groups ADD STAR + the Filters button on the right side of .ws-retention-header's single row --
   per the user, 2026-09-11: an earlier version gave ADD STAR its own row below the header, leaving
   a large empty gap the same way the header/Filters split did before that fix. */
.ws-star-fanship-header-controls {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/* A little extra breathing room before ADD STAR. It was added when the intl page's region strip sat
   right before it; that strip has had its own row since 2026-10-22, so ADD STAR now leads the
   controls on both pages and this just keeps them matching. */
.ws-add-star-label {
  margin-left: 0.5rem;
}

/* "Add Star" search box (_star_fanship_panel3.html) -- a secondary control living inline in its own
   controls row, so neutral-toned (var(--ws-border-input)/--ws-bg-input) rather than the blue-tinted
   treatment .ws-search-wrap input[name="title_search"] gets for Panel 1's primary target-title
   search -- deliberately not that same visual weight, since this never replaces the target. */
.ws-add-star-form {
  display: inline-block;
}

.ws-add-star-form input[name="star_search"] {
  width: 220px;
  height: 30px;
  border: 1px solid var(--ws-border-input);
  border-radius: 4px;
  padding: 0 10px;
  font-size: 13px;
  background: var(--ws-bg-input);
  color: var(--ws-text);
}

.ws-add-star-form input[name="star_search"]:focus {
  outline: none;
  border-color: var(--ws-accent);
  box-shadow: 0 0 0 3px rgba(79, 143, 247, 0.25);
}

#star-fanship-add-error:not(:empty) {
  display: flex;
  align-items: center;
}

/* Short-Form Videos: divider where a title's standard trailers end and its short-form spots begin
   (the service flags the first spot row, _short_form_title_block.html adds the class). Not applied to
   the title's first row, so a spots-only title doesn't get a doubled border under the tbody's own. */
tr.ws-short-form-spot-start > td:not(.role-cell):not(.title-label-cell) {
  border-top: 2px solid var(--ws-border-input);
}

/* ---------- Silver touch-ups ---------- */

/* Silver shares light theme's touch-ups (see the :is() selectors above), but those were tuned for
   near-white panels -- a #eef3f9 search box or #e7f0fb message box glows on a silver base. Pulled
   down into Silver's own range so the only bright-ish surfaces are the data cards themselves. */
/* :where() keeps these theme link colors at plain-`a` specificity, so any class that sets its own
   color (the navigator links, the header user menu) wins instead of turning blue */
:where([data-theme="silver"]) a { color: #1f4fa8; }
:where([data-theme="silver"]) a:hover { color: #163d85; }
[data-theme="silver"] .ws-search-wrap input[name="title_search"] {
  background: #d3dbe5;
  border-color: #8ea3bb;
}
[data-theme="silver"] .ws-message-box { background: #cdd8e5; border-color: #9fb3ca; }
[data-theme="silver"] .ws-poster { background: #b8bec5; color: #8a9199; }
[data-theme="silver"] .ws-view-btn-disabled,
[data-theme="silver"] .ws-view-btn-disabled:hover { background: #b3b9c0; }
[data-theme="silver"] .ws-box-office-banner { background: #cdd8e5; border-color: #9fb3ca; }
[data-theme="silver"] #dash-error .alert { background: #e6d6bd; border-color: #c6a468; }

/* theme picker swatches (user dropdown, see workspace_theme.js) */
.ws-theme-picker {
  display: flex;
  gap: 6px;
  padding: 4px 16px 8px;
}
.ws-theme-swatch {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 4px 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--ws-text-muted);
  font-size: 11px;
  cursor: pointer;
}
.ws-theme-swatch:hover { border-color: var(--ws-border-input); }
.ws-theme-swatch.active { border-color: var(--ws-accent); color: var(--ws-text-strong); }
.ws-theme-swatch-chip {
  width: 28px;
  height: 18px;
  border-radius: 3px;
  border: 1px solid rgba(128, 128, 128, 0.5);
}
.ws-theme-picker-label {
  padding: 4px 16px 0;
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--ws-text-faint);
}
/* same label heading the menu's first group (User Settings/Admin/Docs) -- a little extra space
   below, since a .dropdown-item follows it rather than the swatch row's own top padding */
.ws-menu-section-label {
  padding-bottom: 4px;
}

/* ---------- title-cell info tooltip (comparison_dashboard.js, _title_info_tooltip.html) ---------- */

.ws-title-tip {
  position: fixed;
  z-index: 1080; /* above sticky header (1020) and Bootstrap modals/backdrops */
  display: none;
  min-width: 170px;
  max-width: 280px;
  padding: 8px 10px;
  border-radius: 6px;
  border: 1px solid var(--ws-border);
  background: var(--ws-bg-card);
  color: var(--ws-text);
  font-size: 12px;
  line-height: 1.5;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
  pointer-events: none;
}

.ws-title-tip-show {
  display: block;
}

.ws-title-tip > div {
  display: flex;
  gap: 10px;
}

.ws-title-tip span {
  flex: 0 0 70px;
  color: var(--ws-text-faint);
}

/* "Hide (YEAR) in Titles" user setting -- the |title_name filter wraps each title's "(YYYY)" in
   .ws-title-year; html[data-hide-title-year] is stamped from the setting (base.html) and flipped
   live by the settings switch (workspace_theme.js). Display only. */
[data-hide-title-year] .ws-title-year {
  display: none;
}


/* Trailer Attributes (_trailer_attributes_panel3.html) -- comp chips, chart card */

.ws-attr-comp-label {
  font-size: 12px;
  color: var(--ws-text-muted);
}

.ws-attr-comps {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 4px 0 10px;
}

.ws-attr-comps-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ws-text-muted);
  margin-right: 2px;
}

.ws-attr-comp-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 4px 2px 10px;
  border: 1px solid var(--ws-border-input);
  border-radius: 999px;
  background: var(--ws-bg-card);
  color: var(--ws-text);
  font-size: 12.5px;
}

.ws-attr-comp-chip.is-empty {
  color: var(--ws-text-muted);
}

.ws-attr-comp-nodata {
  font-size: 11px;
}

/* the chip's remove button reuses the comparison tables' .ws-row-remove, which floats right inside
   a title cell -- here it just sits inline at the chip's end */
.ws-attr-comp-chip .ws-row-remove {
  float: none;
  margin: 0;
  opacity: 1;
}

.ws-attr-chart-card {
  background: var(--ws-bg-card);
  border: 1px solid var(--ws-border-soft);
  border-radius: 10px;
  padding: 12px 14px 8px;
}

.ws-attr-chart-title {
  font-size: 12px;
  color: var(--ws-text-muted);
  margin-bottom: 4px;
}

.ws-attr-chart {
  width: 100%;
  height: 470px;
}

/* its Loading… indicator takes no room while idle -- the generic .htmx-indicator only fades it out
   (opacity 0), which left a blank gap ahead of +Comps. htmx puts .htmx-request on it directly
   (hx-indicator), so it only takes space during a request */
#trailerAttrStatus:not(.htmx-request) {
  display: none;
}


/* Segment Ranking (_segment_ranking_results.html) -- ranking card on the left (scrolls inside the
   card, like PBI), Overall bars + trailer buttons + versions stacked on the right; one column under
   1100px */
.ws-rank-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  margin-top: 4px;
}

@media (max-width: 1100px) {
  .ws-rank-layout {
    grid-template-columns: minmax(0, 1fr);
  }
}

.ws-rank-side {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.ws-rank-card {
  background: var(--ws-bg-card);
  border: 1px solid var(--ws-border-soft);
  border-radius: 10px;
  padding: 12px 14px;
  min-width: 0;
}

.ws-rank-card-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--ws-text-strong);
  margin-bottom: 8px;
}

.ws-rank-card-sub {
  display: block;
  font-size: 12px;
  font-weight: 400;
  color: var(--ws-text-muted);
}

.ws-rank-card .table {
  margin-bottom: 0;
}

/* the ranking scrolls inside its card; its header row stays put */
.ws-rank-scroll {
  max-height: max(420px, calc(100vh - 230px));
  overflow-y: auto;
}

.ws-rank-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
}

.ws-rank-sort {
  border: none;
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: 600;
  padding: 0;
  cursor: pointer;
}

.ws-rank-sort::after {
  content: '';
  display: inline-block;
  width: 1em;
  font-size: 10px;
}

.ws-rank-sort:hover {
  color: var(--ws-text-strong);
}

th[aria-sort="descending"] .ws-rank-sort::after {
  content: '\25BC';
}

th[aria-sort="ascending"] .ws-rank-sort::after {
  content: '\25B2';
}

/* row hover -- same inset box-shadow tint as the other tables (a plain tr:hover background would be
   painted over by the heatmap cell's inline background) */
.ws-rank-table tbody tr:hover td,
.ws-t2t-table tbody tr:hover td {
  box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.08);
}

:is([data-theme="light"], [data-theme="silver"]) .ws-rank-table tbody tr:hover td,
:is([data-theme="light"], [data-theme="silver"]) .ws-t2t-table tbody tr:hover td {
  box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.05);
}

.ws-rank-pct {
  width: 110px;
}

.ws-rank-sample {
  width: 90px;
  font-variant-numeric: tabular-nums;
}

.ws-rank-sample.is-low {
  color: var(--ws-flag-low);
}

.ws-rank-sample.is-low i {
  font-size: 10px;
}

/* Overall bars -- plain divs, percentile = width, heatmap color */
.ws-rank-bars {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 4px 0 12px;
}

.ws-rank-bar-row {
  display: grid;
  grid-template-columns: minmax(64px, max-content) minmax(0, 1fr);
  gap: 10px;
  align-items: center;
}

.ws-rank-bar-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--ws-text);
  white-space: nowrap;
}

.ws-rank-bar-track {
  min-width: 0;
}

.ws-rank-bar {
  min-width: 2.6em;
  height: 30px;
  padding: 0 8px;
  border-radius: 4px;
  display: flex;
  align-items: center;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.ws-rank-bar-none,
.ws-rank-bars-empty {
  font-size: 12px;
  color: var(--ws-text-muted);
}

/* Below / At / Above Norm key -- swatches are the heatmap ramp's 0 / 50 / 100 points (50th
   percentile = the norm), same as PBI's legend */
.ws-rank-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin: -2px 0 8px;
  font-size: 11px;
  color: var(--ws-text-muted);
}

.ws-rank-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.ws-rank-legend-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
}

/* trailer buttons: checkboxes styled as toggle pills, all on by default -- same selected look as
   the other small toggles (.ws-percentile-switch-btn.active) */
.ws-rank-trailer-btns {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  border-top: 1px solid var(--ws-border-soft);
  padding-top: 12px;
}

.ws-rank-trailer-btn {
  position: relative;
  margin: 0;
  cursor: pointer;
}

.ws-rank-trailer-btn input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.ws-rank-trailer-btn span {
  display: inline-block;
  padding: 4px 14px;
  border: 1px solid var(--ws-border-input);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ws-text-muted);
}

.ws-rank-trailer-btn:hover span {
  color: var(--ws-text-strong);
}

.ws-rank-trailer-btn input:checked + span {
  background: var(--ws-accent-selected);
  border-color: transparent;
  color: var(--ws-accent-text);
}

.ws-rank-trailer-btn input:focus-visible + span {
  outline: 2px solid var(--ws-accent);
  outline-offset: 2px;
}

/* idle Loading… takes no room, same as Trailer Attributes' */
#segRankStatus:not(.htmx-request) {
  display: none;
}


/* Tracking to Trailer (_tracking_to_trailer_results.html) -- reuses Segment Ranking's layout/cards
   (.ws-rank-*); the table gets a wider share since it has 7 number columns, no sideways scroll at
   normal widths (PBI's version scrolls) */
.ws-t2t-layout {
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
}

@media (max-width: 1100px) {
  .ws-t2t-layout {
    grid-template-columns: minmax(0, 1fr);
  }
}

.ws-t2t-table th[data-t2t-col],
.ws-t2t-table td[data-t2t-col] {
  min-width: 62px;
}

.ws-t2t-table thead th.ws-t2t-group {
  padding-top: 3px;
  padding-bottom: 3px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ws-text-muted);
}

/* the trailer AVG column, set off from the tracking weeks */
.ws-t2t-table .ws-t2t-avg {
  border-left: 2px solid var(--ws-border-input);
}

/* the week buttons stand on their own (no switch track around them -- it read as a border around
   bordered pills); an off button keeps a thin outline, like the trailer buttons, so it still reads
   as a button */
.ws-percentile-switch.ws-t2t-weeks {
  flex-wrap: wrap;
  border: none;
  background: transparent;
  padding: 0;
  gap: 4px;
}

.ws-t2t-weeks .ws-percentile-switch-btn {
  border: 1px solid var(--ws-border-input);
}

.ws-t2t-weeks .ws-percentile-switch-btn.active {
  border-color: transparent;
}

/* Overall bars: one column per week + the AVG Trailer bar, heights = percentile */
.ws-t2t-bars {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr)) minmax(0, 1.1fr);
  column-gap: 8px;
  padding: 4px 0 12px;
}

.ws-t2t-bar-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.ws-t2t-bar-col.is-trailer {
  border-left: 1px dashed var(--ws-border-input);
  padding-left: 8px;
}

.ws-t2t-bar-track {
  height: 170px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.ws-t2t-bar {
  width: 100%;
  min-height: 22px;
  border-radius: 4px 4px 0 0;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 4px;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.ws-t2t-bar-label {
  text-align: center;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ws-text);
  margin-top: 4px;
}

.ws-t2t-bars-caption {
  text-align: center;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ws-text-muted);
  margin-top: 2px;
}

.ws-t2t-bars-caption.is-trailer {
  grid-column: 7;
}

#t2tStatus:not(.htmx-request) {
  display: none;
}


/* Latest Tracking (_latest_tracking_results.html) -- wave-first table: one row per title in each
   region's latest wave. REGIONS strip (shared) + FORMAT/TYPE pills above it, all client-side */
.ws-latest-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 18px;
  margin: 4px 0 8px;
}

.ws-latest-pill-group {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
}

/* same stand-alone pill look as Tracking to Trailer's week buttons */
.ws-latest-pill-group .ws-percentile-switch-btn {
  border: 1px solid var(--ws-border-input);
}

.ws-latest-pill-group .ws-percentile-switch-btn.active {
  border-color: transparent;
}

/* Format/Type pills sit at the row's right end, apart from the region strip (2026-10-21) */
.ws-latest-filters .ws-region-toggle-strip {
  margin-right: auto;
}

.ws-latest-info {
  display: inline-flex;
  color: var(--ws-text-faint);
  cursor: default;
}

.ws-latest-info svg {
  width: 15px;
  height: 15px;
}

.ws-latest-info:hover,
.ws-latest-info:focus-visible {
  color: var(--ws-text-strong);
}

/* a region with a newer wave in field: a small dot on its button's corner */
.ws-region-toggle.ws-region-infield {
  position: relative;
}

.ws-region-toggle.ws-region-infield::after {
  content: '';
  position: absolute;
  top: -3px;
  right: -3px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ws-attr-series-2);
  box-shadow: 0 0 0 2px var(--ws-bg-card);
}

/* ~350 rows across 12 regions: scrolls inside the stage with the header pinned */
.ws-latest-scroll {
  max-height: max(440px, calc(100vh - 250px));
  overflow: auto;
}

.ws-latest-table thead th {
  position: sticky;
  z-index: 1;
}

.ws-latest-table thead tr:first-child th {
  top: 0;
  white-space: nowrap;
}

/* second header row sits under the first -- its height is fixed by the one-line metric names */
.ws-latest-table thead tr:nth-child(2) th {
  top: 31px;
}

.ws-latest-table td:nth-child(3) {
  white-space: nowrap;
}

.ws-latest-table tbody tr.is-group-start td {
  border-top: 2px solid var(--ws-border-input);
}

.ws-latest-table tbody tr.ws-latest-target td:nth-child(-n+3) {
  background: color-mix(in srgb, var(--ws-accent) 22%, var(--ws-bg-paper));
}

.ws-latest-table tbody tr:hover td {
  box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.08);
}

:is([data-theme="light"], [data-theme="silver"]) .ws-latest-table tbody tr:hover td {
  box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.05);
}

#latestTrackingStatus:not(.htmx-request) {
  display: none;
}
